web-dev-qa-db-fra.com

justification de std :: lower_bound et std :: upper_bound?

STL fournit des fonctions de recherche binaire std :: lower_bound et std :: upper_bound, Mais j'ai tendance à ne pas les utiliser car je ne me souviens plus de ce qu'elles font, car leurs contrats me semblent totalement mystifiantes.

En regardant les noms, Je suppose que "lower_bound" pourrait être l'abréviation de "last lower bound",
c'est à dire. le dernier élément de la liste triée qui est <= la valeur donnée (le cas échéant).
De même, je suppose que "upper_bound" pourrait être un raccourci pour "first upper bound",
c'est à dire. le premier élément de la liste triée qui est> = le val donné (le cas échéant).

Mais la documentation indique qu'ils font quelque chose d'assez différent de cela -- Quelque chose qui semble être un mélange d'arrière-plans et aléatoire, pour moi .. Pour paraphraser le doc:
- lower_bound trouve le premier élément qui est> = val
- upper_bound trouve le premier élément qui est> val

Donc lower_bound ne trouve pas du tout une limite inférieure; il trouve le premier upper bound!? Et upper_bound trouve le premier strict upper bound.

Est-ce que cela a un sens ?? Comment vous en souvenez-vous?

51
Don Hatch

Si vous avez plusieurs éléments dans la plage [first, last) dont la valeur est égale à la valeur val que vous recherchez, la plage [l, u) où

l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)

est précisément la plage d'éléments égale à val dans la plage [first, last). Donc l et u sont les "bornes inférieures" et "bornes supérieures" pour la plage equal. Cela a du sens si vous êtes habitué à penser en termes d'intervalles semi-ouverts.

(Notez que std::equal_range renverra les bornes inférieure et supérieure d'une paire, en un seul appel.)

68
Brian
std::lower_bound

Renvoie un itérateur pointant vers le premier élément de la plage [premier, dernier] qui n'est pas inférieur à (c'est-à-dire supérieur ou égal à).

std::upper_bound

Renvoie un itérateur pointant sur le premier élément de la plage [premier, dernier) qui est supérieur à valeur.

Ainsi, en mélangeant les limites inférieure et supérieure, vous êtes en mesure de décrire exactement le début et la fin de votre plage.

Est-ce que cela a un sens ??

Oui.

Exemple:

imaginer un vecteur

std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };

auto lower = std::lower_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                  // ^ lower

auto upper = std::upper_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                           // ^ upper

std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));

tirages: 4 4 4


http://fr.cppreference.com/w/cpp/algorithm/lower_bound

http://fr.cppreference.com/w/cpp/algorithm/upper_bound

27
4pie0

Dans ce cas, je pense qu'une image vaut mille mots. Supposons que nous les utilisions pour rechercher 2 dans les collections suivantes. Les flèches indiquent les itérateurs que les deux retourneraient:

enter image description here

Donc, si vous avez plusieurs objets avec cette valeur déjà présents dans la collection, lower_bound vous donnera un itérateur qui fait référence au premier d'entre eux, et upper_bound donnera un itérateur qui fait référence à l'objet immédiatement après le dernier de leur.

Ceci (entre autres choses) rend les itérateurs retournés utilisables en tant que paramètre hint à insert.

Par conséquent, si vous les utilisez comme indice, l'élément que vous insérez deviendra le premier premier élément avec cette valeur (si vous avez utilisé lower_bound) ou le dernier élément avec cette valeur (si vous avez utilisé upper_bound). Si la collection ne contenait pas d'élément avec cette valeur auparavant, vous aurez toujours un itérateur qui peut être utilisé en tant que hint pour l'insérer à la bonne position dans la collection.

Bien sûr, vous pouvez également insérer sans indice, mais en utilisant un indice, vous avez la garantie que l'insertion se termine avec une complexité constante, à condition que le nouvel élément à insérer puisse être inséré immédiatement avant l'élément désigné par l'itérateur ces deux cas).

8
Jerry Coffin

Considérez la séquence

1 2 3 4 5 6 6 6 7 8 9

la limite inférieure pour 6 correspond à la position du premier 6.

la limite supérieure pour 6 correspond à la position du 7.

ces positions servent de paire commune (début, fin) désignant la suite de 6 valeurs.


Exemple:

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

auto main()
    -> int
{
    vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
    auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
    auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
    for( auto it = pos1; it != pos2; ++it )
    {
        cout << *it;
    }
    cout << endl;
}

J'acceptais la réponse de Brian, mais je venais de réaliser une autre manière utile de penser qui ajoute de la clarté pour moi, alors j'ajoute ceci pour référence.

Pensez à l'itérateur renvoyé comme pointant, non pas à l'élément * iter, mais simplement avant cet élément, c'est-à-dire entre cet élément et l'élément précédent de la liste, s'il en existe un. En pensant de cette façon, les contrats des deux fonctions deviennent symétriques: lower_bound trouve la position de la transition de <val à> = val, et upper_bound trouve la position de la transition de <= val à> val. En d'autres termes, lower_bound est le début de la plage d'éléments dont la comparaison est égale à val (c'est-à-dire la plage renvoyée par std :: equal_range), et upper_bound en est la fin.

