Dove posso trovare un algoritmo di ricerca binaria C ++ "utile"?


106

Ho bisogno di un algoritmo di ricerca binaria che sia compatibile con i contenitori C ++ STL, qualcosa come std::binary_searchnell'intestazione della libreria standard <algorithm>, ma mi serve per restituire l'iteratore che punta al risultato, non un semplice booleano che mi dice se l'elemento esiste.

(In una nota a margine, cosa diavolo stava pensando il comitato standard quando ha definito l'API per binary_search ?!)

La mia preoccupazione principale qui è che ho bisogno della velocità di una ricerca binaria, quindi anche se posso trovare i dati con altri algoritmi, come indicato di seguito, voglio sfruttare il fatto che i miei dati sono ordinati per ottenere i vantaggi di un binario ricerca, non una ricerca lineare.

finora lower_bounde upper_boundfallire se manca il dato:

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

Nota: sto anche bene usando un algoritmo che non appartiene allo spazio dei nomi std purché sia ​​compatibile con i contenitori. Come, ad esempio, boost::binary_search.


2
Per quanto riguarda la modifica: ecco perché std :: equal_range è la soluzione. Altrimenti, dovrai testare l'uguaglianza (o l'equivalenza per essere più)
Luc Hermitte

Devi testare l'uguaglianza dopo aver usato _bound (inferiore / superiore) (vedi risposta sotto).
Luc Touraille

La documentazione lower_bound e upper_bound afferma che l'intervallo deve essere ordinato e per questo motivo possono essere implementati come ricerca binaria.
vividos

@vividos, evviva! hai trovato solo il pezzo di documentazione che avevo bisogno di sapere! Grazie!
Robert Gould

Robert, gli algoritmi lower / upper_bound / equal_range non funzionano con intervalli non ordinati. Sei solo fortunato a vederli lavorare con il campione di elementi che hai preso.
Luc Hermitte

Risposte:


97

Non esistono funzioni di questo tipo, ma puoi scriverne una semplice usando std::lower_bound, std::upper_boundo std::equal_range.

Potrebbe essere una semplice implementazione

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
}

Un'altra soluzione sarebbe usare a std::set, che garantisce l'ordinamento degli elementi e fornisce un metodo iterator find(T key)che restituisce un iteratore all'elemento dato. Tuttavia, le tue esigenze potrebbero non essere compatibili con l'uso di un set (ad esempio se devi memorizzare lo stesso elemento più volte).


sì, funziona, e ho un'implementazione simile in questo momento, tuttavia è un'implementazione "ingenua", nel senso che non fa uso del contesto della situazione, in questo caso dati ordinati.
Robert Gould

5
Non capisco davvero il tuo commento, poiché lower_bound può essere utilizzato solo su dati ordinati. La complessità è inferiore rispetto all'utilizzo di trova (vedi modifica).
Luc Touraille

4
Per completare la risposta di Luc, controlla il classico articolo di Matt Austern Perché non dovresti usare set e Cosa dovresti usare invece (rapporto C ++ 12: 4, aprile 2000) per capire perché la ricerca binaria con vettori ordinati è solitamente preferibile a std :: set , che è un contenitore associativo basato su albero.
ZunTzu

