Di mana saya bisa mendapatkan algoritme pencarian biner C ++ yang "berguna"?

106

Saya memerlukan algoritma pencarian biner yang kompatibel dengan wadah C ++ STL, seperti std::binary_searchdi <algorithm>header perpustakaan standar , tetapi saya membutuhkannya untuk mengembalikan iterator yang menunjuk pada hasil, bukan boolean sederhana yang memberi tahu saya jika elemen itu ada.

(Sebagai catatan tambahan, apa yang dipikirkan komite standar ketika mereka mendefinisikan API untuk binary_search ?!)

Perhatian utama saya di sini adalah bahwa saya memerlukan kecepatan pencarian biner, jadi meskipun saya dapat menemukan data dengan algoritme lain, seperti yang disebutkan di bawah ini, saya ingin memanfaatkan fakta bahwa data saya diurutkan untuk mendapatkan manfaat dari biner pencarian, bukan pencarian linier.

sejauh ini lower_bounddan upper_boundgagal jika datum hilang:

//lousy pseudo code
vector(1,2,3,4,6,7,8,9,0) //notice no 5
iter = lower_bound_or_upper_bound(start,end,5)
iter != 5 && iter !=end //not returning end as usual, instead it'll return 4 or 6

Catatan: Saya juga baik-baik saja menggunakan algoritme yang tidak termasuk dalam namespace std asalkan kompatibel dengan container. Seperti, katakanlah boost::binary_search,.

Robert Gould
sumber
2
Mengenai edit: itulah mengapa std :: equal_range adalah solusinya. Jika tidak, Anda harus menguji kesetaraan (atau kesetaraan agar lebih)
Luc Hermitte
Anda harus menguji kesetaraan setelah menggunakan (bawah / atas) _bound (lihat jawaban di bawah).
Luc Touraille
Dokumentasi lower_bound dan upper_bound menyatakan bahwa kisaran harus diurutkan, dan karenanya dapat diterapkan sebagai pencarian biner.
vividos
@vividos, hore! Anda hanya menemukan bagian dokumentasi yang perlu saya ketahui! Terima kasih!
Robert Gould
Robert, algoritme lower / upper_bound / equal_range tidak berfungsi dengan rentang yang tidak disortir. Anda hanya beruntung melihat mereka bekerja dengan sampel elemen yang Anda ambil.
Luc Hermitte

Jawaban:

97

Tidak ada fungsi seperti itu, tetapi Anda dapat menulis yang sederhana menggunakan std::lower_bound, std::upper_boundatau std::equal_range.

Implementasi sederhana bisa jadi

template<class Iter, class T>
Iter binary_find(Iter begin, Iter end, T val)
{
    // Finds the lower bound in at most log(last - first) + 1 comparisons
    Iter i = std::lower_bound(begin, end, val);

    if (i != end && !(val < *i))
        return i; // found
    else
        return end; // not found
}

Solusi lain adalah menggunakan a std::set, yang menjamin pengurutan elemen dan menyediakan metode iterator find(T key)yang mengembalikan iterator ke item yang diberikan. Namun, persyaratan Anda mungkin tidak kompatibel dengan penggunaan satu set (misalnya jika Anda perlu menyimpan elemen yang sama beberapa kali).

Luc Touraille
sumber
ya ini berhasil, dan saya memiliki implementasi serupa sekarang, namun ini adalah implementasi yang "naif", dalam arti tidak memanfaatkan konteks situasi, dalam hal ini data yang diurutkan.
Robert Gould
5
Saya tidak begitu mengerti komentar Anda, karena lower_bound hanya dapat digunakan pada data yang diurutkan. Kompleksitas lebih rendah daripada menggunakan find (lihat edit).
Luc Touraille
4
Untuk melengkapi jawaban Luc, periksa artikel klasik Matt Austern Why You Shouldn't Use set, dan What You Should Use Just (C ++ Report 12: 4, April 2000) untuk memahami mengapa pencarian biner dengan vektor terurut biasanya lebih disukai daripada std :: set , yang merupakan wadah asosiatif berbasis pohon.
ZunTzu
16
Jangan gunakan *i == val! Lebih baik digunakan !(val < *i). Alasannya adalah karena lower_boundmenggunakan <, bukan ==(yaitu Tbahkan tidak diperlukan untuk dapat dibandingkan dengan kesetaraan). (Lihat STL Efektif Scott Meyers untuk penjelasan tentang perbedaan antara kesetaraan dan kesetaraan .)
gx_
1
@ CanKavaklıoğlu Tidak ada elemen yang terletak di end. Rentang di pustaka standar C ++ diwakili dengan interval setengah terbuka: "poin" iterator akhir setelah elemen terakhir. Dengan demikian, dapat dikembalikan oleh algoritme untuk menunjukkan bahwa tidak ada nilai yang ditemukan.
Luc Touraille
9

