Les algorithmes de parcours séquentiel permettent de parcourir les éléments d'un tableau à l'aide d'une boucle.
Ils font partie des algorithmes fondamentaux en informatique, leur parfaite maîtrise est donc indispensable car ils sont à la base de beaucoup d'autres algorithmes qui doivent passer en revue tous les éléments d'une collection (tableau, tuple, liste, dictionnaires, etc.).
On détaille dans ce chapitre les trois algorithmes au programme :
On veut écrire un algorithme qui cherche si un élément est présent ou non dans un tableau.
La spécification d’un algorithme précise de manière non ambigüe ce que doit faire un algorithme. En particulier, on y indique : le nom des données manipulées (entrées), le nom des données renvoyées (sorties), le rôle de l’algorithme ainsi que les hypothèses sur les entrées (précondition) et les sorties (postcondition). Un algorithme sera considéré comme correct s’il respecte cette spécification.
Voici la spécification de l'algorithme de recherche d'un élément :
T
de taille $n$ et une valeur v
trouvee
v
est présente dans T
v
est du même type que les éléments de T
trouvee
vaut Vrai si v
appartient à T
, et Faux sinonL'idée est la suivante :
trouvee
est initialisé à Faux
T
:
v
, le booléen trouvee
prend la valeur Vrai
Voici l'algorithme :
trouvee ← Faux
pour i de 0 à n-1 faire :
si T[i] = v alors :
trouvee ← Vrai
fin si
fin pour
Le coût d'un algorithme est le nombre d’opérations élémentaires (arithmétiques ou logiques) ainsi que d’affectations nécessaires à son exécution dans le pire cas.
Le coût d’un algorithme dépend toujours de la taille des données d’entrée (ici la taille $n$ du tableau).
Dans tous les cas, notre algorithme va effectuer $n$ tours de boucle, donc dans le pire cas aussi. Ainsi, la comparaison T[i] = v
sera effectuée $n$ fois.
Si tous les éléments du tableau sont égaux à v
alors l'affectation trouvee ← Vrai
est effectuée à chaque tour de boucle, donc $n$ fois.
On dénombre ainsi :
Comparaisons | Affectations | Opérations arithmétiques |
---|---|---|
$n$ | $n+1$ | $0$ |
Le coût de l'algorithme (dans le pire cas) est donc $n + (n+1) + 0 = 2n + 1$.
Comme ce coût est de l'ordre de $n$, on dit que le coût de l'algorithme de recherche d'un élément est linéaire.
On peut écrire une fonction appartient()
possédant deux paramètres v
et T
qui implémente cet algorithme. Cette fonction renvoie True
si v
est dans T
et False
sinon.
def appartient(v, T):
trouvee = False
for i in range(len(T)):
if T[i] == v:
trouvee = True
return trouvee
On peut tester cette fonction :
>>> appartient(5, [2, 3, 1, 5, 0])
True
>>> appartient(1, [0, 3, 0, 0])
False
Il est aussi possible de parcourir le tableau par valeur et non par indice car on n'a pas besoin des indices d'après la spécification de l'algorithme. La fonction suivante implémente cette possibilité :
def appartient_v2(v, T):
trouvee = False
for e in T:
if e == v:
trouvee = True
return trouvee
On peut tester :
>>> appartient_v2(5, [2, 3, 1, 5, 0])
True
>>> appartient_v2(1, [0, 3, 0, 0])
False
L'algorithme ainsi écrit et implémenté par les deux fonctions précédentes peut sembler non optimal. En effet, si le premier élément du tableau T
est la valeur v
alors on sait de suite que la réponse est "Vrai" mais notre algorithme va quand même tester toutes les valeurs suivantes du tableau T
.
On peut utiliser le mot clé return
pour stopper l'exécution de notre fonction dès que la valeur v
est trouvée. Dans le cas, où la boucle for
a atteint la fin du tableau sans trouver v
, il faut renvoyer False
.
def appartient_v3(v, T):
for e in T:
if e == v:
return True # renvoie True dès qu'on trouve v (ce qui stoppe l'exécution de la fonction)
return False # après la boucle (si v n'a pas été trouvé)
>>> appartient_v3(5, [2, 3, 1, 5, 0])
True
>>> appartient_v3(1, [0, 3, 0, 0])
False
Remarques :
v
cherchée, alors il aurait été obligatoire de parcourir le tableau par indice, pour garder trace des indices et renvoyer celui demandé. Le choix du parcours dépend donc de la spécification de l'algorithme à écrire !v
est trouvée) sans utiliser une sortie anticipée (avec return
) à condition d'utiliser une boucle while
. Cependant l'écriture est plus longue et compliquée :def appartient_v4(v, T):
i = 0
trouvee = False
while i <= len(T) and trouvee == False:
if T[i] == v:
trouvee = True
i = i + 1
return trouvee
On veut écrire un algorithme qui recherche la valeur maximale dans un tableau.
La recherche du maximum est présentée ici, mais ce qui va être dit est vrai pour la recherche d'un extremum de manière générale, qu'il s'agisse d'un maximum ou d'un minimum).
T
de taille $n$maxi
T
T
est un tableau non vide d'entiersmaxi
est un entier qui est l'élément maximal de T
L'idée est la suivante :
T
(à partir de la deuxième) :
Voici l'algorithme :
maxi ← T[0]
pour i de 1 à n-1 faire :
si T[i] > maxi alors :
maxi ← T[i]
fin si
fin pour
Dans tous les cas, notre algorithme va effectuer $n-1$ tours de boucle, donc dans le pire cas aussi. Ainsi, la comparaison T[i] > maxi
sera effectuée $n-1$ fois.
Si les éléments sont rangés dans l'ordre (strictement) croissant, alors la condition T[i] > maxi
est vraie à chaque tour de boucle et l'affectation maxi ← T[i]
est effectuée à chaque tour de boucle.
Le pire cas est donc un tableau dans lequel chaque élément est strictement supérieur au précédent (par exemple [1, 2, 3, 4, 5]
pour un tableau de $n=5$ éléments).
On dénombre ainsi dans le pire cas :
Comparaisons | Affectations | Opérations arithmétiques |
---|---|---|
$n-1$ | $n$ | $0$ |
Remarque : on n'a pas compté la soustraction du n-1
(cela ne change rien au coût)
Le coût de l'algorithme (dans le pire cas) est donc $n-1 + n + 0 = 2n - 1$.
Comme ce coût est de l'ordre de $n$, on dit que le coût de l'algorithme de recherche d'un extremum est linéaire.
On peut écrire une fonction maximum()
, possédant en paramètre un tableau d'entiers T
non vide, qui implémente cet algorithme. Cette fonction renvoie la valeur maximale présente dans T
.
def maximum(T):
maxi = T[0]
for i in range(1, len(T)):
if T[i] > maxi:
maxi = T[i]
return maxi
On peut tester :
>>> maximum([1, 2, 3, 4, 5])
5
>>> maximum([7, 9, 2])
9
Il est aussi possible de parcourir le tableau par valeur et non par indice car on n'a pas besoin des indices d'après la spécification de l'algorithme. La fonction suivante implémente cette possibilité :
def maximum_v2(T):
maxi = T[0]
for e in T:
if e > maxi:
maxi = e
return maxi
Remarques :
On veut écrire un algorithme qui calcule la moyenne des nombres présents dans un tableau.
T
de taille $n$m
T
T
est un tableau non vide d'entiersm
est la moyenne des éléments de T
L'idée est de calculer la somme des valeurs de T
puis de calculer la moyenne en divisant la somme par le nombre d'éléments de T
:
T
:
n
(le nombre d'éléments de T
)Voici l'algorithme :
s ← 0
pour i de 0 à n-1 faire :
s ← s + T[i]
fin pour
m ← s/n
Dans tous les cas, notre algorithme va effectuer $n$ tours de boucle, donc dans le pire cas aussi. Ainsi, l'affectation s ← s + T[i]
est effectuée à chaque tour de boucle.
N'importe quel tableau est donc un pire cas pour notre algorithme.
On dénombre ainsi :
Comparaisons | Affectations | Opérations arithmétiques |
---|---|---|
$0$ | $n+2$ | $n+1$ |
Remarque : il y a $n$ additions (s ← s + T[i]
) et une division (s/n
) donc $n+1$ opérations arithmétiques.
Le coût de l'algorithme (dans le pire cas) est donc $0 + (n+2) + (n+1) = 2n + 3$.
Comme ce coût est de l'ordre de $n$, on dit que le coût de l'algorithme de calcul d'une moyenne est linéaire.
On peut écrire une fonction moyenne()
, possédant en paramètre un tableau d'entiers T
non vide, qui implémente cet algorithme. Cette fonction renvoie la moyenne des valeurs présentes dans T
.
def moyenne(T):
s = 0
for i in range(len(T)):
s = s + T[i]
return s / len(T)
On peut tester :
>>> moyenne([10, 14, 6])
10.0
>>> moyenne([8, 15, 12, 14, 19, 11])
13.166666666666666
Il est aussi possible de parcourir le tableau par valeur et non par indice car on n'a pas besoin des indices. La fonction suivante implémente cette possibilité :
def moyenne_v2(T):
s = 0
for e in T:
s = s + e
return s / len(T)
Remarque : Cette fonction est légèrement plus simple à écrire et peut donc être privilégiée pour la calcul de la moyenne des éléments d'un tableau.
Références :
Germain BECKER & Sébastien POINT, Lycée Mounier, ANGERS