16
Non usare *i == val! Piuttosto usa !(val < *i). Il motivo è che lower_boundusa <, non ==(cioè Tnon è nemmeno richiesto di essere paragonabile all'uguaglianza). (Vedere Efficace STL di Scott Meyers per una spiegazione della differenza tra uguaglianza ed equivalenza .)
gx_

1
@ CanKavaklıoğlu Nessun elemento situato in end. Gli intervalli nella libreria standard C ++ sono rappresentati con intervalli semiaperti: l'iteratore finale "punti" dopo l'ultimo elemento. In quanto tale, può essere restituito dagli algoritmi per indicare che non è stato trovato alcun valore.
Luc Touraille

9

Dovresti dare un'occhiata std::equal_range. Restituirà una coppia di iteratori alla gamma di tutti i risultati.


Secondo cplusplus.com/reference/algorithm/equal_range il costo di std :: equal_range è circa il doppio di std :: lower_bound. Sembra che racchiuda una chiamata a std :: lower_bound e una chiamata a std :: upper_bound. Se sai che i tuoi dati non hanno duplicati, allora è eccessivo e std :: lower_bound (come dimostrato nella risposta in alto) è la scelta migliore.
Bruce Dawson

@BruceDawson: cplusplus.com fornisce solo un'implementazione di riferimento per specificare il comportamento ; per un'implementazione effettiva puoi controllare la tua libreria standard preferita. Ad esempio, in llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm possiamo vedere che le chiamate a lower_bound e upper_bound vengono effettuate su intervalli disgiunti (dopo qualche ricerca binaria manuale). Detto questo, è probabile che sia più costoso, specialmente su intervalli con più valori corrispondenti.
Matthieu M.

6

Ce n'è una serie:

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

Cercare:

In una nota separata:

Probabilmente stavano pensando che la ricerca di contenitori potesse indicare più di un risultato. Ma nelle strane occasioni in cui hai solo bisogno di testare l'esistenza di una versione ottimizzata sarebbe anche carina.


3
binary_search non restituisce un iteratore come ho detto prima, ecco perché sto cercando un'alternativa.
Robert Gould

1
Si, lo so. Ma si adatta all'insieme degli algoritmi di ricerca binaria. Quindi è bello che gli altri lo sappiano.
Martin York

8
binary_search è semplicemente, come tante altre cose nell'STL, chiamato sbagliato. Io odio che. Testare l'esistenza non è la stessa cosa che cercare qualcosa.
OregonGhost

2
Queste funzioni di ricerca binaria non sono utili nel caso in cui si desideri conoscere l'indice dell'elemento che si sta cercando. Devo scrivere la mia funzione ricorsiva per questo compito. Spero che questo, template <class T> int bindary_search (const T & item), dovrebbe essere aggiunto alla prossima versione di C ++.
Kemin Zhou

3

Se std :: lower_bound è di livello troppo basso per i tuoi gusti, potresti voler controllare boost :: container :: flat_multiset . È un sostituto immediato per std :: multiset implementato come vettore ordinato utilizzando la ricerca binaria.


1
Buon collegamento; e anche un buon collegamento nel collegamento: lafstern.org/matt/col1.pdf , che descrive come le ricerche implementate con un vettore ordinato, piuttosto che impostato (sebbene entrambi siano log (N)), hanno costanti di proporzionalità significativamente migliori e sono ~ due volte più veloce (lo svantaggio è un tempo di INSERIMENTO maggiore).
Dan Nissenbaum

2

L'implementazione più breve, chiedendosi perché non è inclusa nella libreria standard:

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

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


Posso pensare a due ragioni per cui questo non è nella libreria standard: pensano che sia facile da implementare, ma la ragione principale è probabilmente che potrebbe richiedere una versione invertita di operator () () se value non è intercambiabile con * prima.
user877329

1

Controlla questa funzione, qBinaryFind :

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

Esegue una ricerca binaria dell'intervallo [inizio, fine) e restituisce la posizione di un'occorrenza di valore. Se non sono presenti occorrenze di valore, restituisce end.

Gli elementi nell'intervallo [inizio, fine) devono essere ordinati in ordine crescente; vedere qSort ().

Se sono presenti molte occorrenze dello stesso valore, è possibile restituirne una qualsiasi. Usa qLowerBound () o qUpperBound () se hai bisogno di un controllo più preciso.

Esempio:

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)

La funzione è inclusa <QtAlgorithms>nell'intestazione che fa parte della libreria Qt .


1
Purtroppo questo algoritmo non è compatibile con i contenitori STL.
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;
}

Esempio: Considera un array, A = [1,2,3,4,5,6,7,8,9] Supponi di voler cercare l'indice di 3 Inizialmente, inizio = 0 e fine = 9-1 = 8 Ora , da inizio <= fine; mid = 4; (array [mid] che è 5)! = 3 Ora, 3 si trova a sinistra di mid in quanto è minore di 5. Pertanto, cerchiamo solo la parte sinistra dell'array Quindi, ora start = 0 e end = 3; mid = 2. Poiché array [mid] == 3, abbiamo ottenuto il numero che stavamo cercando. Quindi, restituiamo il suo indice che è uguale a metà.


1
È bello avere il codice, ma potresti migliorare la risposta fornendo una breve spiegazione di come funziona per le persone che non conoscono la lingua.
Taegost

Qualcuno ha erroneamente segnalato il tuo post come di bassa qualità . Una risposta di solo codice non è di bassa qualità . Tenta di rispondere alla domanda? In caso contrario, contrassegnare come "non una risposta" o consigliare l'eliminazione (se in coda di revisione). b) È tecnicamente errato? Downvote o commenta.
Wai Ha Lee

0

Una soluzione che restituisce la posizione all'interno dell'intervallo potrebbe essere questa, utilizzando solo operazioni sugli iteratori (dovrebbe funzionare anche se l'iteratore non fa aritmetica):

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;

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.