8.1. Algorithmes de recherche

8.1.1. Recherche d’un élément dans une liste

Il faut noter que Python dispose déjà de l’opérateur in pour tester si un élément figure dans une liste.

In [1]: 2 in [5, 4, 1, 2, 3]
Out[1]: True

In [2]: 6 in [5, 4, 1, 2, 3]
Out[2]: False

La méthode index permet de renvoyer l’indice de l’élément dans la liste s’il a été trouvé.

In [3]: [5, 4, 1, 2, 3].index(2)
Out[3]: 3

In [4]: [5, 4, 1, 2, 3].index(6)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 [5, 4, 1, 2, 3].index(6)

ValueError: 6 is not in list

On peut néanmoins proposer notre propre algorithme : il suffit de balayer la liste et de renvoyer True dès qu’on trouve l’élément recherché et False si on a parcouru toute la liste sans trouver l’élément.

In [5]: def appartient(elt, lst):
   ...:     for e in lst:
   ...:         if e == elt:
   ...:             return True
   ...:     return False
   ...: 

In [6]: appartient(2, [5, 4, 1, 2, 3])
Out[6]: True

In [7]: appartient(6, [5, 4, 1, 2, 3])
Out[7]: False

On peut également proposer une version qui renvoie l’indice la première occurence de l’élément recherché s’il est trouvé et None sinon.

In [8]: def indice(elt, lst):
   ...:     for i in range(len(lst)):
   ...:         if lst[i] == elt:
   ...:             return i
   ...:     return None
   ...: 

In [9]: indice(2, [5, 4, 1, 2, 3])
Out[9]: 3

In [10]: indice(6, [5, 4, 1, 2, 3])  # L'interpréteur IPython n'affiche pas None

8.1.2. Recherche par dichotomie dans une liste triée

Lorsque l’on dispose d’une liste triée par ordre croissant, on peut grandement améliorer notre algorithme en utilisant le principe de dichotomie.

  • On recherche tout d’abord l’élément central de la liste.

  • Si c’est l’élément recherché, on s’arrête. Sinon, on le compare à l’élément recherché.

  • Si l’élément recherché est inférieur à l’élément central, on le recherche dans la première partie de la liste. Sinon, on le recherche dans la deuxième partie de la liste.

  • On retourne donc à la première étape de notre algorithme appliqué à l’une des deux demi-listes.

In [11]: def appartient_dicho(elt, lst):
   ....:     g = 0
   ....:     d = len(lst) - 1
   ....:     while g <= d:
   ....:         m = (g + d) // 2
   ....:         if lst[m] == elt:
   ....:             return True
   ....:         if elt < lst[m]:
   ....:             d = m - 1
   ....:         else:
   ....:             g = m + 1
   ....:     return False
   ....: 

In [12]: appartient_dicho(13, [1, 3, 5, 7, 8, 10, 13, 14, 17, 19])
Out[12]: True

In [13]: appartient_dicho(18, [1, 3, 5, 7, 8, 10, 13, 14, 17, 19])
Out[13]: False

Comme souvent, un dessin vaut mieux qu’un long discours. On donne deux exemples d’application de cet algorithme.

Recherche de 5 dans la liste [1, 3, 5, 7, 8, 10, 13, 14, 17, 19, 20]

Figure made with TikZ

Recherche de 13 dans la liste [1, 3, 5, 7, 8, 10, 13, 14, 17, 19]

Figure made with TikZ

A nouveau, on peut proposer une version qui renvoie l’indice de la première occurence de l’élément recherché plutôt qu’un booléen.

In [14]: def indice_dicho(elt, lst):
   ....:     g = 0
   ....:     d = len(lst) - 1
   ....:     while g <= d:
   ....:         m = (g + d) // 2
   ....:         if lst[m] == elt:
   ....:             return m
   ....:         if elt < lst[m]:
   ....:             d = m - 1
   ....:         else:
   ....:             g = m + 1
   ....:     return None
   ....: 

In [15]: indice_dicho(13, [1, 3, 7, 8, 10, 13, 14, 17, 19])
Out[15]: 5

In [16]: indice_dicho(18, [1, 3, 7, 8, 10, 13, 14, 17, 19])  # L'interpréteur IPython n'affiche pas None

