Trouver vite dans un tableau trié (recherche dichotomique)

icône de pdf
Signaler
Cette leçon explique le fonctionnement de la recherche dichotomique, son implémentation, ses invariants et sa complexité. Tu y verras pourquoi elle est beaucoup plus rapide qu’une recherche linéaire, et comment Python fournit aussi le module `bisect` pour automatiser cette opération. Mots-clés : recherche dichotomique, recherche binaire, complexité logarithmique, invariant, Python, bisect.

Introduction

Chercher efficacement une information dans une grande quantité de données est un défi fondamental en informatique. Lorsqu’un tableau est trié, il devient possible de trouver une valeur cible bien plus rapidement qu’en le parcourant intégralement. L’algorithme de recherche dichotomique, ou recherche binaire, exploite cet ordre pour diviser le problème à chaque étape. Il illustre parfaitement la capacité à décomposer un problème et à réutiliser une méthode algorithmique adaptée à une structure de données. Ce procédé est central pour de nombreuses applications concrètes comme la recherche dans un répertoire, le positionnement d’un élément dans une liste, ou l’accélération de la recherche dans des bases de données triées.

Comprendre le principe de la recherche dichotomique

Lorsqu’un tableau est trié, on peut chercher une valeur sans le parcourir de bout en bout. L’idée est de réduire l’intervalle de recherche par moitiés successives, jusqu’à isoler la valeur cible ou constater son absence.

Prenons l’exemple d’un tableau trié :

tableau = [3, 6, 9, 12, 15, 18, 21]

Si on cherche la valeur 15 :

  • On commence par comparer avec l’élément du milieu, ici 12.

  • Comme 15 > 12, la valeur cherchée ne peut être que dans la partie droite.

  • On recommence avec la moitié droite : [15, 18, 21].

  • Le milieu est 18. Cette fois, 15 < 18, donc on regarde la partie gauche de cette moitié : [15].

  • La valeur recherchée est trouvée.

Chaque étape divise l’espace de recherche. Cela permet de trouver une valeur en un nombre de comparaisons bien plus faible que dans une recherche classique.

À retenir

La recherche dichotomique fonctionne sur un tableau trié et divise l’intervalle de recherche à chaque étape pour localiser rapidement la valeur cible.

Implémentation et complexité de l’algorithme

Voici une implémentation en Python de la recherche dichotomique :

def recherche_dichotomique(tab, x):
    gauche = 0
    droite = len(tab) - 1
    while gauche <= droite:
        milieu = (gauche + droite) // 2
        if tab[milieu] == x:
            return milieu
        elif tab[milieu] < x:
            gauche = milieu + 1
        else:
            droite = milieu - 1
    return -1

Cette version fonctionne uniquement si le tableau est trié par ordre croissant. Elle renvoie l’indice de la valeur trouvée ou -1 si elle est absente.

La recherche dichotomique divise l’intervalle à chaque itération. Si le tableau contient n éléments, le nombre maximal d’itérations est le plus petit entier k tel que 2ᵏ ≥ n, c’est-à-dire log₂(n). On dit que la complexité de l’algorithme est en O(log n). Cette notation exprime que le nombre d’opérations croît lentement même lorsque la taille du tableau augmente fortement.

À retenir

L’algorithme a une complexité logarithmique O(log n), car il divise la taille du problème par deux à chaque étape. Il est donc bien plus rapide qu’une recherche linéaire sur de grandes données.

Terminaison et correction : variant et invariant

Comme tout algorithme comportant une boucle, il est essentiel de s’assurer que la recherche dichotomique fonctionne toujours correctement et s’arrête toujours.

Terminaison : un variant de boucle

À chaque itération, la taille de l’intervalle [gauche, droite] diminue strictement. Plus précisément, droite - gauche forme un variant de boucle, c’est-à-dire une grandeur entière qui diminue à chaque passage et devient négative lorsque l’intervalle est vide. Cela garantit la fin de la boucle.

Correction : un invariant de boucle

L’invariant de boucle est la propriété suivante : si la valeur recherchée est présente dans le tableau, alors elle se trouve dans l’intervalle [gauche, droite] à chaque étape. La mise à jour des bornes (gauche ou droite) respecte cette propriété. Ainsi, si la valeur existe, on la trouvera nécessairement.

À retenir

La terminaison est assurée par un variant (l’intervalle de recherche diminue). La correction repose sur un invariant : la valeur cible reste toujours dans l’intervalle courant.

Comparaison avec la recherche linéaire

Pour mieux comprendre l’intérêt de la recherche dichotomique, comparons-la avec la méthode naïve : la recherche linéaire, qui examine chaque élément un par un.

def recherche_lineaire(tab, x):
    for i in range(len(tab)):
        if tab[i] == x:
            return i
    return -1

La recherche linéaire fonctionne quel que soit l’ordre du tableau, mais nécessite en moyenne n/2 comparaisons, et n dans le pire des cas. Sa complexité est O(n).

En comparaison, la recherche dichotomique est bien plus rapide : avec n = 1 000 000, elle fait au maximum log₂(1 000 000) ≈ 20 comparaisons, contre jusqu’à 1 000 000 pour la recherche linéaire.

À retenir

La recherche linéaire fonctionne toujours mais est lente. La recherche dichotomique est beaucoup plus rapide, à condition que le tableau soit trié.

Variantes, erreurs fréquentes et outils Python

Recherche dans un tableau décroissant

Si les éléments sont triés dans l’ordre décroissant, il faut inverser les comparaisons dans l’algorithme. Cela reste une recherche dichotomique, mais adaptée à l’ordre du tableau.

Risque de dépassement (avancé)

Dans certains langages de bas niveau, le calcul du milieu avec (gauche + droite) // 2 peut entraîner un dépassement de capacité si les indices sont très grands. Une version plus sûre utilise gauche + (droite - gauche) // 2. En Python, ce risque n’existe pas grâce à la gestion dynamique des entiers.

Cette subtilité est utile à connaître dans des contextes avancés, mais elle n’est pas exigée au niveau de Première.

Module bisect : automatiser la recherche

Le module bisect de Python permet d’utiliser directement une recherche dichotomique. La fonction bisect.bisect_left(tab, x) renvoie l’indice où x pourrait être inséré tout en conservant l’ordre du tableau. Cela permet de savoir où se situe x ou s’il est déjà présent.

import bisect

tab = [3, 6, 9, 12, 15, 18, 21]
indice = bisect.bisect_left(tab, 15)
if indice < len(tab) and tab[indice] == 15:
    print("Trouvé à l’indice", indice)
else:
    print("Non trouvé")

À retenir

Le module bisect propose une recherche dichotomique prête à l’emploi pour connaître l’indice d’insertion d’un élément dans une liste triée.

Conclusion

La recherche dichotomique illustre une stratégie algorithmique efficace, reposant sur la structure ordonnée des données et l’idée de diviser pour mieux chercher. Son coût logarithmique la rend incontournable dans le traitement rapide de données triées. Elle met en valeur des concepts essentiels de l’informatique comme l’invariant, le variant et la complexité algorithmique. Elle prépare aussi à des méthodes plus avancées comme la recherche dans des arbres équilibrés ou l’algorithme des k plus proches voisins, qui s’appuie sur la recherche d’éléments proches dans un espace structuré.