6- Recherche et dichotomie
I. Maximum, minimum
C'est très courant de chercher le maximum ou le minimum d'une liste.
Loading editor...
def minimum(L):
"""renvoie le minimum de la liste L et son indice"""
val_min = L[0]
i_min = 0
#on commence à 1 car on a déjà vu l'élément 0
for i in range(1, len(L)):
if L[i] < val_min:
val_min = L[i]
i_min = i
return (val_min, i_min)Loading editor...
minimum([5, 3, 8, 1, 9, 2])
Modifiez le code précédent pour trouver le deuxième minimum.
II. Recherche d'un élément dans une liste
a) Recherche "naïve"
Loading editor...
def recherche(L, x):
"""renvoie True si x est dans L, False sinon."""
for y in L:
if x == y:
return True
return FalseLoading editor...
print(recherche([3, 1, 4, 1, 5], 4)) print(recherche([3, 1, 4, 1, 5], 7))
b) Recherche dans une liste triée : la dichotomie
Le mot dichotomie veut dire "couper en deux". C'est une méthode très courante pour faire des algorithmes plus efficaces : on coupe le problème en deux, et soit on traite les parties indépendamment (tri fusion, tri rapide...), soit on arrive à ne traiter qu'une seule des deux parties (recherches, exponentiation rapide...).
On a vu comment on peut trier une liste. Comment utiliser ce tri pour chercher un élément plus vite ?
Idée : on regarde au milieu de la liste.
- Si l'élément du milieu est plus petit que
x, alorsxest dans la deuxième moitié. - Sinon, dans la première moitié.
Par exemple, voici les étapes de la recherche de 2 dans la liste [-1, 0, 1, 2, 3, 4, 5, 10, 123, 124]. À chaque étape, la partie de la liste où l'on cherche encore est en gras :
| Étape | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 10 | 123 | 124 |
| 1 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 10 | 123 | 124 |
| 2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 10 | 123 | 124 |
| 3 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 10 | 123 | 124 |
Loading editor...
def recherche_dicho(L, x):
"""Si L est une liste triée dans l'ordre croissant, renvoie l'indice de x dans L, ou bien None si x n'est pas dans L."""
i = 0 #borne inf incluse
j = len(L) #borne sup exclue
while (j - i) > 0:
milieu = (i + j) // 2
if L[milieu] < x:
i = milieu + 1
elif L[milieu] > x:
j = milieu
else: #cas où L[milieu] == x
return milieu
return NoneLoading editor...
L = [-1, 0, 1, 2, 3, 4, 5, 10, 123, 124] print(recherche_dicho(L, 2)) print(recherche_dicho(L, 6))
Pour visualiser la recherche, voici la même fonction, qui note en plus chaque étape (la partie de la liste où l'on cherche encore, en vert, et le milieu, en rouge), pour l'animer avec animer_liste :
On peut aussi suivre les valeurs des variables à chaque tour de boucle avec tracer_variables :
Combien de tours de boucle au maximum pour une liste de taille 8 ? De taille 16 ? Et pour
À chaque tour de boucle, la taille de la partie où l'on cherche est divisée par deux. Il faut donc au plus 4 tours pour une liste de taille 8, 5 pour une liste de taille 16, et
En fait, la complexité est logarithmique, soit en
III. Recherche du zéro d'une fonction
Supposons qu'on ait une fonction
On peut appliquer le même principe ! On regarde le signe de
Loading editor...
def recherche_zero(f, a, b, eps):
"""Si f(a) < 0 et f(b) > 0, renvoie un zéro de f sur [a, b] à eps près."""
while b - a > eps:
milieu = (a + b) / 2
if f(milieu) < 0:
a = milieu
else:
b = milieu
return (a + b) / 2Par exemple, pour trouver une valeur approchée de
Loading editor...
def f(x):
return x ** 2 - 2
recherche_zero(f, 0, 2, 1e-6)Pour visualiser les étapes, voici la même fonction, qui renvoie en plus la liste des intervalles animer_dichotomie :
On veut trouver le nombre de tours de boucle avant que le programme s'arrête. À chaque tour, la taille de l'intervalle est divisée par deux : après
On a donc, en ordre de grandeur de complexité,
Les concepteurs de sujet aiment bien tout ce qui touche aux applications physiques, donc c'est vraiment le genre d'algorithme qui tombe !
Exercices
Exercice 1
Écrire une fonction indice_min(L) qui renvoie l'indice du plus petit élément d'une liste non vide.
Loading editor...
def indice_min(L):
i_min = 0
for i in range(1, len(L)):
if L[i] < L[i_min]:
i_min = i
return i_minExercice 2
Modifier recherche_dicho pour qu'elle renvoie l'indice de la première occurrence de x dans une liste triée qui peut contenir des doublons (par exemple 2 pour [1, 3, 5, 5, 5, 8] et x = 5).
Loading editor...
Quand on trouve x, on continue à chercher à gauche, en gardant l'indice trouvé :
def premiere_occurrence(L, x):
i, j = 0, len(L)
trouve = None
while j - i > 0:
milieu = (i + j) // 2
if L[milieu] < x:
i = milieu + 1
else:
if L[milieu] == x:
trouve = milieu
j = milieu
return trouveExercice 3
Utiliser recherche_zero pour calculer une valeur approchée de
Loading editor...
recherche_zero(lambda x: x ** 3 - 10, 0, 10, 1e-6) # environ 2.154435