Où puis-je trouver un algorithme de recherche binaire C ++ «utile»?

106

J'ai besoin d'un algorithme de recherche binaire compatible avec les conteneurs C ++ STL, quelque chose comme std::binary_searchdans l'en- <algorithm>tête de la bibliothèque standard , mais j'en ai besoin pour renvoyer l'itérateur qui pointe sur le résultat, pas un simple booléen me disant si l'élément existe.

(En passant, à quoi pensait le comité standard quand ils ont défini l'API pour binary_search?!)

Ma principale préoccupation ici est que j'ai besoin de la vitesse d'une recherche binaire, donc bien que je puisse trouver les données avec d'autres algorithmes, comme mentionné ci-dessous, je veux profiter du fait que mes données sont triées pour bénéficier des avantages d'un binaire recherche, pas une recherche linéaire.

jusqu'ici lower_boundet upper_boundéchouer si la donnée est manquante:

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

Remarque: je suis également très bien en utilisant un algorithme qui n'appartient pas à l'espace de noms std tant qu'il est compatible avec les conteneurs. Comme, par exemple, boost::binary_search.

Robert Gould
la source
2
Concernant l'édition: c'est pourquoi std :: equal_range est la solution. Sinon, vous devrez tester l'égalité (ou l'équivalence pour être plus)
Luc Hermitte
Vous devez tester l'égalité après avoir utilisé (inférieur / supérieur) _bound (voir la réponse ci-dessous).
Luc Touraille
La documentation limite inférieure et limite supérieure indiquent que la plage doit être triée, et pour cette raison, elles peuvent être implémentées en tant que recherche binaire.
vividos
@vividos, hourra! vous avez trouvé la documentation dont j'avais besoin! Merci!
Robert Gould
Robert, les algorithmes lower / upper_bound / equal_range ne fonctionnent pas avec des plages non triées. Vous avez juste de la chance de les voir travailler avec l'échantillon d'éléments que vous avez pris.
Luc Hermitte

Réponses:

97

Il n'y a pas de telles fonctions, mais vous pouvez en écrire une simple en utilisant std::lower_bound, std::upper_boundou std::equal_range.

Une implémentation simple pourrait être

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
}

Une autre solution serait d'utiliser un std::set , qui garantit l'ordre des éléments et fournit une méthode iterator find(T key)qui retourne un itérateur à l'élément donné. Cependant, vos exigences peuvent ne pas être compatibles avec l'utilisation d'un ensemble (par exemple, si vous devez stocker le même élément plusieurs fois).

Luc Touraille
la source
oui cela fonctionne, et j'ai une implémentation similaire en ce moment, mais c'est une implémentation "naïve", dans le sens où elle n'utilise pas le contexte de la situation, dans ce cas des données triées.
Robert Gould
5
Je ne comprends pas vraiment votre commentaire, car lower_bound ne peut être utilisé que sur des données triées. La complexité est inférieure à l'utilisation de find (voir modifier).
Luc Touraille
4
Pour compléter la réponse de Luc, consultez l'article classique de Matt Austern Pourquoi vous ne devriez pas utiliser set, et ce que vous devriez utiliser à la place (Rapport C ++ 12: 4, avril 2000) pour comprendre pourquoi la recherche binaire avec des vecteurs triés est généralement préférable à std :: set , qui est un conteneur associatif basé sur une arborescence.
ZunTzu
16
N'utilisez pas *i == val! Utilisez plutôt !(val < *i). La raison en est que lower_boundutilise <, non ==(c'est-à T- dire qu'il n'est même pas nécessaire d'être comparable à l'égalité). (Voir Effective STL de Scott Meyers pour une explication de la différence entre l' égalité et l' équivalence .)
gx_
1
@ CanKavaklıoğlu Il n'y a aucun élément localisé à end. Les plages de la bibliothèque standard C ++ sont représentées avec des intervalles semi-ouverts: l'itérateur de fin "pointe" après le dernier élément. En tant que tel, il peut être retourné par des algorithmes pour indiquer qu'aucune valeur n'a été trouvée.
Luc Touraille
9

Vous devriez y jeter un œil std::equal_range. Il renverra une paire d'itérateurs dans la plage de tous les résultats.

Luc Hermitte
la source
Selon cplusplus.com/reference/algorithm/equal_range, le coût de std :: equal_range est environ deux fois plus élevé que std :: lower_bound. Il semble qu'il encapsule un appel à std :: lower_bound et un appel à std :: upper_bound. Si vous savez que vos données n'ont pas de doublons, c'est exagéré et std :: lower_bound (comme démontré dans la réponse du haut) est le meilleur choix.
Bruce Dawson
@BruceDawson: cplusplus.com ne donne qu'une implémentation de référence pour spécifier le comportement ; pour une implémentation réelle, vous pouvez vérifier votre bibliothèque standard préférée. Par exemple, dans llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm, nous pouvons voir que les appels à lower_bound et upper_bound sont effectués à des intervalles disjoints (après une recherche binaire manuelle). Cela étant dit, il est susceptible d'être plus cher, en particulier sur les plages avec plusieurs valeurs correspondantes.
Matthieu M.
6

