web-dev-qa-db-fra.com

Calcul du milieu de la recherche binaire

Je lisais un livre d'algorithmes qui avait l'algorithme suivant pour la recherche binaire:

public class BinSearch {
  static int search ( int [ ] A, int K ) {
    int l = 0 ;
    int u = A. length −1;
    int m;
    while (l <= u ) {
      m = (l+u) /2;
      if (A[m] < K) {
        l = m + 1 ;
      } else if (A[m] == K) {
        return m;
        } else {
          u = m−1;
        }
       }
       return −1;
      }
 }

L'auteur dit "L'erreur est dans l'affectation m = (l+u)/2; Il peut conduire à déborder et doit être remplacé par m = l + (u-l)/2."

Je ne vois pas comment cela causerait un débordement. Lorsque j'exécute l'algorithme dans mon esprit pour quelques entrées différentes, je ne vois pas la valeur de la MID à sortir de l'index de la matrice.

Donc, dans quel cas le débordement aurait-il lieu?

36
Bharat

Ceci POST couvre ce célèbre bug dans beaucoup de détails. Comme d'autres l'ont dit, c'est une question de trop-plein. La solution recommandée sur le lien est la suivante:

int mid = low + ((high - low) / 2);

// Alternatively
int mid = (low + high) >>> 1;

Il convient également de mentionner probablement que, dans le cas où des indices négatifs sont autorisés, ou peut-être que ce n'est même pas un tableau qui est recherché (par exemple, la recherche d'une valeur dans certaines gangriers entier satisfaisant certaines conditions), le code ci-dessus peut ne pas être correct aussi bien. . Dans ce cas, quelque chose de laids que

(low < 0 && high > 0) ? (low + high) / 2 : low + (high - low) / 2

peut être nécessaire. Un bon exemple est recherche de la médiane dans un tableau non formé sans le modifier ni en utilisant un espace supplémentaire En effectuant simplement une recherche binaire sur l'ensemble Integer.MIN_VALUE-Integer.MAX_VALUE gamme.

53
Jeff Foster

Le problème est que (l+u) est évalué en premier et pourrait déborder int, alors (l+u)/2 retournerait la mauvaise valeur.

6
murgatroid99

Jeff a suggéré vraiment bon post Pour lire ce bogue, voici un résumé si vous voulez un aperçu rapide.

Lors de la programmation des perles, Bentley dit que la ligne analogue "définit m à la moyenne de L et U, tronquée jusqu'au entier le plus proche." Sur la face, cette affirmation peut sembler correcte, mais il échoue à des valeurs importantes des variables INT bas et élevées. Plus précisément, il échoue si la somme de faible et élevée est supérieure à la valeur INT positive maximale (2 ^ 31 - 1). La somme déborde d'une valeur négative et la valeur reste négative lorsqu'elle est divisée par deux. en C Ceci provoque un indice de matrice hors limites avec des résultats imprévisibles. En Java, il jette ArrayIndexoutofboundsException.

3
Vipin

La réponse simple est, l'ajout l + u Peut déborder et avoir un comportement non défini dans certaines langues, comme décrit dans n article de blog de Joshua Bloch, sur un bogue dans le Java Bibliothèque pour la mise en œuvre de la recherche binaire .

Certains lecteurs peuvent ne pas comprendre ce qu'il s'agit de:

l + (u - l) / 2

Notez que dans certains codes, les noms de variables sont différents et il est

low + (high - low) / 2

La réponse est la suivante: disons si vous avez deux chiffres: 200 et 210, et maintenant, vous voulez maintenant le "numéro du milieu". Et disons que si vous ajoutez deux chiffres et que le résultat est supérieur à 255, il peut déborder et le comportement est indéfini, alors que pouvez-vous faire? Un moyen simple consiste simplement à ajouter la différence entre eux, mais à seulement la moitié de celui-ci, à la plus petite valeur: Regardez quelle est la différence entre 200 et 210. Il est 10. (vous pouvez considérer la longueur de la "différence" ou " ", entre eux). Donc, il vous suffit d'ajouter 10 / 2 = 5 À 200 et obtenez 205. Vous n'avez pas besoin d'ajouter 200 and 210 ensemble en premier - et c'est comment nous pouvons atteindre le calcul: (u - l) Est le différence. (u - l) / 2 Est la moitié de celui-ci. Ajouter cela à l et nous avons l + (u - l) / 2.

Pour mettre cela dans les perspectives d'historique, Robert Sedgewick a mentionné que la première recherche binaire a été mentionnée en 1946 et ce n'était pas correct qu'en 1964. Jon Bentley décrit dans ses perles de programmation de livres en 1988 que 90% des programmeurs professionnels ne pouvaient pas Ecrivez-le correctement donné quelques heures. Mais même Jon Bentley lui-même avait ce bug de débordement pendant 20 ans. Une étude publiée en 1988 a montré que le code précis pour la recherche binaire n'a été trouvé que dans 5 manuels sur 20 sur 20. En 2006, Joshua Bloch a écrit ce blog post sur le bogue de calcul de la valeur mid. Il a donc fallu 60 ans pour que ce code soit correct. Mais maintenant, la prochaine fois dans l'entretien d'embauche, n'oubliez pas de l'écrire correctement dans ces 20 minutes.

2
nonopolarity

Le débordement potentiel est dans le l+u addition elle-même.

C'était en fait n bogue dans les premières versions de la recherche binaire dans le JDK.

2
Nemo