Algoritma cepat untuk mencari array float yang diurutkan untuk menemukan pasangan float yang mengurung nilai input

10

Saya memiliki serangkaian float, diurutkan dari yang terkecil hingga terbesar, dan harus dapat memilih float terdekat yang lebih besar dari atau kurang dari nilai input yang diteruskan. Nilai input ini tidak harus hadir sebagai nilai dalam array.

Pendekatan naif adalah melakukan pencarian linear sederhana melalui array. Itu mungkin terlihat seperti ini:

void FindClosestFloatsInArray( float input, std::vector<float> array, 
                               float *min_out, float *max_out )
{
    assert( input >= array[0] && input < array[ array.size()-1 ] );
    for( int i = 1; i < array.size(); i++ )
    {
        if ( array[i] >= input )
        {
            *min = array[i-1];
            *max = array[i];
        }
    }
}

Tapi jelas saat array semakin besar, ini akan menjadi semakin lambat.

Adakah yang punya ide tentang algoritma yang memungkinkan saya menemukan data ini secara lebih optimal? Saya sudah beralih ke pencarian biner, yang agak memperbaiki masalah, tetapi masih jauh lebih lambat dari yang saya inginkan, dan karena saya tidak benar-benar mencari nilai spesifik yang ada dalam array, itu tidak akan pernah dapat mengakhiri dini.