On peut également proposer une version récursive.

In [17]: def appartient_dicho_recursif(elt, lst):
   ....:     if len(lst) == 1:
   ....:         return elt == lst[0]
   ....:     m = len(lst)//2
   ....:     if elt == lst[m]:
   ....:         return True
   ....:     if elt < lst[m]:
   ....:         return appartient_dicho_recursif(elt, lst[:m-1])
   ....:     else:
   ....:         return appartient_dicho_recursif(elt, lst[m+1:])
   ....:     return False
   ....: 

In [18]: appartient_dicho_recursif(13, [1, 3, 5, 7, 8, 10, 13, 14, 17, 19])
Out[18]: True

In [19]: appartient_dicho_recursif(18, [1, 3, 5, 7, 8, 10, 13, 14, 17, 19])
Out[19]: False

8.1.3. Recherche du maximum ou du minimim d’une liste

On suppose qu’on dispose d’une liste d’éléments que l’on peut comparer les uns aux autres et on cherche à déterminer le plus grand ou le plus petit élément. Python dispose déjà de deux fonctions min et max pour effectuer cette tâche.

In [20]: lst = [5, -7, 4, -3, 2, 10]

In [21]: min(lst), max(lst)
Out[21]: (-7, 10)

On peut également proposer notre algorithme. Rien de bien difficile, il suffit de parcourir un à un les éléments de la liste et de comparer chaque élément au minimum ou maximum des éléments précédents.

In [22]: def minmax(lst):
   ....:     m, M = None, None
   ....:     for elt in lst:
   ....:         if m is None or m > elt:
   ....:             m = elt
   ....:         if M is None or M < elt:
   ....:             M = elt
   ....:     return m, M
   ....: 

In [23]: minmax([5, -7, 4, -3, 2, 10])
Out[23]: (-7, 10)

8.1.4. Recherche d’une sous-chaîne dans une chaîne de caractères

L’objectif est de retrouver une sous-chaîne (qu’on appellera motif) dans une chaîne de caractères. Par exemple, la chaîne "pitapipapa" contient le motif "pipa" mais pas le motif "tapi". Python propose déjà cette fonctionnalité à l’aide de l’opérateur in.

In [24]: "pipa" in "pitapipapa"
Out[24]: True

In [25]: "tapa" in "pitapipapa"
Out[25]: False

La méthode index permet de renvoyer l’indice du caractère où a été trouvé le motif le cas échéant.

In [26]: "pitapipapa".index("pipa")
Out[26]: 4

In [27]: "pitapipapa".index("tapa")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[27], line 1
----> 1 "pitapipapa".index("tapa")

ValueError: substring not found

On présente ici un algorithme naïf qui est assez peu efficace mais qui a le mérite d’être très facile à comprendre : on prend successivement chaque caractère de la chaîne comme point de départ et on compare les caractères de la chaîne et les caractères du motif à partir de ce point de départ.

In [28]: def recherche_chaine(chaine, motif):
   ....:     n = len(chaine)
   ....:     m = len(motif)
   ....:     for ind in range(n-m+1):
   ....:         nb = 0
   ....:         while nb < m and chaine[ind+nb] == motif[nb]:
   ....:             nb +=1
   ....:         if nb == m:
   ....:             return True
   ....:     return False
   ....: 

In [29]: recherche_chaine("pitapipapa", "pipa")
Out[29]: True

In [30]: recherche_chaine("patapipapa", "tapa")
Out[30]: False

Succès

Echec

On peut à nouveau proposer une version de l’algorithme qui renvoie l’indice de la première occurence rencontrée.

In [31]: def indice_chaine(chaine, motif):
   ....:     n = len(chaine)
   ....:     m = len(motif)
   ....:     for ind in range(n-m):
   ....:         nb = 0
   ....:         while nb < m and chaine[ind+nb] == motif[nb]:
   ....:             nb +=1
   ....:         if nb == m:
   ....:             return nb
   ....:     return None
   ....: 

In [32]: indice_chaine("pitapipapa", "pipa")
Out[32]: 4

In [33]: indice_chaine("patapipapa", "tapa")     # L'interpréteur IPython n'affiche pas None

Notes