"Kullanışlı" bir C ++ ikili arama algoritmasını nereden edinebilirim?


106

std::binary_searchStandart kitaplığın <algorithm>başlığında olduğu gibi, C ++ STL kapsayıcılarıyla uyumlu bir ikili arama algoritmasına ihtiyacım var , ancak öğenin var olup olmadığını söyleyen basit bir boole değil, sonucu işaret eden yineleyiciyi döndürmek için ona ihtiyacım var.

(Bir yan not olarak, standart komite binary_search için API'yi tanımlarken ne düşünüyordu ?!)

Buradaki temel endişem, ikili aramanın hızına ihtiyacım olmasıdır, bu nedenle verileri diğer algoritmalarla bulabilsem de, aşağıda belirtildiği gibi, verilerimin bir ikili programın avantajlarından yararlanacak şekilde sıralanması gerçeğinden yararlanmak istiyorum. arama, doğrusal bir arama değil.

Şimdiye kadar lower_boundve upper_boundveri eksikse başarısız:

//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

Not: Ayrıca, kapsayıcılarla uyumlu olduğu sürece std ad alanına ait olmayan bir algoritma kullanıyorum. Mesela, diyelim boost::binary_search.


2
Düzenleme ile ilgili olarak: bu yüzden std :: eşit_aralık çözümdür. Aksi takdirde, eşitliği (veya daha fazla olması için) test etmeniz gerekecek
Luc Hermitte

(Alt / üst) _bound kullandıktan sonra eşitliği test etmelisiniz (aşağıdaki yanıta bakın).
Luc Touraille

lower_bound ve upper_bound belgeleri, aralığın sıralanması gerektiğini ve bu nedenle ikili arama olarak uygulanabileceğini belirtir.
vividos

@vividos, yaşasın! sadece bilmem gereken dokümantasyon parçasını buldun! Teşekkürler!
Robert Gould

Robert, alt / üst_bound / eşit_aralık algoritmaları sıralanmamış aralıklarla çalışmaz. Onları aldığınız element örneğiyle çalışırken gördüğünüz için şanslısınız.
Luc Hermitte

Yanıtlar:


97

Orada böyle bir işlev, ancak kullanarak basit bir yazabilir std::lower_bound, std::upper_boundveya std::equal_range.

Basit bir uygulama olabilir

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
}

Diğer bir çözüm std::set, öğelerin sıralanmasını garanti eden iterator find(T key)ve verilen öğeye bir yineleyici döndüren bir yöntem sağlayan a kullanmaktır . Ancak, gereksinimleriniz bir setin kullanımıyla uyumlu olmayabilir (örneğin, aynı öğeyi birden çok kez depolamanız gerekiyorsa).


evet bu işe yarıyor ve şu anda benzer bir uygulamam var, ancak bu "naif" bir uygulama, yani durumun bağlamını, bu durumda sıralı verileri kullanmaması anlamında.
Robert Gould

5
Lower_bound yalnızca sıralı verilerde kullanılabileceğinden yorumunuzu gerçekten anlamıyorum. Karmaşıklık, bul'u kullanmaktan daha düşüktür (bkz. Düzenleme).
Luc Touraille

4
Luc'un cevabını tamamlamak için, sıralı vektörlerle ikili aramanın neden genellikle std :: set'e tercih edildiğini anlamak için Matt Austern'in Neden Seti Kullanmamalısınız ve Bunun Yerine Ne Kullanmalısınız (C ++ Raporu 12: 4, Nisan 2000) adlı klasik makalesine bakın. , ağaç tabanlı bir ilişkisel kapsayıcıdır.
ZunTzu

