DC1 : tableau (= liste en Python)
A2 : complexité
Pour trouver un nombre dans une liste 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.
//déclarations val: Valeur cherchée (entier) i: Entier liste : liste de N entiers triés retour : Booléen //initialisation retour ← faux i ← 0 //Boucle de recherche // La condition début inférieur ou égal à fin permet d'éviter de faire // une boucle infinie si 'val' n'existe pas dans le tableau. Tant que i < N et retour est faux: si liste[i] == val: retour ← vrai i ← i+1 //Affichage du résultat Si retour == vrai: Afficher "La valeur ", val, " est dans la liste." Sinon: Afficher "La valeur ", val, " n'est pas dans la liste."
Le but est d'implémenter la recherche naïve précédente en Python de manière itérative.
Pour cela écrire le script de la fonction naive_search(lst, val)
où lst
est une liste d'entier et val
la valeur à chercher dans la liste.
Cette fonction renvoie True
si val
est dans la liste lst
et False
sinon.
Penser à tester cette fonction avec les valeurs 3
, 12
, 48
et
4
et avec la liste : ma_liste = [3, 5, 12, 15, 48]
Faire le même exercice en utilisant désormais une boucle for
.
On voit que cet algorithme n'est peut-être pas le plus pertinent, car dans le pire des cas, c'est-à-dire le cas où l'élément cherché n'est pas présent, on devra parcourir toute la liste. Le coût en temps dans le pire des cas est donc linéaire.
Cette méthode, vous la connaissez tous!
Vous avez probablement joué au jeu : trouve un nombre entre 1 et 100.
Vous avez commencé par proposer 50.
Si on vous répond, c'est plus, vous proposez 75 puis si l'on vous répond c'est moins vous proposez 62...
Vous êtes alors en train d'effectuer une recherche dichotomique !
Faire une recherche dichotomique, c'est chercher une valeur dans un tableau trié en prenant, à chaque étape, le milieu de l'ensemble des valeurs possibles pour éliminer la moitié des possibilités.
Soit une liste de $n$ objets déjà triés. On recherche un objet en particulier.
on choisit dans la liste l'objet médian (une moitié des objets est avant, l'autre moitié est après).
on compare les deux objets
Trois cas :
si on a trouvé l'objet cherché alors c'est fini.
si l'objet recherché est placé avant l'objet médian alors on recommence avec cette première partie de la liste.
si l'objet recherché est placé après l'objet médian alors on recommence avec cette seconde partie de la liste.
on répète cette démarche jusqu'à ce qu'au bout d'un certain nombre d'essais (cela se calcule) :
soit on a trouvé l'objet cherché,
soit il n'est pas dans la liste.
Prenons par exemple la liste triée suivante :
[1, 3, 4, 6, 7, 8, 10, 13, 14]
, où nous allons rechercher par dichotomie
l'élément 4.
On utilise deux pointeurs indice_gauche
et indice_droit
pour délimiter la sous-liste sur laquelle porte la recherche courante, au départ il
s'agit de la liste entière.
indice_gauche
pointe vers l'élément d'indice 0 et indice_droit
pointe vers le dernier élément de notre liste qui ici a l'indice 8.
On cherche l'indice indice_centre
de l'élément médian en ne gardant que la
partie entière de la moyenne indice_gauche
et indice_droit
,
ici $\frac{0+8}{2}=4$, l'élément médian est donc l'élément d'indice 4,
c'est-à-dire l'élément 7.
Maintenant on compare l'élément central 7 à l'élément recherché 4 : ici $4<7$, cela signifie que 4 ce trouve dans la première moitié de la liste (celle avant 7).
On modifie alors la valeur du pointeur indice_droit
, qui prend la valeur de
(indice_centre-1), et on recommence avec cette nouvelle sous-liste.
indice_gauche
= 0 ; indice_droit
= 3 et indice_centre
=1
(partie entière de $\frac{0+3}{2}$, c'est-à-dire de 1.5).
Maintenant on compare l'élément central 3 à l'élément recherché 4 : ici $4 > 3$, cela signifie que que 4 ce trouve dans la deuxième moitié de la sous-liste (celle après 3).
On modifie alors la valeur du pointeur indice_gauche
, qui prend la valeur de
(indice_centre+1), et on recommence avec cette nouvelle sous-liste.
indice_gauche
=2 ; indice_droit
= 3 et indice_centre
=2
(partie entière de $\frac{2+3}{2}$, c'est-à-dire de 2.5).
Maintenant on compare l'élément central 4 à l'élément recherché 4 : ici $4 = 4$, l'élément recherché est présent.
On reprend le tableau trié par ordre croissant de l'exemple précédent où l'on cherche la valeur 4.
Si la sous-liste devient vide, c'est-à-dire que le pointeur indice_gauche
devient supérieur au pointeur indice_droit
, la recherche est infructueuse
et l'algorithme s'arrête.
Décrivez chacune des étapes de progression de la recherche dichotomique de l'élément 35
appliquée à la liste ordonnée [2, 12, 17, 25, 33, 35, 44, 54, 77, 91]
.
Indiquez ensuite le nombre de comparaisons nécessaires à cette recherche.
Décrivez chacune des étapes de progression de la recherche dichotomique de l'élément 50
appliquée à la liste ordonnée [2, 12, 17, 25, 33, 35, 44, 54, 77, 91]
.
Indiquez ensuite le nombre de comparaisons nécessaires à cette recherche.
Voici le pseudo-code en langage naturel de cet algorithme :
//déclarations indice_gauche, indice_droite, elmt, indice_centre: Entiers liste : liste de N entiers triés retour : Booléen (elmt est la valeur cherchée) //initialisation indice_gauche ← 0 indice_droite ← N-1 retour ← faux //Boucle de recherche // La condition début inférieur ou égal à fin permet d'éviter de faire // une boucle infinie si 'elmt' n'existe pas dans le tableau. Tant que retour != vrai et indice_gauche <= indice_droite: indice_centre ← partie_entière((indice_gauche + indice_droite)/2) si liste[indice_centre] == elmt: retour ← vrai sinon: si elmt > liste[indice_centre]: indice_gauche ← indice_centre+1 sinon: indice_droit ← indice_centre-1 //Affichage du résultat Si retour == vrai: Afficher "La valeur ", elmt, " est au rang ", indice_centre Sinon: Afficher "La valeur ", elmt, " n'est pas dans le tableau."
Voici une animation (inspirée de celle de www.infoforall.fr) qui permet de trier par dichotomie un tableau trié par ordre croissant :
Sélectionner la valeur cherchée dans le tableau :
Indice | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Élément | 6 | 8 | 9 | 13 | 18 | 35 | 37 | 59 | 60 | 62 | 63 | 68 | 70 | 78 | 80 | 83 | 84 | 89 | 91 | 96 |
Écrire une fonction recherche_dichotomie
qui reçoit en paramètre une
liste nommée liste
et l'élément recherché nommé elmt
et qui retourne un booléen en fonction de la présence ou non de l'élément
recherché.
Tester le script avec les deux exemples traités dans l'exercice 3 (cf. accès direct.).
Voici une liste de prénom d'ami.e.s :
liste_amis = ['Ariel', 'Benjamin', 'Amir', 'Melvina', 'Hector', 'Zoé', 'Yasmine', 'Ursula', 'Andréa', 'Marie-Claire', 'Marc-Aurèle', 'Jim', 'Paula'
'Pietro', 'Xavier', 'Éléonore', 'Arthur', 'Firmin', 'Alice', 'Bob', 'Firdaous', 'Olga', 'Shinzo', 'Li', 'Mathéo', 'Rachel', 'Philippine', 'Tao']
.
Trier par ordre croissant cette liste liste_amis
.
Vous pouvez soit implémenter un tri par sélection ou par insertion pour vérifier
que vous maîtrisez toujours les algorithmes vu au
chapitre A3,
soit vous pouvez utiliser directement la fonction sorted
:
lst2 = sorted(lst1)
: lst2
est une liste triée formé des éléments
de la liste lst1
.
Utilisez votre programme de l'exercice 4 afin de savoir si 'Paulo'
fait partie de la liste liste_amis
.
Utilisez votre programme de l'exercice 4 afin de savoir si 'Yasmine'
fait partie de la liste liste_amis
.
Le but de l'exercice est de compléter une fonction rechercher
qui détermine
si une valeur val
est présente
dans un tableau de valeurs tab
triées dans l'ordre croissant.
L'algorithme traite le cas du tableau vide et il est écrit pour que la recherche dichotomique
ne se fasse que dans le cas où la valeur est comprise entre les valeurs extrêmes du
tableau.
def rechercher(tab, val):
"""
tab : tableau trié dans l'ordre croissant
val : nombre entier
La fonction renvoie True si tab contient val et False sinon
"""
# cas du tableau vide
if ...:
return False
# cas où val n'est pas compris entre les valeurs extrêmes
if (val < tab[...]) or ...:
return ...
# cas où val est compris entre les valeurs extrêmes
deb = 0
fin = ...
while deb <= fin:
mil = ...
if val == tab[mil]:
return ...
elif val > tab[mil]:
deb = ...
else:
fin = ...
return ...
Compléter le script précédent puis tester avec le jeu de tests suivant :
assert rechercher([], 0) == False
assert rechercher([0, 1, 2, 3, 4, 5, 6, 7], 9) == False
assert rechercher([0, 1, 2, 3, 5, 6, 7], 4) == False
assert rechercher([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 6) == True
Nous avons vu précédemment que l'algorithme naïf de recherche d'un nombre dans une liste triée
avait dans le pire des cas une complexité linéaire.
Voyons si la dichotomie est plus avantageuse de ce point de vu.
Déterminez le nombre maximal de comparaisons nécessaires à la recherche d'un élément dans une liste, en complétant le tableau ci-dessous.
Taille de la liste | 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | N |
---|---|---|---|---|---|---|---|---|---|---|
Recherche séquentielle | ||||||||||
Recherche dichotomique |
Le coût pour rechercher par la méthode par dichotomie une valeur dans un tableau à $2^n$
éléments est de l'ordre de $n$ dans le pire des cas.
On dit que le coût est logarithmique car le nombre $p$ tel que $2^p=N$
se note $log_2(N)=p$ ; $p$ est appelé logarithme de base 2 de $p$.
Cette fonction $log_2$ peut être définie quand $N$ est une puissance de $2$ par le programme suivant :
def log2(n):
compteur = 0
while n > 0:
compteur += 1
n = n // 2
return compteur
Exercice qui reprend le jeu introductif.
Écrire un programme qui permette à l'ordinateur de jouer contre l'utilisateur pour
retrouver un nombre.
L'utilisateur choisira un nombre entre 0 et 100 et l'ordinateur devra le trouver,
le plus efficacement possible.
À chaque proposition faite par l'ordinateur,
l'utilisateur devra donner une réponse sous la forme d'une chaîne de caractères codant
explicitement les notions de "plus grand", "plus petit" ou "bravo, tu as trouvé !".
Copier puis compléter le script suivant en vous servant des commentaires.
def ...:
print("Choisissez dans votre tête un nombre entre 0 et 100 et l'ordinateur essaiera de le deviner.")
print("Donnez-lui des indices en répondant par :")
print("'plus grand' ou plus simplement par "+" si le nombre choisi est plus grand que la proposition.")
print("'plus petit' ou plus simplement par "-" si le nombre choisi est plus petit que la proposition.")
print("'bravo, tu as trouvé !' ou plus simplement par "=" lorsque l'ordinateur trouve le bon nombre.")
bas = 0
haut = 100
trouve = ... # booléen
nb_essai = ... # entier
while ...:
# Proposition de l'ordinateur (entier au milieu de l'intervalle de recherche)
proposition = ...
print("L'ordinateur propose : ", ...)
essai = ...
# L'utilisateur donne un indice
reponse = input("Réponse (plus grand ou + / plus petit ou - / bravo, tu as trouvé ! ou =) : ")
# Traitement de la réponse
if reponse == "plus grand" ou reponse == "+":
...
elif reponse == "plus petit" ou reponse == "-":
...
elif reponse == "bravo, tu as trouvé !" ou reponse == "=":
trouve = ...
print("L'ordinateur a trouvé le nombre en", ..., "essai(s) !")
else:
print("Réponse non reconnue, merci d'utiliser : 'plus grand', '+', 'plus petit', '-', 'bravo, tu as trouvé !' ou '='.")
Attention ! Il y a d'autres manières d'écrire le script demandé, manières tout aussi pertinentes que celle proposée ci-dessus avec des trous.
def recherche_dichotomie(lst, elt):
indice_gauche = 0
indice_droit = len(lst) - 1
rep = False
while rep == False and indice_gauche <= indice_droit :
indice_centre = (indice_gauche + indice_droit)//2
if lst[indice_centre] == elt :
rep = True
else:
if elt > lst[indice_centre]:
indice_gauche = indice_centre + 1
else:
indice_droit = indice_centre - 1
return rep
Dans l'algorithme précédent, nous avons une boucle conditionnelle while
, nous devons donc nous interroger sur la
terminaison de cet algorithme, c'est-à-dire le fait que l'algorithme se termine toujours
en un nombre fini d'étapes.
On appelle variant de boucle une quantité entière qui :
est positive ou nulle lorsque l'on reste dans la boucle ;
décroît strictement à chaque itération de la boucle.
Un variant de boucle sert à prouver la terminaison d'une boucle, c'est-à-dire que l’on sort nécessairement de la boucle au bout d’un nombre fini d’itérations.
La variable longueur = indice_droit - indice_gauche
est un variant de boucle pour l'algorithme de dichotomie.
Voici une démonstration à lire et à comprendre justifiant que longueur = indice_droit - indice_gauche
est un variant de boucle.
Il suffit de prouver deux choses par définition du variant :
longueur
est toujours positif ou nul.
longueur
diminue strictement à chaque étape.
Première condition : longueur
positive ou nulle :
La deuxième condition nécessaire de la boucle while
: indice_gauche <= indice_droit
signifie qu'à chaque itération
longueur
est positif ou nul.
Seconde condition : longueur
diminue strictement à chaque itération :
Étudions les étapes de chaque itérations :
L'itération commence par l'indice du milieu avec indice_centre = (indice_gauche + indice_droit) // 2
.
Forcément indice_gauche <= indice_centre <=indice_droit
Ensuite, l'itération se poursuit par une structure conditionnelle pour laquelle il y a trois cas :
Cas où lst[indice_centre] == elt
:
La valeur cherchée est trouvée, le booléen rep
passe à True
ce qui met fin
à la boucle while
. La terminaison est assurée alors.
Cas où lst[indice_centre] < elt
:
Alors indice_gauche
prend la valeur indice_centre + 1
si bien que la variable longueur
va diminuer au moins de 1. En effet :
La nouvelle valeur de longueur
est : longueur = indice_droit - indice_gauche = indice_droit - (indice_centre + 1) = indice_droit - indice_centre - 1
.
Comme indice_gauche <= indice_centre
, indice_droit - indice_centre - 1 <= indice_droit - indice_gauche - 1
.
Ainsi, longueur <= indice_droit - indice_gauche - 1
: le variable a strictement diminuée.
Cas où lst[indice_centre] > elt
:
Alors indice_droit
prend la valeur indice_centre - 1
si bien que la variable longueur
va diminuer au moins de 1. En effet :
La nouvelle valeur de longueur
est : longueur = indice_droit - indice_gauche = (indice_centre - 1) - indice_gauche = indice_centre - indice_gauche - 1
.
Comme indice_centre <= indice_droit
, indice_centre - indice_gauche - 1 <= indice_droit - indice_gauche - 1
.
Ainsi, longueur <= indice_droit - indice_gauche - 1
: le variable a strictement diminuée.
Le variant diminuant strictement, il y a aura au plus $n+1$ itérations où $n$ est la valeur initiale du variant : l'algorithme se termine donc dans tous les cas !
On considère l'algorithme suivant :
def recherche(lst, elt):
pos = 0
n = len(lst)
while pos < n and lst[pos] != elt:
pos = pos + 1
return pos < n
Dans quel cas la fonction recherche
renvoie False
?
Démontrer que la variable définie par var = n - pos
est un variant.
Voici une fonction separer
qui prend en paramètre une liste lst
formée de nombres entiers et une valeur entière val
.
Cette fonction est dite à effet de bord : elle modifie la liste
saisie comme argument et renvoie cette liste modifiée.
def separer(lst: list, val: int) -> list:
i = 0
j = len(lst) - 1
while i < j :
if lst[i] <= val:
i = i + 1
else:
temp = lst[i]
lst[i] = lst[j]
lst[j] = temp
j = j - 1
return lst
Prouver que cette fonction se termine toujours en exhibant un variant de boucle.
Tester cette fonction avec les couples suivant :
[6, 8, 3, 9, 4, 1, 2, 6], 5
[6, 8, 3, 9, 4, 1, 2, 6], 4
[6, 8, 3, 9, 4, 1, 2, 6], 0
[6, 8, 3, 9, 4, 1, 2, 6], 9
Quel est le rôle des trois premières lignes suivant le else:
?
Décrire le rôle de la fonction separer
.
Propriétaire des ressources ci-dessous : ministère de l'Éducation nationale et de la jeunesse, licence CC BY SA NC
Voici une sélection de questions issues de la banque nationale de sujets, répondez à ces questions (attention, cette sélection n'est pas exhaustive).
Un algorithme de recherche dichotomique dans une liste triée de taille $n$ nécessite, dans le pire des cas, exactement $k$
comparaisons.
Combien cet algorithme va-t-il utiliser, dans le pire des cas, de comparaisons sur une liste de taille $2n$ ?
Réponses :
A- $k$
B- $k+1$
C- $2k$
D- $2k+1$
On considère la fonction suivante :
def f(x,L):
g = 0
d = len(L)-1
while g < d:
m = (g+d)//2
if x <= L[m]:
d = m
else:
g = m + 1
return g
Cette fonction implémente :
Réponses :
A- le tri par insertion
B- le tri par sélection
C- la recherche dichotomique
D- la recherche du plus proche voisin
En utilisant une recherche dichotomique, combien faut-il de comparaisons pour trouver une valeur dans un tableau trié de 1000 nombres ?
Réponses :
A- 3
B- 10
C- 1000
D- 1024
Quelle précondition suppose l'algorithme de recherche dichotomique dans un tableau ?
Réponses :
A- que le tableau soit à éléments positifs
B- que le tableau soit trié
C- que l'élément cherché dans le tableau soit positif
D- que l'élément cherché figure effectivement dans le tableau
La fonction ci-dessous permet d’effectuer une recherche par dichotomie de l’index
m
de l’élément x
dans un tableau L
de valeurs distinctes et triées.
def dicho(x,L):
g = 0
d = len(L)-1
while g <= d:
m = (g+d)//2
if L[m] == x:
return m
elif L[m] < x:
g = m+1
else:
d = m-1
return None
Combien de fois la cinquième ligne du code de la fonction m = (g+d)//2
sera-t-elle exécutée dans l'appel
dicho(32, [4, 5, 7, 25, 32, 50, 51, 60])
?
Réponses :
A- 1 fois
B- 2 fois
C- 3 fois
D- 4 fois
Plusieurs des QCM suivants sont issus de https://genumsi.inria.fr.
Auteur : Christophe JOUBERT
Avec un algorithme de recherche dichotomique, combien d'étapes sont nécessaires pour déterminer que 45 n'est pas présent dans la liste suivante :
[1, 7, 12, 16, 18, 20, 24, 28, 35, 43, 69]
?
Réponses :
A- 2
B- 3
C- 4
D- 5
Autrice : Sylvie Genre
On souhaite écrire une fonction recherche_dichotomique(t, v)
, qui renvoie une position v
dans le tableau t
, supposé trié, et None
si v
ne s'y trouve pas.
Parmi les 4 fonctions ci-dessous, laquelle est correcte ?
Réponses :
A-
def recherche_dichotomique(t, v) :
g = 0
d = len(t) - 1
while g <= d :
m = (g + d) // 2
if t[m] < v :
g = m + 1
elif t[m] > v :
d = m - 1
else :
return m
return None
B-
def recherche_dichotomique(t, v) :
g = 0
d = len(t) - 1
while g <= d :
m = (g + d) // 2
if t[m] > v :
g = m + 1
elif t[m] < v :
d = m - 1
else :
return m
return None
C-
def recherche_dichotomique(t, v) :
g = 0
d = len(t)
while g <= d :
m = (g + d) // 2
if t[m] < v :
g = m + 1
elif t[m] > v :
d = m - 1
else :
return m
return None
D-
def recherche_dichotomique(t, v) :
g = 0
d = len(t) - 1
while g < d :
m = (g + d) // 2
if t[m] < v :
g = m + 1
elif t[m] > v :
d = m - 1
else :
return m
return None
Auteur : Antony OLIVIER
Par quel moyen usuel peut-on démontrer la terminaison d'une boucle ?
Réponses :
A- une assertion
B- un invariant
C- un variant
D- la complexité
Dans la fonction suivante, les valeurs des variables x et y sont des entiers naturels :
def f(x, y) :
s = x
t = y
while t > 0 :
s = s + 1
t = t - 1
return s
Quelle affirmation est fausse ?
Réponses :
A- La propriété s + t = x + y
est un invariant de boucle.
B- La variable s + t
est un variant de boucle.
C- La propriété t est supérieur ou égal à 0
est un invariant de la boucle while.
D- La variable t
est un variant de la boucle while.
Il faut actualiser la page pour changer de question. Propriétaire de la ressource : le site GeNumsi en licence CC BY_NC-SA
Voici une liste de sites traitant de la dichotomie :
Les différents
auteurs mettent l'ensemble du site à disposition selon les termes de la licence Creative
Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0
International