Anda harus melihatnya std::equal_range. Ini akan mengembalikan sepasang iterator ke kisaran semua hasil.

Luc Hermitte
sumber
Menurut cplusplus.com/reference/algorithm/equal_range biaya std :: equal_range kira-kira dua kali lebih tinggi dari std :: lower_bound. Tampaknya itu membungkus panggilan ke std :: lower_bound dan panggilan ke std :: upper_bound. Jika Anda tahu bahwa data Anda tidak memiliki duplikat maka itu berlebihan dan std :: lower_bound (seperti yang ditunjukkan pada jawaban teratas) adalah pilihan terbaik.
Bruce Dawson
@BruceDawson: cplusplus.com hanya memberikan implementasi referensi untuk menentukan perilaku ; untuk implementasi yang sebenarnya, Anda dapat memeriksa perpustakaan standar favorit Anda. Misalnya, di llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm kita dapat melihat bahwa panggilan ke lower_bound dan upper_bound dilakukan pada interval terputus-putus (setelah beberapa pencarian biner manual). Namun demikian, kemungkinan akan lebih mahal, terutama pada rentang dengan beberapa nilai yang cocok.
Matthieu M.
6

Ada satu set di antaranya:

http://www.sgi.com/tech/stl/table_of_contents.html

Pencarian untuk:

Di catatan terpisah:

Mereka mungkin berpikir bahwa penampung penelusuran dapat menghasilkan lebih dari satu hasil. Tetapi pada kesempatan aneh di mana Anda hanya perlu menguji keberadaan versi yang dioptimalkan juga akan menyenangkan.

Martin York
sumber
3
binary_search tidak mengembalikan iterator seperti yang saya sebutkan sebelumnya, itulah mengapa saya mencari alternatif.
Robert Gould
1
Ya saya tahu. Tapi itu cocok dengan kumpulan algoritma pencarian biner. Jadi itu bagus untuk diketahui orang lain.
Martin York
8
binary_search, seperti banyak hal lain di STL, dinamai salah. Aku benci itu. Menguji keberadaan tidak sama dengan mencari sesuatu.
OregonGhost
2
Fungsi pencarian biner ini tidak berguna jika Anda ingin mengetahui indeks elemen yang Anda cari. Saya harus menulis fungsi rekursif saya sendiri untuk tugas ini. Saya harap ini, template <class T> int bindary_search (const T & item), harus ditambahkan ke versi C ++ berikutnya.
Kemin Zhou
3

Jika std :: lower_bound terlalu rendah untuk Anda sukai, Anda mungkin ingin memeriksa boost :: container :: flat_multiset . Ini adalah pengganti drop-in untuk std :: multiset yang diimplementasikan sebagai vektor yang diurutkan menggunakan pencarian biner.

ZunTzu
sumber
1
Tautan yang bagus; dan juga tautan yang bagus di tautan: lafstern.org/matt/col1.pdf , yang menjelaskan bagaimana pencarian diimplementasikan dengan vektor yang diurutkan, daripada ditetapkan (meskipun keduanya adalah log (N)), memiliki konstanta proporsionalitas yang jauh lebih baik dan ~ dua kali lebih cepat (kerugiannya menjadi waktu INSERTION lebih besar).
Dan Nissenbaum
2

Implementasi terpendek, bertanya-tanya mengapa tidak disertakan dalam pustaka standar:

template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
{
    // Note: BOTH type T and the type after ForwardIt is dereferenced 
    // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. 
    // This is stricter than lower_bound requirement (see above)

    first = std::lower_bound(first, last, value, comp);
    return first != last && !comp(value, *first) ? first : last;
}

Dari https://en.cppreference.com/w/cpp/algorithm/lower_bound

