TD M1 : Dichotomie, complexité¶
L'objectif de ce TP est de revoir quelques techniques de recherche dans une liste et d'évaluer la complexité des algorithmes utilisés.
Q1. Pour cela on va commencer par créer une liste L de 100 nombres entiers dont les valeurs seront choisies aléatoirement entre 0 et 1000. Écrire une fonction nommé liste_alea(n : int) -> list qui reçoit un entier $n$ et qui renvoie une liste de taille $n$ dont tous les éléments sont des entiers obtenus aléatoirement entre 0 et $n$ (Utiliser des listes par compréhension).
import random
def liste_alea(n : int) -> list:
"""
Renvoie une liste de n entiers aléatoires entre 0 et n
"""
return # à compléter
# Affichons le début de la liste L :
print(liste_alea(10))
Rappelons tout d'abord qu'en python il existe l'opérateur in qui fait le travail !
L = liste_alea(10)
e1 = L[0] # un élément de L !
e2 = -1 # un élément qui n'est pas dans L
print(e1 in L)
print(e2 in L)
Mais l'objectif est ici de créer nos propres algorithmes ;-) !
I. Méthode naïve : recherche séquentielle¶
Un premier algorithme est de tester progressivement un à un tous les éléments de la liste et de les comparer à l'élément cherché.
Exercice 1
On va implémenter une fonction recherche_sequentielle(L : list, elt : int) -> bool qui reçoit une liste d'entier ainsi qu'un entier et renvoie True si cet entier est dans la liste et ̀False sinon.
Q2 Pour ne pas oublier les bonnes habitudes, commençons par écrire une fonction test_recherche_sequentielle() qui fait passer 2 ou 3 tests à la fonction recherfche_sequentielle avec des listes simples (rappel : on utilise le mot clé asssert )
def test_recherche_sequentielle():
# à compléter...
#
print("Tous les tests sont passés")
test_recherche_sequentielle()
Q3. Compléter alors le code ci-dessous pour créer la fonction recherche_sequentielle(L : list,e : int) -> bool.
def recherche_sequentielle(L : list,e : int) -> bool :
"""reçoit une liste d'entier L et un entier e, et renvoie :
-> True si e appartient à L
-> False si e n'appartient pas à L"""
# à compléter
# on teste :
test_recherche_sequentielle()
Q4. Quelle est la complexité de cet algorithme dans le meilleur et dans le pire des cas ?
Réponses :
- Dans le meileur des cas la complexité est : O(1), c'est à dire en temps constant
- Dans le pire des cas la complexité est : O(n), c'est à dire proportinnel à la taille de la liste
II. Algorithme dichotomique¶
Lorsque les données du tableau sont triées, on peut envisager une méthode bien plus performante : la recherche par dichotomie
!!! info "le principe" On regarde la valeur au milieu de la liste,
- Si c'est l'élément cherché, on s'arrête en renvoyant
True, - Sinon :
- Si l'élément est avant, on oublie la deuxième moitié du tableau,
- Sinon, s'il est après on oublie la première partie du tableau,
- puis on recommence avec la partie restante si elle est non vide, sinon on renvoie
False
!!!
Exercice 2 :
Q5. Compléter la fonction recherche_dichotomie(L : list, e : int)-> bool qui implémente l'algorithme précédent.
def recherche_dichotomie(L : list, e : int) -> bool :
a,b = 0,len(L)-1 # les indices gauche et droite
while a<=b :
m = (a+b)//2 # division entiere
if e == L[m]:
return # à compélter
else :
if e < L[m] :
# à compléter
else:
# à compléter
return False
Q6. Tester votre fonction recherche_dichotomie avec la fonction test_recherche_dichotomie() donnée ci-dessous (Ne pas oublier que les listes doivent êtres triées...)
from random import randint
def test_recherche_dichotomie():
assert recherche_dichotomie([1,2,3],1) == True
assert recherche_dichotomie([1,2,3],4) == False
assert recherche_dichotomie([],1) == False # avec liste vide
print("Tous les tests sont passés")
test_recherche_dichotomie()
Q7. D'après le cours, rappeler (ou redémontrer !) quelle est la complexité de cet algorithme.
Réponse : à compléter
Rappel : on a vu dans le TD L1 sur la complexité une façon de visualiser que cette complexité est bien logarithmique.
Q8. Complexité chronométrique :
Dans cette question on va comparer les vitesses d'exécution des fonctions recherche_sequentielle et recherche_dichotomie dans le pire des cas, c'est à dire lorsque l'élément n'est pas dans la liste.
- Utiliser la fonction
liste_aleadu début du TD pour créer une listeLde 1000 entiers aléatoires. - Trier cette liste en tapant :
L.sort() - Chronométrer 10000 répétitions de la fonction
recherche_sequentielle(L,-1) - Chronométrer 10000 répétitions de la fonction
recherche_dichotomie(L,-1) - Comparer
- Recommencer avec une liste
Lde taille 100000
from time import time
taille = 1000
# à compléter
print("Le temps d'exécution dans le pire des cas avec la fonction 'recherche_sequentielle' est :", t1-t0)
# à compléter
print("Le temps d'exécution dans le pire des cas avec la fonction 'recherche_dichotomie' est :", t1-t0)
Q8. (pour les plus rapide) Refaire la question précédente pour des valeurs de taille prises dans la liste liste_n = [2**i for i in range(13)] et remplir deux listes tseq et tdic qui contiennent les temps mis par chacun des deux algorithmes. Faire le graphique de tdic/tseqen fonction de net voir que les écarts augmentent très rapidement...
from time import time
import matplotlib.pyplot as plt
liste_n = [2**i for i in range(13)]
tdic = []
tseq = []
for taille in liste_n :
# à compléter
# dessin :
X = liste_n
Y = [tdic[i]/tseq[i] for i in range(len(tdic))]
plt.clf()
plt.plot(X,Y,'o')
plt.show()
III. Rappels du cours de mathématiques¶
On a déjà rencontré cette notion de dichotomie en cours de mathématiques afin de résoudre efficacement des équations du type $f(x)=0$.
On rappelle ici l'algorithme utilisé
def f(x) :
return x*x-2
def dichotomie(f,a,b,eps):
assert f(a)*f(b)<0
while (b-a)>eps:
m = (b+a)/2
if f(a)*f(m)<0:
b=m
else:
a=m
return (a,b)
a,b = dichotomie(f,0,2,0.001)
print(f"Une solution de f(x)=0 est comprise entre {a:.3f} et {b:.3f}")
Q9. Tester, et comparer les algorithmes et bien visualiser que le principe est le même !
IV. Un cas concret¶
On a lié à cette feuille un fichier nommé Provence_10km_trie.txt qui contient les résultats des coureurs sur la course à pied des 10km de la Provence (année ??). Les données sont triées par ordre alphabétique et les noms sont écrits en majuscule, sans accents...
Ce fichier texte est organisé de la manière suivante :
txt:
NOM : PLACE
Q10. En retournant si besoin voir les TD sur la lectures de données dans un fichier, compléter fonction read_datas() ci-dessous qui va lire les données de ce fichier et qui renvoie 2 listes: une constituée des NOMS, l'autres des PLACES correspondantes.
def read_datas():
""" récupère les données du fichier 'Provence_10km_trie.txt' dont les données
sont stockées en deux colonnes séparées par un ':'
NOM : PLACE
La fonction renvoie deux listes : la première avec les noms des particpants,
la deuxième avec leurs places respectives
"""
noms = []
places = []
with open('Provence_10km_trie.txt') as f :
for line in f :
if line == '\n' : break
nom,place = # à compléter
noms.append(nom.strip())
places.append(place.strip())
return noms, places
# on teste
n,p = read_datas()
print(len(n))
print(n[:10])
print(p[:10])
Q11. En vous inspirant de la fonction recherche_dichotomie, écrire une fonction find_friend(names : list, ranks : list, friend : str) -> int qui reçoit la liste des noms, la liste des places correspondantes ainsi que le nom d'un ami (écrit en majuscules sans accents) et qui renvoie la place de cet ami dans la course (et 0 s'il n'y était pas !)
def find_friend(names : list, ranks : list, friend : str) -> int :
""" reçoit la liste des noms, la liste des places correspondantes
ainsi que le nom d'un ami (écrit en majuscules sans accents) et
qui renvoie la place de cet ami dans la course (et 0 s'il n'y était pas !)"""
a,b = 0, len(names)-1
while b-a >=0 :
# à copmpléter
Q12. Donner les places (s'ils étaient présents) des coureurs : 'MURAGA', 'ZUFFO', 'GRIEZMANN'
names, ranks = read_datas()
friends = ['MURAGA', 'ZUFFO', 'GRIEZMANN' ]
for f in friends :
rk = find_friend(names,ranks,f)
if rk == 0 :
print(f"{f} non trouvé dans la liste")
else :
print(f"Place de {f} : {rk}")