Il y en a un ensemble:

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

Rechercher:

Sur une note distincte:

Ils pensaient probablement que la recherche de conteneurs pouvait aboutir à plus d'un résultat. Mais dans les rares cas où vous avez juste besoin de tester l'existence, une version optimisée serait également bien.

Martin York
la source
3
binary_search ne renvoie pas d'itérateur comme je l'ai mentionné plus tôt, c'est pourquoi je recherche une alternative.
Robert Gould
1
Oui je sais. Mais il s'inscrit dans l'ensemble des algorithmes de recherche binaire. Donc, c'est bien pour les autres de savoir.
Martin York
8
binary_search est juste, comme tant d'autres choses dans la STL, mal nommée. Je déteste ça. Tester l'existence n'est pas la même chose que rechercher quelque chose.
OregonGhost
2
Ces fonctions de recherche binaire ne sont pas utiles dans le cas où vous souhaitez connaître l'index de l'élément que vous recherchez. Je dois écrire ma propre fonction récursive pour cette tâche. J'espère que cela, template <class T> int bindary_search (const T & item), devrait être ajouté à la prochaine version de C ++.
Kemin Zhou
3

Si std :: lower_bound est trop bas à votre goût, vous pouvez vérifier boost :: container :: flat_multiset . Il s'agit d'un remplacement de std :: multiset implémenté en tant que vecteur trié à l'aide de la recherche binaire.

ZunTzu
la source
1
Bon lien; et aussi un bon lien dans le lien: lafstern.org/matt/col1.pdf , qui décrit comment les recherches implémentées avec un vecteur trié, plutôt que défini (bien que les deux soient log (N)), ont des constantes de proportionnalité nettement meilleures et sont ~ deux fois plus rapide (l'inconvénient étant un temps d'INSERTION plus long).
Dan Nissenbaum le
2

L'implémentation la plus courte, se demandant pourquoi elle n'est pas incluse dans la bibliothèque 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;
}

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

trozen
la source
Je peux penser à deux raisons pour lesquelles ce n'est pas dans la bibliothèque standard: Ils pensent que c'est facile à implémenter, mais la raison principale est probablement que cela peut nécessiter une version inversée de operator () () si la valeur n'est pas interchangeable avec * d'abord.
user877329
1

Vérifiez cette fonction, qBinaryFind :

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

Effectue une recherche binaire de la plage [début, fin) et renvoie la position d'une occurrence de valeur. S'il n'y a pas d'occurrences de valeur, renvoie end.

Les éléments de la plage [début, fin) doivent être triés par ordre croissant; voir qSort ().

S'il existe de nombreuses occurrences de la même valeur, n'importe laquelle d'entre elles peut être renvoyée. Utilisez qLowerBound () ou qUpperBound () si vous avez besoin d'un contrôle plus fin.

Exemple:

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 fonction est incluse dans l'en- <QtAlgorithms>tête qui fait partie de la bibliothèque Qt .

Lawand
la source
1
Malheureusement, cet algorithme n'est pas compatible avec les conteneurs STL.
bartolo-otrit
0

std :: borne_ inférieure () :)

moogs
la source
OP: "jusqu'ici échouent à la limite inférieure et supérieure, car ..."
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;
}

Exemple: Prenons un tableau, A = [1,2,3,4,5,6,7,8,9] Supposons que vous souhaitiez rechercher l'index de 3 Initialement, start = 0 et end = 9-1 = 8 Now , puisque début <= fin; milieu = 4; (tableau [milieu] qui vaut 5)! = 3 Maintenant, 3 se trouve à gauche du milieu car il est plus petit que 5. Par conséquent, nous ne cherchons que la partie gauche du tableau. Par conséquent, maintenant start = 0 et end = 3; mid = 2. Depuis array [mid] == 3, nous avons donc obtenu le numéro que nous recherchions. Par conséquent, nous retournons son indice qui est égal à mid.

Siddharth Kumar Shukla
la source
1
C'est bien d'avoir du code, mais vous pouvez améliorer la réponse en fournissant une brève explication de son fonctionnement pour les personnes qui ne connaissent pas la langue.
Taegost
Quelqu'un a incorrectement signalé votre message comme étant de mauvaise qualité . Une réponse codée uniquement n'est pas de mauvaise qualité . Tente-t-il de répondre à la question? Sinon, indiquez «pas une réponse» ou recommandez la suppression (si dans la file d'attente de révision). b) Est-ce techniquement incorrect? Voter ou commenter.
Wai Ha Lee
0

Une solution renvoyant la position à l'intérieur de la plage pourrait être comme ceci, en utilisant uniquement des opérations sur les itérateurs (cela devrait fonctionner même si l'itérateur ne fait pas d'arithmétique):

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
la source