trozen
sumber
Saya dapat memikirkan dua alasan mengapa ini tidak ada di pustaka standar: Mereka pikir itu mudah diimplementasikan, tetapi alasan utamanya mungkin karena itu mungkin memerlukan versi operator () () terbalik jika nilai tidak dapat dipertukarkan dengan * pertama.
user877329
1

Periksa fungsi ini, qBinaryFind :

RandomAccessIterator qBinaryFind ( RandomAccessIterator begin, RandomAccessIterator end, const T & value )

Melakukan pencarian biner dari rentang [awal, akhir) dan mengembalikan posisi kemunculan nilai. Jika tidak ada kemunculan nilai, pengembalian berakhir.

Item dalam rentang [awal, akhir) harus diurutkan dalam urutan menaik; lihat qSort ().

Jika ada banyak kemunculan dengan nilai yang sama, salah satunya dapat dikembalikan. Gunakan qLowerBound () atau qUpperBound () jika Anda membutuhkan kontrol yang lebih baik.

Contoh:

QVector<int> vect;
 vect << 3 << 3 << 6 << 6 << 6 << 8;

 QVector<int>::iterator i =
         qBinaryFind(vect.begin(), vect.end(), 6);
 // i == vect.begin() + 2 (or 3 or 4)

Fungsi ini termasuk dalam <QtAlgorithms>tajuk yang merupakan bagian dari pustaka Qt .

Lawand
sumber
1
Sayangnya algoritme ini tidak kompatibel dengan kontainer STL.
bartolo-otrit
0

std :: lower_bound () :)

moogs
sumber
OP: "sejauh ini batas_bawah dan batas_atas gagal, karena ..."
underscore_d
0
int BinarySearch(vector<int> array,int var)
{ 
    //array should be sorted in ascending order in this case  
    int start=0;
    int end=array.size()-1;
    while(start<=end){
        int mid=(start+end)/2;
        if(array[mid]==var){
            return mid;
        }
        else if(var<array[mid]){
            end=mid-1;
        }
        else{
            start=mid+1;
        }
    }
    return 0;
}

Contoh: Misalkan sebuah array, A = [1,2,3,4,5,6,7,8,9] Misalkan Anda ingin mencari indeks 3 Awalnya, start = 0 dan end = 9-1 = 8 Sekarang , sejak awal <= akhir; pertengahan = 4; (array [mid] yaitu 5)! = 3 Sekarang, 3 terletak di kiri mid karena lebih kecil dari 5. Oleh karena itu, kita hanya mencari bagian kiri array. Oleh karena itu, sekarang start = 0 dan end = 3; mid = 2. Sejak array [mid] == 3, maka kita mendapatkan nomor yang kita cari. Karenanya, kami mengembalikan indeksnya yang sama dengan mid.

Siddharth Kumar Shukla
sumber
1
Memiliki kode itu bagus, tetapi Anda dapat meningkatkan jawabannya dengan memberikan penjelasan singkat tentang cara kerjanya untuk orang yang baru mengenal bahasa tersebut.
Taegost
Seseorang salah menandai postingan Anda sebagai berkualitas rendah . Jawaban khusus kode tidak berkualitas rendah . Apakah itu mencoba menjawab pertanyaan? Jika tidak, tandai sebagai 'bukan jawaban' atau rekomendasikan penghapusan (jika dalam antrian tinjauan). b) Apakah secara teknis tidak benar? Tidak suka atau berkomentar.
Wai Ha Lee
0

Solusi yang mengembalikan posisi di dalam rentang bisa seperti ini, hanya menggunakan operasi pada iterator (itu harus berfungsi bahkan jika iterator tidak aritmatika):

template <class InputIterator, typename T>
size_t BinarySearchPos(InputIterator first, InputIterator last, const T& val)
{       
    const InputIterator beginIt = first;
    InputIterator element = first;
    size_t p = 0;
    size_t shift = 0;
    while((first <= last)) 
    {
        p = std::distance(beginIt, first);
        size_t u = std::distance(beginIt, last);
        size_t m = p + (u-p)/2;  // overflow safe (p+u)/2
        std::advance(element, m - shift);
        shift = m;
        if(*element == val) 
            return m; // value found at position  m
        if(val > *element)
            first = element++;
        else
            last  = element--;

    }
    // if you are here the value is not present in the list, 
    // however if there are the value should be at position u
    // (here p==u)
    return p;

}
Michele Belotti
sumber