Je souhaite qu'ils en parlent comme ceci (ou l'une des autres bonnes réponses données) dans la documentation; cela le rendrait beaucoup moins mystifiant!

4
Don Hatch

le code source a en fait une deuxième explication que j'ai trouvée très utile pour comprendre le sens de la fonction:

lower_bound: Trouve la première position dans laquelle [val] pourrait être insérée sans changer l'ordre.

upper_bound: Trouve la dernier position dans laquelle [ val] pourrait être inséré sans modifier le classement.

ceci [premier, dernier] forme une plage dans laquelle le val peut être inséré tout en conservant la commande initiale du conteneur

lower_bound return "first" c'est-à-dire trouver la "limite inférieure de la plage"

upper_bound return "last" c'est-à-dire trouve la "limite supérieure de la plage"

1
xwang

Les deux fonctions sont très similaires, en ce sens qu'elles trouveront un point d'insertion dans une séquence triée qui préservera le tri. S'il n'y a aucun élément existant dans la séquence égal à l'élément de recherche, ils renverront le même itérateur.

Si vous essayez de trouver quelque chose dans la séquence, utilisez lower_bound - il pointera directement sur l'élément s'il est trouvé.

Si vous insérez dans la séquence, utilisez upper_bound - il conserve le classement d'origine des doublons.

1
Mark Ransom

Pour un tableau ou un vecteur:

std :: lower_bound: Retourne un itérateur pointant sur le premier élément de la plage 

  • inférieur ou égal à la valeur. (pour tableau ou vecteur en ordre décroissant)
  • supérieur ou égal à la valeur. (pour tableau ou vecteur en ordre croissant)

std :: upper_bound: Retourne un itérateur pointant sur le premier élément de la plage

  • inférieur à valeur. (pour tableau ou vecteur par ordre décroissant)

  • supérieur à valeur. (pour tableau ou vecteur en ordre croissant)

0
Vipul Jain

Oui. La question a absolument un point. Quand quelqu'un donnait son nom à ces fonctions, il ne pensait qu'à des tableaux triés contenant des éléments répétés. Si vous avez un tableau avec des éléments uniques, "std :: lower_bound ()" agit plutôt comme une recherche d'une "limite supérieure" à moins qu'il ne trouve l'élément réel.

Donc, voici ce dont je me souviens à propos de ces fonctions:

  • Si vous faites une recherche binaire, envisagez d'utiliser std :: lower_bound () et lisez le manuel. std :: binary_search () est également basé sur celui-ci.
  • Si vous voulez trouver le "lieu" d'une valeur dans un tableau trié de valeurs uniques, considérez std :: lower_bound () et lisez le manuel.
  • Si vous avez une tâche arbitraire de recherche dans un tableau trié, lisez le manuel de std :: lower_bound () et de std :: upper_bound ().

Si vous ne lisez pas le manuel après un mois ou deux depuis la dernière utilisation de ces fonctions, cela entraîne presque certainement un bogue.

0
Angel Sinigersky

Il existe déjà de bonnes réponses quant à ce que std::lower_bound et std::upper_boundis.

Je voudrais répondre à votre question 'comment se souvenir d'eux'?

Il est facile à comprendre/à retenir si nous établissons une analogie avec les méthodes STL begin() et end() de tout conteneur. begin() renvoie l'itérateur de départ dans le conteneur, tandis que end() renvoie l'itérateur situé juste en dehors du conteneur. Nous savons tous à quel point ils sont utiles lors d'une itération.

Désormais, sur un conteneur trié et une valeur donnée, lower_boundet upper_bound renverront une plage d'itérateurs pour cette valeur sur laquelle il est facile d'effectuer une itération (tout comme début et fin)

Utilisation pratique ::

Outre l'utilisation mentionnée ci-dessus sur la liste triée pour accéder à la plage d'une valeur donnée, l'une des meilleures applications de upper_bound est d'accéder aux données ayant une relation plusieurs-à-un dans une carte.

Par exemple, considérons la relation suivante: 1 -> a, 2 -> a, 3 -> a, 4 -> b, 5 -> c, 6 -> c, 7 -> c, 8 -> c , 9 -> c, 10 -> c

Les 10 mappages ci-dessus peuvent être sauvegardés dans la carte comme suit:

numeric_limits<T>::lowest() : UND
1 : a
4 : b
5 : c
11 : UND

Les valeurs peuvent être consultées par l'expression (--map.upper_bound(val))->second.

Pour les valeurs de T allant du plus bas au 0, l'expression retournera UND. Pour les valeurs de T allant de 1 à 3, elle retournera 'a' et ainsi de suite.

Imaginons maintenant que nous ayons des centaines de mappages de données avec une valeur et des centaines de mappages de ce type . Cette approche réduit la taille de la carte et la rend ainsi efficace.

0
shainu vy

Imaginez ce que vous feriez si vous vouliez trouver le premier élément égal à val dans [first, last). Vous commencez par exclure des premiers éléments strictement inférieurs à val, puis en arrière du dernier - 1 éléments strictement supérieurs à val. Ensuite, la plage restante est [lower_bound, upper_bound]

0
Xuan Liu