Leçon
La théorie — Recherche dichotomique : quatre choix, seize boucles
La recherche dichotomique trouve une valeur x dans un tableau trié en conservant un intervalle d’indices allant de lo à hi, dans lequel x doit se trouver s’il est présent. Chaque itération lit l’élément central a[mid], puis ne garde que la moitié située du côté de x. L’intervalle est fermé lorsque hi est son dernier indice (lo <= hi, avec hi initialisé à n − 1). Il est demi-ouvert lorsque hi désigne le premier indice qui n’en fait pas partie (lo < hi, avec hi initialisé à n).
Ce que signifie chaque symbole
lo, hi- les bornes de l’intervalle restant à explorer
mid- ⌊(lo + hi)/2⌋, l’indice lu pendant cette itération
n- le nombre d’éléments, placés aux indices 0 à n − 1
N- la longueur maximale testée par le verdict : chaque n de 0 à N et chaque cible, soit (N + 1)² cas
- Suppose
- Les valeurs du tableau sont distinctes, la boucle renvoie un résultat dès que a[mid] est égal à x et, dans l’exécution comme dans le verdict, les entiers n’ont pas de borne supérieure. Le tableau des débordements abandonne cette dernière hypothèse et calcule sur 32 bits, comme Java.
- Ne tient plus quand
- En présence de valeurs répétées, les deux boucles correctes renvoient toujours un indice contenant x, mais pas nécessairement le premier. Pour trouver la première occurrence, comme l’exige une requête d’intervalle sur un index trié, la boucle ne doit pas s’arrêter en cas d’égalité. Elle utilise un intervalle demi-ouvert, applique
hi = middès que a[mid] ≥ x, puis prend lo comme réponse à la fin.
Problème entièrement résolu
-
Pourquoi seules deux boucles résistent 4 étapes
Cliquez sur Blocage avec lo = mid. Le verdict sait tester des tableaux comptant jusqu’à 256 éléments, pas davantage. Démontrez, pour tout n, que la boucle semi-ouverte avec
lo = mid + 1se termine toujours, que la même boucle aveclo = midse bloque dès que hi = lo + 1 et a[lo] < x, puis déduisez ce que ce raisonnement impose à la boucle fermée.Ouvrez ce problème dans l'outil
-
Dans la boucle semi-ouverte, le corps ne s’exécute que lorsque lo < hi. mid, arrondi à l’entier inférieur, est alors supérieur ou égal à lo et strictement inférieur à hi.
-
Ainsi,
hi = middiminue hi d’au moins une unité, tandis quelo = mid + 1augmente lo d’au moins une unité. Dans les deux cas, hi − lo, initialement égal à n, baisse d’au moins une unité à chaque passage et ne peut devenir négatif : la boucle se termine en au plus n passages. -
Prenons maintenant
lo = mid. Lorsque hi = lo + 1, mid vaut ⌊(2lo + 1)/2⌋ = lo. Si a[lo] < x, la mise à jour laisse donc lo inchangé et l’itération suivante répète exactement la précédente. Le réglage proposé atteint cette situation avec lo = 6, hi = 7, a[6] = 13 et x = 14. -
La boucle fermée s’exécute tant que lo <= hi ; le cas lo = hi est donc permis. On a alors mid = lo = hi : remplacer une borne par mid ne change rien. Chaque mise à jour doit franchir mid, ce qui est légitime puisque a[mid] vient d’être lu. Dans l’intervalle semi-ouvert, hi est déjà exclu. La mise à jour
hi = midécarte donc l’élément qui vient d’être lu et tous ceux qui le suivent, puisque a[mid] > x les exclut. En revanche,hi = mid − 1écarterait aussi a[mid − 1], qui pourrait être égal à x.
Réponse
À chaque passage, l’intervalle doit rétrécir sans écarter d’indice susceptible de contenir x. Au départ, il doit couvrir tout le tableau. Avec
lo <= hi, il faut donc hi = n − 1,hi = mid − 1etlo = mid + 1; aveclo < hi, il faut hi = n,hi = midetlo = mid + 1. Le verdict confirme ces deux versions jusqu’à 256 éléments, mais la démonstration vaut pour tout n, ce qu’aucun test ne saurait établir. En arithmétique sur 32 bits, une hypothèse supplémentaire est nécessaire : lo + hi doit être représentable. Le troisième enseignement montre ce qui arrive lorsqu’elle ne l’est pas. -
Parcours
Pourquoi un algorithme est correct
Références (2)
- The overflow in the JDK's binary search, the nine years it went unnoticed, and Bentley's proved-correct version carrying the same line; also the source of the 1946 and 1962 dates the first insight quotes from Programming Pearls: Joshua Bloch, "Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken." Google Research Blog, 2 June 2006.
- The bug report the page reproduces: java.util.Arrays.binarySearch on a 1,100,000,000-byte array, searching for its last element, throwing ArrayIndexOutOfBoundsException: -1064671149: OpenJDK bug JDK-5045582, "(coll) binarySearch() fails for size larger than 1<<30." Filed 11 May 2004 against Java 5.0, resolved (fixed in 6) 28 April 2006.