16
Kullanmayın *i == val! Daha çok kullanın !(val < *i). Bunun nedeni ise lower_boundkullandığı <değil ==(yani Tbile eşitlik-karşılaştırılabilir olması gerekli değildir). ( Eşitlik ve eşdeğerlik arasındaki farkın açıklaması için Scott Meyers'in Etkili
STL'sine bakın

1
@ CanKavaklıoğlu adresinde herhangi bir eleman bulunmamaktadır end. C ++ standart kitaplığındaki aralıklar yarı açık aralıklarla temsil edilir: son yineleyici , son öğeden sonra "noktalar" . Bu nedenle, hiçbir değerin bulunmadığını belirtmek için algoritmalar tarafından döndürülebilir.
Luc Touraille

9

Bir bakmalısın std::equal_range. Tüm sonuçların aralığına bir çift yineleyici döndürecektir.


Cplusplus.com/reference/algorithm/equal_range'a göre std :: equ_range'in maliyeti std :: lower_bound'un yaklaşık iki katıdır. Görünüşe göre std :: lower_bound çağrısını ve std :: upper_bound çağrısını sarmalıyor. Verilerinizin kopyaları olmadığını biliyorsanız, bu aşırı ve std :: lower_bound (en üstteki yanıtta gösterildiği gibi) en iyi seçimdir.
Bruce Dawson

@BruceDawson: cplusplus.com sadece davranışı belirtmek için bir referans uygulama verir ; gerçek bir uygulama için favori standart kitaplığınızı kontrol edebilirsiniz. Örneğin, llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm'de , alt_bound ve üst_bound çağrılarının ayrık aralıklarla (bazı manuel ikili aramalardan sonra) yapıldığını görebiliriz. Bununla birlikte, özellikle birden çok değerle eşleşen aralıklarda daha pahalı olması muhtemeldir.
Matthieu M.

6

Bir dizi var:

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

Aramak:

Ayrı bir notta:

Muhtemelen kapsayıcıları aramanın birden fazla sonucu ifade edebileceğini düşünüyorlardı. Ancak, sadece varoluşu test etmeniz gereken garip durumda, optimize edilmiş bir sürüm de iyi olurdu.


3
binary_search daha önce bahsettiğim gibi bir yineleyici döndürmüyor, bu yüzden bir alternatif arıyorum.
Robert Gould

1
Evet biliyorum. Ancak ikili arama algoritmaları kümesine uyuyor. Bu yüzden başkalarının bilmesi güzel.
Martin York

8
binary_search, STL'deki diğer pek çok şey gibi yanlış adlandırılmıştır. Bundan nefret ediyorum. Varoluşu test etmek, bir şeyi aramakla aynı şey değildir.
OregonGhost

2
Bu ikili arama fonksiyonları, aradığınız elemanın indeksini bilmek istediğinizde kullanışlı değildir. Bu görev için kendi özyinelemeli fonksiyonumu yazmalıyım. Umarım bu şablon <class T> int bindary_search (const T & item), bir sonraki C ++ sürümüne eklenmelidir.
Kemin Zhou

3

Eğer std :: lower_bound beğeninize göre çok düşük seviyedeyse, boost :: container :: flat_multiset'i kontrol etmek isteyebilirsiniz . İkili arama kullanılarak sıralanmış bir vektör olarak uygulanan std :: multiset yerine açılan bir ikamedir.


1
İyi bağlantı; ve aynı zamanda iyi bağlantı içinde link: lafstern.org/matt/col1.pdf , (hem günlüğü (K) olsa) aramaları yerine sette daha sıralanmış bir vektör ile uygulanan açıklamaktadır sahip önemli ölçüde ~ orantılılık iyi sabitlerini ve vardır iki kat daha hızlı (dezavantaj daha büyük INSERTION süresidir).
Dan Nissenbaum

2

En kısa uygulama, neden standart kitaplığa dahil edilmediğini merak ederek:

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;
}

Gönderen https://en.cppreference.com/w/cpp/algorithm/lower_bound


Bunun standart kitaplıkta olmaması iki neden düşünebilirim: Uygulamanın kolay olduğunu düşünüyorlar, ancak ana neden muhtemelen, değer önce * ile değiştirilemezse, operatörün () () tersine çevrilmiş bir sürümünü gerektirmesidir.
user877329

1

Bu işlevi kontrol edin, qBinaryFind :

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

Aralık [başlangıç, bitiş) için ikili bir arama gerçekleştirir ve bir değer oluşumunun konumunu döndürür. Değer oluşumu yoksa, end döndürür.

[Başlangıç, bitiş) aralığındaki öğeler artan sırada sıralanmalıdır; bkz. qSort ().

Aynı değerin birçok oluşumu varsa, bunlardan herhangi biri döndürülebilir. Daha hassas kontrole ihtiyacınız varsa qLowerBound () veya qUpperBound () kullanın.

Misal:

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)

İşlev, Qt kitaplığının <QtAlgorithms>bir parçası olan başlığa dahil edilmiştir .


1
Maalesef bu algoritma STL kapsayıcılarıyla uyumlu değil.
bartolo-otrit


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;
}

Örnek: Bir dizi düşünün, A = [1,2,3,4,5,6,7,8,9] 3'ün dizinini aramak istediğinizi varsayalım Başlangıç ​​= 0 ve bitiş = 9-1 = 8 Şimdi başlangıç ​​<= bitiş; mid = 4; (array [mid], ki bu 5'tir)! = 3 Şimdi, 3, 5'ten küçük olduğu için ortanın solunda yer alır. Bu nedenle, dizinin yalnızca sol kısmını ararız. Dolayısıyla, şimdi start = 0 ve end = 3; mid = 2. Dizi [mid] == 3 olduğundan, aradığımız sayıyı aldık. Dolayısıyla, mid'e eşit olan endeksini döndürürüz.


1
Koda sahip olmak iyidir, ancak dilde yeni olan insanlar için nasıl çalıştığına dair kısa bir açıklama sağlayarak cevabı iyileştirebilirsiniz.
Taegost

Birisi yayınınızı yanlış bir şekilde düşük kaliteli olarak işaretledi . Bir kod tek cevap düşük kaliteli değildir . Soruyu cevaplamaya çalışıyor mu? Değilse, 'yanıt değil' olarak işaretleyin veya silmeyi önerin (inceleme kuyruğundaysa). b) Teknik olarak yanlış mı? Olumsuz oy verin veya yorum yapın.
Wai Ha Lee

0

Aralık içindeki konumu döndüren bir çözüm, yalnızca yineleyiciler üzerindeki işlemleri kullanarak şöyle olabilir (yineleyici aritmetik olmasa bile çalışmalıdır):

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;

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.