Informasi lebih lanjut: Nilai floating point dalam array tidak harus didistribusikan secara merata (yaitu, array dapat terdiri dari nilai "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f ".

Saya melakukan operasi ini ratusan ribu kali, tetapi saya dapat melakukan sejumlah pra-pemrosesan pada array float, jika itu akan meningkatkan waktu pencarian. Saya benar-benar dapat mengubah menggunakan sesuatu selain vektor untuk menyimpannya, jika itu akan membantu.

Trevor Powell
sumber
Apa yang membuat Anda berpikir bahwa pencarian biner Anda tidak dapat dihentikan lebih awal? Tentunya Anda bisa menguji elemen-elemen di i dan i +1 untuk melihat apakah mereka mengelompokkan nilai target, dan mengakhiri jika ya?
Paul R
Sebagai alternatif, saya dapat menguji elemen-elemen di i dan i-1 untuk melihat apakah elemen-elemen tersebut mengurung nilai target. Saya juga perlu menguji apakah 'i' adalah> = array.size () - 1 sehingga saya dapat menghindari melakukan tes Anda, dan apakah itu <= 0 sehingga saya dapat menghindari melakukan tes saya ... itu sebenarnya banyak persyaratan tambahan untuk tampil di setiap langkah, untuk memeriksa awal-keluar. Saya membayangkan mereka akan banyak memperlambat algoritma, meskipun saya akan mengakui bahwa saya belum benar-benar memprofilkannya.
Trevor Powell
3
Tidak perlu terlalu rumit - jika array Anda berukuran N maka Anda hanya perlu memperlakukannya seolah-olah itu berukuran N - 1. Dengan begitu selalu ada elemen yang valid di i + 1. Anda melakukan pencarian biner melalui elemen N - 1 untuk elemen i yang kurang dari nilai target Anda, dengan elemen i + 1 lebih besar dari nilai target.
Paul R

Jawaban:

11

Kode dalam pertanyaan (pencarian linear), seperti yang Anda tunjukkan dengan benar, akan menjadi lambat untuk array float besar. Secara teknis itu O (n) di mana n adalah jumlah nilai float di array Anda.

Secara umum, yang terbaik yang dapat Anda lakukan untuk menemukan nilai dalam array yang dipesan adalah pencarian pohon rekursif dari beberapa jenis (misalnya pencarian biner), dalam hal ini Anda dapat mencapai waktu pencarian O (log n) dalam jumlah elemen dalam array Anda. O (log n) jauh lebih baik daripada O (n) untuk nilai n yang besar.

Karenanya, pendekatan yang saya sarankan adalah pencarian biner sederhana dari array , yaitu:

  1. Tetapkan indeks integer min / maks untuk mencakup seluruh array float Anda
  2. uji nilai di tengah rentang pada indeks mid = (min + maks / 2) terhadap nilai pencarian x
  3. jika x lebih rendah dari nilai ini, atur max ke mid, atau atur min ke mid
  4. ulangi (2-4) sampai Anda menemukan nilai yang benar

Ini adalah algoritma O (log n) yang harusnya cukup cepat untuk hampir semua situasi. Secara intuitif, ini berfungsi dengan mengurangi separuh rentang yang akan dicari pada setiap langkah hingga Anda menemukan nilai yang benar.

Sangat sulit untuk mengalahkan pencarian biner sederhana, jadi jika Anda sudah menerapkan ini dengan benar maka Anda mungkin sudah cukup dekat dengan optimal. Namun, jika Anda mengetahui distribusi data dan / atau memiliki rentang nilai pencarian terbatas (x), masih ada beberapa trik lanjutan lainnya yang dapat Anda coba:

  • Bucketing - membuat bucket (misalnya untuk setiap interval antara dua bilangan bulat), yang masing-masing berisi daftar nilai float yang lebih kecil di antara dua bilangan bulat yang terikat ditambah dua nilai tepat di bawah dan tepat di atas setiap rentang. Anda kemudian dapat memulai pencarian Anda di (trunc (x) +0.5). Ini akan memberi Anda percepatan yang baik jika Anda memilih ember berukuran tepat (ini secara efektif meningkatkan faktor percabangan pohon .....). Jika bilangan bulat tidak bekerja untuk Anda, maka Anda dapat mencoba ember dengan presisi titik tetap lainnya (mis. Kelipatan 1/16).
  • Pemetaan bit - jika rentang nilai pencarian yang mungkin cukup kecil, Anda bisa mencoba membuat tabel pencarian besar yang diindeks oleh nilai bitwise x. Ini akan menjadi O (1) tetapi Anda mungkin membutuhkan banyak memori yang akan sangat tidak ramah pada cache Anda ... jadi gunakan dengan hati-hati. Ini terutama tidak menyenangkan karena Anda mencari nilai float, jadi Anda mungkin perlu beberapa GB untuk memperhitungkan semua bit yang kurang signifikan ......
  • Pembulatan dan hashing - tabel hash mungkin bukan struktur data terbaik untuk masalah ini, tetapi jika Anda dapat bertahan kehilangan sedikit akurasi mereka bisa bekerja - cukup kumpulkan bit terendah dari nilai pencarian Anda dan gunakan hashmap untuk secara langsung melihat ke atas nilai yang benar. Anda harus bereksperimen pada trade-off yang tepat antara ukuran dan presisi hashmap, dan juga memastikan bahwa semua nilai hash yang mungkin terisi sehingga ini bisa sedikit rumit ......
  • Penyeimbangan pohon - pohon ideal Anda harus memiliki peluang 50% ke kiri atau kanan. Jadi jika Anda membuat pohon berdasarkan distribusi nilai pencarian (x), maka Anda dapat mengoptimalkan pohon untuk menghasilkan jawaban dengan jumlah tes minimal. Ini mungkin merupakan solusi yang baik jika banyak nilai dalam array float Anda sangat berdekatan, karena itu akan memungkinkan Anda untuk menghindari mencari cabang-cabang ini terlalu sering.
  • Pohon bit-kritis - ini masih pohon (jadi tetap O (log n) ...) tetapi beberapa kasus: Anda perlu mengubah float Anda ke dalam format titik tetap untuk membuat perbandingan berfungsi

Namun, kecuali Anda berada dalam situasi yang sangat istimewa, saya mungkin akan merekomendasikan tetap dengan pencarian biner sederhana. Alasan:

  • itu jauh lebih mudah diimplementasikan
  • ini sangat cepat untuk sebagian besar kasus umum
  • overhead tambahan dari pendekatan yang lebih kompleks (mis. penggunaan memori yang lebih tinggi / tekanan cache) sering melebihi keuntungan teoretis minor
  • itu akan lebih kuat untuk perubahan di masa depan dalam distribusi data ....
mikera
sumber
1

Ini tampaknya cukup sederhana:

Lakukan pencarian biner untuk float yang ingin Anda ikat - O (log n) waktu.

Kemudian elemen di sebelah kiri adalah batas bawah, dan elemen di sebelah kanannya adalah batas atas.

Ankit Soni
sumber
0

Jawaban yang jelas adalah menyimpan float di pohon . Mendukung operasi 'sebelumnya' dan 'berikutnya' sepele di pohon. Jadi lakukan saja 'berikutnya' pada nilai Anda, dan kemudian lakukan 'sebelumnya' pada nilai yang Anda temukan di langkah pertama.

David Schwartz
sumber
1
Ini pada dasarnya sama dengan pencarian biner.
kevin cline
-1

Makalah ini ("pencarian sublogaritmik tanpa perkalian") mungkin menarik; bahkan berisi beberapa kode sumber. Untuk keperluan perbandingan, Anda bisa memperlakukan bilangan float sebagai bilangan bulat dengan pola bit yang sama; ini adalah salah satu tujuan desain standar floating point IEEE.

zvrba
sumber