récursivité algorithmique et complexité algorithmique et Les algorithmes de tri

Yassine Anddam
Yassine Anddamprofesseur à Education

La récursivité en algorithmique Initiation à la complexité algorithmique Les algorithmes de tri

La récursivité en algorithmique
1
I. Définition
Un algorithme est dit récursif s'il est défini en fonction de lui-même.
La récursion est un principe puissant permettant de définir une entité à l'aide d'une partie de celle-ci.
Chaque appel successif travaille sur un ensemble d'entrées toujours plus précis, en se rapprochant de plus en
plus de la solution d'un problème.
Evolution d’un appel récursif
L'exécution d'un appel récursif passe par deux phases, la phase de descente et la phase de la remontée :
 Dans la phase de descente, chaque appel récursif fait à son tour un appel récursif. Cette phase se
termine lorsque l'un des appels atteint une condition terminale.
Condition pour laquelle la fonction doit retourner une valeur au lieu de faire un autre appel récursif.
 La phase de la remontée, cette phase se poursuit jusqu'à ce que l'appel initial soit terminé, ce qui
termine le processus récursif.
II. Les types de la récursivité
La récursivité simple
La fonction contient un seul appel récursif dans son corps.
Exemple : la fonction factorielle
Trace d’exécution de la fonction factorielle (calcul de la valeur de 4! )
La récursivité multiple
La fonction contient plus d'un appel récursif dans son corps.
Exemple : le calcul du nombre de combinaisons en se servant de la relation de Pascal :
La trace d’exécution des fonctions récursive
multiple est sous forme d’arbre inversé.
La récursivité mutuelle
Des fonctions sont dites mutuellement récursives si elles dépendent les unes des autres
Par exemple la définition de la parité d'un entier peut être écrite de la manière suivante :
def pair(n) :
if n>=1 :
def imppair(n) :
if n>=1 :
def factorielle(n) :
if n>0 :
return n*factorielle(n-1)
return 1
def combinaison(n,p) :
if n>1 :
return combinaison(n-1,p) + combinaison(n-1,p-1)
return 1
Phase descente
Condition d’arrêt
Phase remontée
2
return impair(n-1)
return True
return pair(n-1)
return False
La récursivité imbriquée
La récursivité imbriquée consiste à faire un appel récursif à l'intérieur d'un autre appel récursif.
Exemple : La fonction d'Ackermann
def ackermann(m, n):
if m == 0:
return n + 1
elif n == 0 and m>0:
return ackermann(m - 1, 1)
else:
return ackermann(m - 1, ackermann(m, n - 1))
III. Transformer une boucle en une procédure récursive
Soit la procédure suivante :
def compter(n) :
s=0
for i in range(1,n+1):
s+=i
return s
Cette procédure peut être traduite en une procédure récursive, qui admet un paramètre ; l'instruction qui
l'appellera sera "compter(1)":
def compter(n) :
if n>1 :
return n + compter(n-1)
else:
return 1
IV. Transformer deux boucles imbriquées en une procédure récursive
Supposons qu'on ait maintenant deux boucles imbriquées. Nous allons traduire progressivement cette
procédure itérative en une procédure récursive avec deux paramètres :
def compter02(n,p) :
if n>0:
P=0
for b in range(p):
P+= Matrice[n-1,b]
return P+compter02(n-1,p)
return 0
print(compter02(3,5))
Pour supprimer les deux boucles, on commence par supprimer la première en suivant l'exemple ci-dessus ;
on obtient la procédure suivante que l'on appelle avec "afficher(0)" :
def compter02(n,p) :
if n>0:
P=0
for b in range(p):
P+= Matrice[n-1,b]
return P+compter02(n-1,p)
return 0
print(compter02(3,5))
3
Il ne nous reste plus qu'à supprimer la deuxième boucle; en sachant que lorsque "b=10" dans la procédure
initiale, le programme revient à la boucle sur "a" et remet "b" à zéro, alors on a 2 appels récursifs :
 le premier dans le cas où "b" est inférieur à 10 et alors on appelle la procédure avec "a" inchangé et
"b" incrémenté de 1
 le deuxième où "b=10" et alors on appelle la procédure avec "a" incrémenté de 1 et "b" initialisé à 0.
L'appel sera "affiche(0,0)".
def compter03(n,p) :
if n>0 :
if p>0 :
return Matrice[n-1,m-1]+compter03(n,m-1)
return compter03(n-1,p)
return 0
4
Initiation à la complexité algorithmique
I. Introduction
Un algorithme est un ensemble d’instructions permettant de transformer un ensemble de données en un
ensemble de résultats avec un nombre fini d’étapes. Pour atteindre cet objectif, un algorithme utilise deux
ressources machine :
 le temps
 l’espace mémoire.
L’optimisation de l’efficacité en termes d’exécution va à l’encontre (en opposition) de l’optimisation en
espace.
Il n’y a pas de méthode ou d’échelle de mesure permettant d’évaluer la fiabilité ou la robustesse d’un
algorithme. Par contre, il existe des méthodes rationnelles et rigoureuses pour évaluer l’efficacité en espace
et en temps d’un algorithme, Analyse de la "complexité des algorithmes".
La complexité en temps d’un algorithme se mesure essentiellement en calculant le nombre d’opérations
élémentaires pour traiter une donnée de taille n.
On note Dn l’ensemble des données de taille n et T(n) le coût de l’algorithme sur la
donnée de taille n.
II. Les types de complexités
On définit 3 types de complexités :
A. Complexité au meilleur
Tmin(n) = mindϵDnT(d)
C’est le plus petit nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu de données de taille fixée
(ici n). C’est une borne inférieure de la complexité de l’algorithme sur un jeu de données de taille n.
B. Complexité au pire
Tmax(n) = maxdϵDnT(d)
C’est le plus grand nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu de données de taille fixée
(ici n).
Avantage : il s’agit d’un maximum et l’algorithme finira donc toujours avant d’avoir effectué Tmax(n)
opérations.
Inconvénient : cette complexité peut ne pas refléter le comportement usuel de l’algorithme, le pire cas
pouvant ne se produire que très rarement.
C. Complexité en moyenne
Tmoy(n) = ∑dϵDnT(d)/Dn
C’est la moyenne des complexités de l’algorithme sur des jeux de données de taille n.
Avantage : elle reflète le comportement général de l’algorithme si les cas extrêmes sont rares ou si la
complexité varie peu en fonction des données.
Inconvénient : la complexité sur un jeu de donnes particulier peut être nettement plus importante que la
complexité en moyenne, dans ce cas la complexité moyenne ne donnera pas une bonne indication du
comportement de l’algorithme.
Un algorithme est dit optimal si sa complexité est la complexité minimale parmi les algorithmes de sa
classe.
III. Définitions
A. La complexité asymptotique
La complexité asymptotique d’un algorithme décrit le comportement de celui-ci quand la taille n des
données du problème traité devient de plus en plus grande, plutôt qu’une mesure exacte du temps
d’exécution.
La complexité asymptotique de l’algorithme donne une mesure approximative du temps pris pour
l’exécution d’un algorithme étudié.
T : Dn → IN
n → T(n)
5
Exemple
On considère que un algorithme (dans le pire des cas) a pris le temps suivant :
Tmax(n) = (n+1)*a + (n-1)*b + (2n+1)*c,
Alors on dira que la complexité de cet algorithme est tout simplement en n.
On élimine toute constante, et on suppose que les opérations d’affectation, de test et d’addition ont des
temps constants.
B. Notation de Grand-O
Soit f(n) une fonction non négative. f(n) est en O(g(n)) (f est dominée par g) s’il existe deux constante
positives c et n0 telles que: f(n) <= c*g(n) pour tout n >= n0.
Utilité : Le temps d’exécution est borné
Signification : Pour toutes les grandes entrées (i.e, n >= n0), on est assuré que l’algorithme ne prend pas plus
de c(g(n)) étapes : borne supérieure.
Exemple 1
Initialiser une liste d’entiers de taille n
Il y a n itérations dont chacune nécessite un temps <= c où c’est une constante (test logique + accès à la liste
+ affectation + incrémentation).
Le temps est donc T(n) <= c*n
Donc T(n) = O(n) (grand-O de n)
Exemple 2
T(n) = c
On écrit T(n) = O(1).
Exemple 3
T(n) = c1n2
+ c2n
c1n2
+ c2n <= c1n2
+ c2n2
= (c1 + c2)n2
pour tout n >= 1.
T(n) <= c.n2
où c = c1 + c2 et n0 = 1.
Donc : T(n) est en O(n2
).
Remarques
 Si f(n) = O(g(n)) et g(n) = O(h(n)) alors f(n) = O(h(n)).
 Si f(n) = O(kg(n)) où k > 0 est une constante alors f(n) = O(g(n)).
 Si f1(n) = O(g1(n)) et f2(n) = O(g2(n)) alors (f1 + f2)(n) = O(max(g1(n),
g2(n)))
 Si f1(n) = O(g1(n)) et f2(n) = O(g2(n)) alors f1(n)f2(n) = O(g1(n) g2(n))
IV. Quelques règles de calcul de complexité
Règle 1
La complexité d’un ensemble d’instructions est la somme des complexités de
chacune d’elles.
Règle 2 : instructions élémentaires
Les opérations élémentaires telles que l’affectation, test, accès à une liste (ou
tableau), opérations logiques et arithmétiques, lecture ou écriture d’une variable simple ... etc. Sont en O(1)
Règle 3 : instructions conditionnelles
Complexité de l’instruction Si: maximum entre le Alors et le
Sinon : T(n) = max(T(Bloc1), T(Bloc2))
Complexité de l’instruction de Sélection (switch) : maximum
parmi les différents cas.
Règle 4 : Instructions de répétition
La complexité de la boucle for est calculée par la complexité du
corps de cette boucle multipliée par le nombre de fois qu’elle est
répétée.
En règle générale, pour déterminer la complexité d’une boucle
while, il faudra avant tout déterminer le nombre de fois que cette boucle est répétée, ensuite le multiplier par
la complexité du corps de cette boucle.
Exemple 1
i = 0
while (i < n) :
L[i] = 0
i += 1
Exemple 2
Print(“Message”)
i=100*2
Exemple 3
S=0
for i in range(n)
for j in range(i+1,n)
S+=i*j
Print(“message”)
for i in range(n)
print(“intération:”,i)
6
Règle 5 : Procédure et fonction
Leur complexité est déterminée par celle de leur corps. L’appel à une fonction est supposé prendre un temps
constant en O(1).
On fait la distinction entre les fonctions récursives et celles qui ne le sont pas. Dans le cas de la récursivité,
le temps de calcul est exprimé comme une relation de récurrence.
V. Efficacité d’un algorithme
A. Définitions
Un algorithme est dit efficace si sa complexité (temporelle) asymptotique est dans O(P(n)) où P(n) est un
polynôme et n la taille des données du problème considéré.
On dit qu’un algorithme A est meilleur qu’un algorithme B si et seulement si : O(TA(n))<O(TB(n)).
B. Classification
Complexité constante O(1) : On rencontre cette complexité quand toutes les instructions du problème sont
exécutées une seule fois quel que soit la taille du problème.
Complexité linéaire O(n) : C’est le cas d’une boucle de 1 à n et le corps de la boucle effectue un travail de
durée constante et indépendante de n.
Exemple : calcul du produit scalaire de deux vecteurs.
Complexité logarithmique O(log(n)) : La durée d’exécution croit légèrement avec n.
Ce cas se rencontre quand la taille du problème est divisée par une constante à chaque itération.
Exemple : recherche dichotomique dans une liste triée L[0,…,n-1].
Complexité quasi-linéaire ou n-logarithmique O(nlog(n)): se rencontre dans les algorithme où à chaque
itération la taille du problème est divisée par une constante avec à chaque fois un parcours linéaire des
données.
Exemple : tri par fusion.
Complexité quadratique O(n2) : C’est le cas des algorithmes avec deux boucles imbriquées chacune allant
de 1 à n et avec le corps de la boucle interne qui est constant.
Exemple : multiplication de deux matrices carrées d'ordre n
Complexité cubique O(n3) : Quadratique mais avec trois boucles imbriquées.
Complexité exponentielle O(2n) : Les algorithmes de ce genre sont dit naïfs car ils sont inefficaces et
inutilisables dès que n dépasse 50. On rencontre typiquement ces algorithmes dans les parcours arborescents.
Exemple : tours de Hanoï.
VI. Exercices d’application
Calculer la complexité des programmes suivants :
Programme 1
def prog1(n):
for i in range(n):
print(i)
T(n)=c*n=n
La complexité est : O(n)
Programme 2
def prog2(n):
for i in range(n):
for j in range(n):
print(i)
T(n)=c*n*n=cn²=n²
La complexité est :O(n)
Programme 3
def prog3(n):
s=1
i=1
while s<=n:
i+=1
s=s+i
S 1 3 6 10 15 ………… n
i 1 2 3 4 5 ………… k
K*(K+1)/2=n , k²=n , k=√ donc t(n)=	√
Normalement c’est 2n mais les valeurs constantes
doivent être éliminer.
7
print(s)
La complexité est : O(√ )
Programme 4
import math
def prog4(n):
n=int(math.sqrt(n))
for i in range(1,n+1):
print(i)
t(n)=	√
La complexité est : O(√ )
Programme 5
for i in range(1,n+1):
for j in range(1,i+1):
for k in range(1,101):
Print(“message”)
i 1 2 3 … n
j 1 fois 2 fois 3 fois … n fois
k 100
fois
2*100
fois
3*100
fois
… n*100
fois
100+2*100+3*100+….+n*100
100*(1+2+…+n)
t(n)=n²
La complexité est :O(n²)
Programme 6
for i in range(1,n+1):
for j in range(1,pow(i,2)):
for k in range(1,(n//2)+1):
Print(“message”)
i 1 2 3 … n
j 1 fois 4 fois 9 fois … n² fois
k n/2
fois
n/2*4
fois
n/2*9
fois
… n/2*n²
fois
n/2+n/2*4+n/2*9+…+n/2*n²
n/2(1+2+9+…+n²)
n/2((n(n+1)(2n+1))/6)
t(n)=n4
La complexité est : O(n4
)
Programme 7
i,k=1,0
while i<n:
i*=2
k+=1
print(k)
i 1 2 3 … n
j 20
21
22
… 2k
n=2k
t(n)=log2(n)
La complexité est : O(log2(n))
Programme 8
def prog8(n):
l=0
for i in range(n//2,n+1):
for j in range(1,n//2+1):
k=1
while k<n: k*=2; l+=1
i = n/2 fois
j= n/2 fois
k=log2(n)
T(n)=n²log2(n)
La complexité est : O(n²log2(n))
Programme 9
for i in range(n//2,n+1):
j=1
while j<=n:
j*=2;k=1
while k<=n: k*=2
La complexité est : O(n*log2(n)²)
8
Programme 10
while n>1:
n/=2
n 2 2² 23
… n
ité 1 2 3 … 2k
La complexité est : O(log2(n))
Programme 11
for i in range(1,n+1):
for j in range(1,n+1,j+i):
Print(“message”)
i 1 2 3 … k n
J n n/2 n/3 … 2/k n/n
t(n)=n*(1+1/2+1/3+…+1/n)
t(n)=n*logn
La complexité est :O(n*log(n))
Programme 12
n=2**2**k
for i in range(1,n+1):
j=2
while j<=n:
j=j**2;print(“message”)
K 1 2 3 … 2^2^k
N 4 16 2^2^3=2^8 2^2^k=n
J 2,4 2,4,16 2,2^2,2^4,2^8
ité N*2 N*3 N*4 N*(k+1)
T(n)= N*(k+1)=N*2^2^k=n*log2(log2(n))
La complexité est : O(n*log2(log2(n)))
9
Les algorithmes de tri
Introduction
Le problème auquel nous allons nous confronter est très simple à comprendre : à partir d’une liste de
données numériques, réordonner ces valeurs par ordre croissant.
Tout sous-programme résolvant ce problème sera naturellement une procédure, il n'y a pas lieu ici de
calculer une valeur à retourner. La liste à trier en sera alors un paramètre qui sera lu et modifié.
Pour des raisons évidentes d'optimisation de la mémoire utilisée, nos tris se feront "sur place", c'est-à-dire
que nous n'utiliserons pas de listes intermédiaires pour stocker provisoirement les valeurs.
Il s’agit d’un des plus problèmes les plus classiques de l’algorithmique. De nombreuses résolutions en sont
possibles avec des méthodes radicalement différentes.
Méthodes utilisées
Nous étudierons dans ce chapitre deux types d'algorithmes de tri :
 Des algorithmes itératifs
 Des algorithmes récursifs, basés sur le paradigme “diviser pour régner”, dans lesquels le tri d’une
liste s’effectuera en la divisant plusieurs fois par deux jusqu’à obtention de sous-listes ne comportant
qu’une seule valeur.
I. Algorithmes itératifs
On va présenter dans cette partie les trois algorithmes de tri itératifs les plus connus.
A. Tri par sélection
Le tri par sélection est assez intuitif du point de vue mathématique, puisqu'il consiste dans un premier temps
à mettre à la première place le plus petit élément de la liste, puis à la seconde place le deuxième plus petit
élément, etc.
En voici sa description précise :
 Rechercher dans la liste la plus petite valeur et la permuter avec le premier élément de la liste.
 Rechercher ensuite la plus petite valeur à partir de la deuxième case et la permuter avec le second
élément de la liste.
 Et ainsi de suite jusqu’à avoir parcouru toute la liste.
Exemple : Déroulement du tri par sélection sur un exemple
On considère la liste suivante de cinq entiers :
Première itération :
 On détermine le minimum des éléments de
la liste :
 Et on le permute avec le premier élément de
la liste :
Seconde itération :
 On détermine le minimum des éléments de
la liste à partir de la deuxième case :
 Et on le permute avec le second élément de
la liste :
Troisième itération :
 On détermine le minimum des éléments de
la liste à partir de la troisième case :
 Et on le permute avec le troisième élément
de la liste :
Quatrième itération :
 On détermine le minimum des éléments de
la liste à partir de la quatrième case :
 Et on le permute avec le quatrième élément
de la liste :
10
 Le cinquième et dernier élément la liste est
de fait à sa place, le tri est terminé :
1. Version itérative
Tri par sélection Complexité
def triSelection(l):
for i in range(len(l)-1):
indMini=i
for j in range(i+1,len(l)):
if l[j]<l[indMini]:
indMini=j
l[i], l[indMini]= l[indMini],
l[i]
2. Version récursive
Tri par sélection Complexité
def RecSelection(list, i, j=1, flag=True):
print("i=",i,"j=",j)
size = len(list)
if (i < size - 1):
if flag:
j = i + 1;
if (j < size):
if (list[i] > list[j]):
list[i],list[j]=list[j],list[i]
RecSelection(list, i, j + 1, False);
else:
RecSelection(list, i + 1, i+2);
list = [6, 2, 3, 7, 1, 6, 2, 3, 7, 1]
print("Liste non triée : ",list)
RecSelection(list, 0)
print("Liste triée: ",list)
B. Tri à bulles
Le tri à bulles consiste à parcourir toute la liste en comparant chaque élément avec son successeur, puis en
les remettants dans le "bon" ordre si nécessaire. Et à recommencer si besoin est.
En voici sa description précise :
 Parcourir la liste en comparant chaque élément avec son successeur.
 Si ce dernier est le plus petit des deux, les permuter.
 Si lors du parcours de la liste une permutation au moins a été effectuée, recommencer un nouveau
parcours.
Exemple : Déroulement du tri à bulles sur un exemple
On considère la liste suivante de cinq entiers :
Première itération :
 On compare 5 et 8 :
 Ils sont dans l'ordre, donc on ne les permute
pas :
 On compare 8 et 2 :
Seconde itération :
 On compare 5 et 2 :
 On les permute pour les mettre dans l'ordre :
 On compare 5 et 8 :
 Ils sont dans l'ordre, donc on ne les permute
pas :
11
 On les permute pour les mettre dans l'ordre :
 On compare 8 et 9 :
 Ils sont dans l'ordre, donc on ne les permute
pas :
 On compare 9 et 5 :
 On les permute pour les mettre dans l'ordre :
 On compare 8 et 5 :
 On les permute pour les mettre dans l'ordre :
Troisième itération :
On parcourt de nouveau la liste, mais cette fois-ci
on constate qu’il n’y a plus de permutations à
effectuer.
 On n'effectue donc pas de quatrième itération,
la liste est triée :
1. Version itérative
Tri à bulles Complexité
def bubbleSort(L):
for dernier in range(len(L)-1,0,-1):
for i in range(dernier):
if L[i]>L[i+1]:
L[i],L[i+1]=L[i+1],L[i]
2. Version récursive
Tri à bulles Complexité
def RecBubble(Array, f, d=0):
if d < f-1:
if Array[d] > Array[d+1]:
Array[d],Array[d+1]=Array[d+1],Array[d]
RecBubble(Array, f, d+1)
elif f>1:
RecBubble(Array, f-1, 0)
list = [6, 4,3,1,5,2, 3, 7]
print("Liste non triée : ",list)
RecBubble(list, len(list)-1)
print("Liste triée : ",list)
C. Tri par insertion
Le tri par insertion est celui naturellement utilisé par les joueurs de cartes pour trier leur jeu. Il consiste à
insérer les éléments un par un en s'assurant que lorsque l'on rajoute un nouvel élément, les éléments déjà
insérés restent triés.
En voici sa description précise :
 Considérer le premier élément de la liste. A lui tout seul il constitue une liste triée.
 Insérer ensuite le second élément de telle sorte que les deux premiers éléments soient triés.
 Continuer ainsi en insérant successivement chaque élément à sa “bonne” place dans la partie déjà
triée de la liste.
Exemple : Déroulement du tri par insertion sur un exemple
On considère la liste suivante de cinq entiers :
Insertion du second élément vis-à-vis du
premier :
 On considère la valeur 3 :
 Que l’on retire provisoirement de la liste :
Insertion du troisième élément vis-à-vis des
deux premiers :
 On considère la valeur 1 :
 Que l’on retire provisoirement de la liste :
12
 On décale le 5
 Et on réinsère la valeur 3 :
 On décale le 3 et le 5 :
 Et on réinsère la valeur 1 :
Insertion du quatrième élément vis-à-vis des
trois premiers :
 On considère la valeur 4 :
 Que l’on retire provisoirement de la liste :
 On décale le 5 :
 Et on réinsère la valeur 4 :
Insertion du cinquième élément vis-à-vis des
quatre premiers :
 On considère la valeur 2 :
 Que l’on retire provisoirement de la liste :
 On décale le 3, le 4 et le 5 :
 Et on réinsère la valeur 2 :
Chaque élément a été inséré à sa place, le tri est terminé :
1. Version itérative
Tri par insertion Complexité
def insertionsort(A):
for i in range(len(A)):
for k in range(len(A)-1, i, -1 ):
if (A[k] < A[k-1]):
A[k],A[k-1] = A[k-1],A[k]
2. Version récursive
Tri par insertion Complexité
def insertionSort(array,i=1):
print("array",array)
if i >= len(array):
return array
if array[i-1] > array[i]:
temp = array[i]
for a in range(0, i):
print(array[a])
if temp < array[a]:
array.insert(a,temp)
del array[i+1]
break
return insertionSort(array, i+1)
L=[4,5,2,1] ; print("Liste non triée :
",L)
insertionSort(L); print("Liste triée :
",L)
II. Algorithmes récursifs
On va présenter dans cette partie les deux algorithmes de tri récursifs les plus connus. Ils utilisent tous les
deux le paradigme « diviser pour régner ».
13
A. Tri fusion
Le tri fusion consiste à trier récursivement les deux moitiés de la liste, puis à fusionner ces deux sous-listes
triées en une seule. La condition d’arrêt à la récursivité sera l’obtention d'une liste à un seul élément, car une
telle liste est évidemment déjà triée.
Voici donc les trois étapes (diviser, régner et combiner) de cet algorithme :
1. Diviser la liste en deux sous-listes de même taille (à un élément près) en la "coupant" par la moitié.
2. Trier récursivement chacune de ces deux sous-listes. Arrêter la récursion lorsque les listes n'ont plus
qu'un seul élément.
3. Fusionner les deux sous-listes triées en une seule.
Exemple : Déroulement du tri fusion sur un exemple
On considère la liste suivante de sept entiers :
On la subdivise en deux sous-listes en la coupant par la moitié :
Sous-listes que l’on scinde à leur tour :
Sous-listes que l’on scinde à leur tour :
Ces sous-listes sont triées car elles n’ont qu’un élément. On va maintenant les fusionner deux par deux en
de nouvelles sous-listes triées :
De nouveau une étape de fusionnement :
Une dernière fusion :
On a fusionné toutes les sous-listes obtenues lors des appels récursifs, le tri est terminé :
14
1. Algorithme
Algorithme Tri par fusion
def tri_fusion(liste):
if len(liste)<2:
return liste(:)
else:
milieu = len(liste)//2
listel = tri_fusion(liste[:milieu])
liste2 = tri_fusion(liste[milieu:])
return fusion(listel,liste2)
Fonction de fusion
def fusion(listel,liste2):
liste=[]
i, j=0,0
while i<len(listel)and j<len(liste2):
if listel[i]<=liste2[j]:
liste.append(listel[i]); i+=1
else:
liste.append(liste2[j]); j+=1
while i<len(listel):
liste.append(listel[i]); i+=1
while j<len(liste2):
liste.append(liste2[j]); j+=1
return liste
B. Tri rapide
Il existe plusieurs algorithmes qui ont une complexité optimale en O(n*log(n)). On remarque cependant
qu'en pratique, un de ces tris est souvent plus rapide que les autres. C'est le tri rapide, qui est un des
algorithmes de tri les plus adoptés.
Le tri rapide est un algorithme récursif. Le principe est de choisir un élément de la liste, que l'on nommera
pivot, et de créer deux autres listes : une à gauche du pivot, composée des éléments inférieurs au pivot et une
à droite du pivot, composée des éléments supérieurs au pivot. On applique la même méthode pour ces sous-
listes et ainsi de suite jusqu'à avoir des listes d'un seul élément.
Il existe plusieurs variantes du tri rapide selon la façon de choisir le pivot. Dans notre cas, nous choisirons
comme pivot le premier élément de la liste.
Voici donc les trois étapes (diviser, régner et combiner) de cet algorithme :
1. Considérer le premier élément de la liste et le positionner à sa place définitive, avec à sa gauche une
sous-liste constituée d’éléments qui lui sont inférieurs ou et égaux et à sa droite une sous-liste
constituée d’éléments qui lui sont strictement supérieurs.
2. Appliquer récursivement ce même traitement aux deux sous-listes ainsi obtenues. Arrêter la
récursion lorsque les listes n'ont plus qu'un seul élément.
3. Pas de résultats à combiner.
Exemple : Déroulement du tri rapide sur un exemple
On considère la liste suivante de huit entiers :
Lors du premier appel récursif, on va positionner le premier élément, à savoir 7, à sa place définitive. Pour
faire cela, on va mettre à sa gauche les éléments de la liste qui lui sont inférieurs ou égaux, et à sa droite
ceux qui lui sont strictement supérieurs :
15
Lors du second appel récursif, selon le même principe on va placer les éléments 4 et 10 qui sont les
premiers éléments des sous-listes créées lors de l'appel précédent :
Le placement des éléments 4 et 10 s'est fait à l’intérieur des deux sous-listes délimitées par l'élément 77.
On voit bien ici le côté récursif de cet algorithme, puisque l’on applique le même traitement aux deux
sous-listes qu’à la liste initiale.
Lors du troisième appel récursif, on va placer les éléments 2, 5 et 14 qui sont les premiers éléments des
sous-listes créées lors de l'appel précédent :
Lors du quatrième appel récursif, on va placer les éléments 12 et 16 qui sont les premiers éléments des
sous-listes créées lors de l'appel précédent :
Chaque élément a été positionné à sa place, le tri est terminé :
16
1. Algorithme
def quickSort(alist):
quickSortHelper(alist,0,len(alist)-1)
def quickSortHelper(alist,first,last):
if first<last:
splitpoint = partition(alist,first,last)
quickSortHelper(alist,first,splitpoint-1)
quickSortHelper(alist,splitpoint+1,last)
def partition(alist,first,last):
pivotvalue = alist[first]
leftmark = first+1
rightmark = last
done = False
while not done:
while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
leftmark = leftmark + 1
while alist[rightmark] >= pivotvalue and rightmark >= leftmark:
rightmark = rightmark -1
if rightmark < leftmark:
done = True
else:
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
return rightmark
alist = [54,26,93,17,77,31,44,55,20]
quickSort(alist)
print(alist)
III. Comparaison des algorithmes de tri
Algorithme de tri Pire cas Cas moyen Meilleur cas (optimal)
Sélection n² n² n²
Bulle n² n² n
Insertion n² n² n
Fusion n logn n logn n logn
Rapide n² n logn n logn

Recommandé

Chapitre 4 récursivité par
Chapitre 4 récursivitéChapitre 4 récursivité
Chapitre 4 récursivitéSana Aroussi
5.6K vues62 diapositives
Récursivité par
RécursivitéRécursivité
Récursivitémohamed_SAYARI
5.9K vues3 diapositives
Chapitre iii récursivité et paradigme diviser pour régner par
Chapitre iii récursivité et paradigme diviser pour régnerChapitre iii récursivité et paradigme diviser pour régner
Chapitre iii récursivité et paradigme diviser pour régnerSana Aroussi
4.8K vues60 diapositives
Chapitre 3 NP-complétude par
Chapitre 3 NP-complétudeChapitre 3 NP-complétude
Chapitre 3 NP-complétudeSana Aroussi
4K vues84 diapositives
Cours algorithmique et complexite complet par
Cours algorithmique et complexite completCours algorithmique et complexite complet
Cours algorithmique et complexite completChahrawoods Dmz
58.3K vues104 diapositives
Chapitre 2 complexité par
Chapitre 2 complexitéChapitre 2 complexité
Chapitre 2 complexitéSana Aroussi
7.4K vues34 diapositives

Contenu connexe

Tendances

Algorithmes d'approximation par
Algorithmes d'approximationAlgorithmes d'approximation
Algorithmes d'approximationmohamed_SAYARI
21.9K vues13 diapositives
Chapitre vi np complétude par
Chapitre vi np complétudeChapitre vi np complétude
Chapitre vi np complétudeSana Aroussi
2.5K vues66 diapositives
Les algorithmes recurrents par
Les algorithmes recurrentsLes algorithmes recurrents
Les algorithmes recurrentsmohamed_SAYARI
18.9K vues10 diapositives
Chapitre iv algorithmes de tri par
Chapitre iv algorithmes de triChapitre iv algorithmes de tri
Chapitre iv algorithmes de triSana Aroussi
12.1K vues83 diapositives
Cours tic complet par
Cours tic completCours tic complet
Cours tic completsofixiito
49.4K vues88 diapositives
Chap04 les-algorithme-de-tri-et-de-recherche par
Chap04 les-algorithme-de-tri-et-de-rechercheChap04 les-algorithme-de-tri-et-de-recherche
Chap04 les-algorithme-de-tri-et-de-rechercheRiadh Harizi
3.4K vues15 diapositives

Tendances(20)

Algorithmes d'approximation par mohamed_SAYARI
Algorithmes d'approximationAlgorithmes d'approximation
Algorithmes d'approximation
mohamed_SAYARI21.9K vues
Chapitre vi np complétude par Sana Aroussi
Chapitre vi np complétudeChapitre vi np complétude
Chapitre vi np complétude
Sana Aroussi2.5K vues
Chapitre iv algorithmes de tri par Sana Aroussi
Chapitre iv algorithmes de triChapitre iv algorithmes de tri
Chapitre iv algorithmes de tri
Sana Aroussi12.1K vues
Cours tic complet par sofixiito
Cours tic completCours tic complet
Cours tic complet
sofixiito49.4K vues
Chap04 les-algorithme-de-tri-et-de-recherche par Riadh Harizi
Chap04 les-algorithme-de-tri-et-de-rechercheChap04 les-algorithme-de-tri-et-de-recherche
Chap04 les-algorithme-de-tri-et-de-recherche
Riadh Harizi3.4K vues
Chapitre 2 -Complexité des problèmes avec correction.pdf par MbarkiIsraa
Chapitre 2 -Complexité des problèmes avec correction.pdfChapitre 2 -Complexité des problèmes avec correction.pdf
Chapitre 2 -Complexité des problèmes avec correction.pdf
MbarkiIsraa445 vues
exercices Corrigées du merise par Yassine Badri
exercices Corrigées du  meriseexercices Corrigées du  merise
exercices Corrigées du merise
Yassine Badri20.9K vues
Chapitre i introduction et motivations par Sana Aroussi
Chapitre i introduction et motivationsChapitre i introduction et motivations
Chapitre i introduction et motivations
Sana Aroussi1.8K vues
Chapitre v algorithmes gloutons par Sana Aroussi
Chapitre v algorithmes gloutonsChapitre v algorithmes gloutons
Chapitre v algorithmes gloutons
Sana Aroussi8.6K vues
Algorithme & structures de données Chap III par Ines Ouaz
Algorithme & structures de données Chap IIIAlgorithme & structures de données Chap III
Algorithme & structures de données Chap III
Ines Ouaz12K vues
Le problème de voyageur de commerce: algorithme génétique par Rima Lassoued
Le problème de voyageur de commerce: algorithme génétiqueLe problème de voyageur de commerce: algorithme génétique
Le problème de voyageur de commerce: algorithme génétique
Rima Lassoued24.1K vues
cours de complexité algorithmique par Atef MASMOUDI
cours de complexité algorithmiquecours de complexité algorithmique
cours de complexité algorithmique
Atef MASMOUDI4.8K vues
Réseaux de neurones récurrents et LSTM par Jaouad Dabounou
Réseaux de neurones récurrents et LSTMRéseaux de neurones récurrents et LSTM
Réseaux de neurones récurrents et LSTM
Jaouad Dabounou1.7K vues
EXPOSE SUR L’ALGORITHME DU TRI À BULLES (BUBBLE SORT). par vangogue
EXPOSE SUR L’ALGORITHME DU TRI À BULLES (BUBBLE SORT).EXPOSE SUR L’ALGORITHME DU TRI À BULLES (BUBBLE SORT).
EXPOSE SUR L’ALGORITHME DU TRI À BULLES (BUBBLE SORT).
vangogue6.7K vues

Similaire à récursivité algorithmique et complexité algorithmique et Les algorithmes de tri

Cours algorithmique et complexite par
Cours algorithmique et complexite Cours algorithmique et complexite
Cours algorithmique et complexite Saddem Chikh
1.4K vues104 diapositives
Cours algorithmique et complexite complet par
Cours algorithmique et complexite completCours algorithmique et complexite complet
Cours algorithmique et complexite completChahrawoods Dmz
5.3K vues104 diapositives
chap 3 complexité (3).pdf par
chap 3 complexité (3).pdfchap 3 complexité (3).pdf
chap 3 complexité (3).pdfdonixwm
23 vues32 diapositives
cours algorithme par
cours algorithmecours algorithme
cours algorithmemohamednacim
260 vues129 diapositives
Cours_3_0910_2.pdf par
Cours_3_0910_2.pdfCours_3_0910_2.pdf
Cours_3_0910_2.pdfSongSonfack
11 vues32 diapositives
Cours_3_0910.pdf par
Cours_3_0910.pdfCours_3_0910.pdf
Cours_3_0910.pdfLAHCIENEELHOUCINE
127 vues32 diapositives

Similaire à récursivité algorithmique et complexité algorithmique et Les algorithmes de tri(20)

Cours algorithmique et complexite par Saddem Chikh
Cours algorithmique et complexite Cours algorithmique et complexite
Cours algorithmique et complexite
Saddem Chikh1.4K vues
Cours algorithmique et complexite complet par Chahrawoods Dmz
Cours algorithmique et complexite completCours algorithmique et complexite complet
Cours algorithmique et complexite complet
Chahrawoods Dmz5.3K vues
chap 3 complexité (3).pdf par donixwm
chap 3 complexité (3).pdfchap 3 complexité (3).pdf
chap 3 complexité (3).pdf
donixwm23 vues
124776153 td-automatique-1 a-jmd-2011 par sunprass
124776153 td-automatique-1 a-jmd-2011124776153 td-automatique-1 a-jmd-2011
124776153 td-automatique-1 a-jmd-2011
sunprass8.1K vues
Exercices%20g%C3%A9n%C3%A9raux%20sur%20Python (1).pdf par adeljaouadi
Exercices%20g%C3%A9n%C3%A9raux%20sur%20Python (1).pdfExercices%20g%C3%A9n%C3%A9raux%20sur%20Python (1).pdf
Exercices%20g%C3%A9n%C3%A9raux%20sur%20Python (1).pdf
adeljaouadi2 vues
Travaux Dirigés N°03_Automatique-Asservissement.pdf par CoolDangbenon
Travaux Dirigés N°03_Automatique-Asservissement.pdfTravaux Dirigés N°03_Automatique-Asservissement.pdf
Travaux Dirigés N°03_Automatique-Asservissement.pdf
CoolDangbenon4 vues
algo et complexité .pptx par tarekjedidi
algo et complexité  .pptxalgo et complexité  .pptx
algo et complexité .pptx
tarekjedidi51 vues
Algorithmique seconde (corrigés et commentaires) par DriNox NordisTe
Algorithmique seconde (corrigés et commentaires)Algorithmique seconde (corrigés et commentaires)
Algorithmique seconde (corrigés et commentaires)
DriNox NordisTe2.6K vues
1 analyse-et-mesure-des-performances par m.a bensaaoud
1 analyse-et-mesure-des-performances1 analyse-et-mesure-des-performances
1 analyse-et-mesure-des-performances
m.a bensaaoud333 vues
Cours Recherche opérationnelle (les files d'attentes) par Aboubakr Moubarak
Cours Recherche opérationnelle (les files d'attentes)Cours Recherche opérationnelle (les files d'attentes)
Cours Recherche opérationnelle (les files d'attentes)
Aboubakr Moubarak 6.9K vues
optimisation cours.pdf par Mouloudi1
optimisation cours.pdfoptimisation cours.pdf
optimisation cours.pdf
Mouloudi153 vues

Plus de Yassine Anddam

Corrige iscae informatique 2010 par
Corrige iscae informatique 2010Corrige iscae informatique 2010
Corrige iscae informatique 2010Yassine Anddam
424 vues1 diapositive
Corrige iscae informatique 2011 par
Corrige iscae informatique 2011Corrige iscae informatique 2011
Corrige iscae informatique 2011Yassine Anddam
294 vues1 diapositive
Corrige iscae informatique 2012 par
Corrige iscae informatique 2012Corrige iscae informatique 2012
Corrige iscae informatique 2012Yassine Anddam
309 vues2 diapositives
Corrigé iscae informatique 2013 par
Corrigé iscae informatique 2013Corrigé iscae informatique 2013
Corrigé iscae informatique 2013Yassine Anddam
307 vues1 diapositive
Corrigé iscae informatique 2014 par
Corrigé iscae informatique 2014Corrigé iscae informatique 2014
Corrigé iscae informatique 2014Yassine Anddam
363 vues1 diapositive
Corrige iscae informatique 2015 par
Corrige iscae informatique 2015Corrige iscae informatique 2015
Corrige iscae informatique 2015Yassine Anddam
435 vues3 diapositives

Plus de Yassine Anddam(20)

Corrige iscae informatique 2017 CPGE ECT par Yassine Anddam
Corrige iscae informatique 2017 CPGE ECTCorrige iscae informatique 2017 CPGE ECT
Corrige iscae informatique 2017 CPGE ECT
Yassine Anddam229 vues
Corrige iscae informatique 2017 ens general par Yassine Anddam
Corrige iscae informatique 2017 ens generalCorrige iscae informatique 2017 ens general
Corrige iscae informatique 2017 ens general
Yassine Anddam263 vues
ISCAE informatique septemebre 2009 par Yassine Anddam
ISCAE informatique septemebre 2009ISCAE informatique septemebre 2009
ISCAE informatique septemebre 2009
Yassine Anddam114 vues
Concours iscae informatique 2017 Enseignement général par Yassine Anddam
Concours iscae informatique 2017 Enseignement généralConcours iscae informatique 2017 Enseignement général
Concours iscae informatique 2017 Enseignement général
Yassine Anddam309 vues
Cnaem 2013 epreuve gestion, management et informatique par Yassine Anddam
Cnaem 2013 epreuve gestion, management et informatiqueCnaem 2013 epreuve gestion, management et informatique
Cnaem 2013 epreuve gestion, management et informatique
Yassine Anddam3.5K vues

Dernier

La Lettre Formelle.pptx par
La Lettre Formelle.pptxLa Lettre Formelle.pptx
La Lettre Formelle.pptxstudymaterial91010
14 vues10 diapositives
Julia Margaret Cameron par
Julia Margaret CameronJulia Margaret Cameron
Julia Margaret CameronTxaruka
59 vues20 diapositives
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2... par
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...BenotGeorges3
23 vues18 diapositives
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de... par
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...M2i Formation
5 vues36 diapositives
MNGTCOUT PROJET 04112023.pptx par
MNGTCOUT PROJET 04112023.pptxMNGTCOUT PROJET 04112023.pptx
MNGTCOUT PROJET 04112023.pptxHAIDI2
5 vues56 diapositives
Éléments visuels.pdf par
Éléments visuels.pdfÉléments visuels.pdf
Éléments visuels.pdfStagiaireLearningmat
27 vues2 diapositives

Dernier(13)

Julia Margaret Cameron par Txaruka
Julia Margaret CameronJulia Margaret Cameron
Julia Margaret Cameron
Txaruka59 vues
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2... par BenotGeorges3
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 (adapté au 2...
BenotGeorges323 vues
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de... par M2i Formation
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...
Formation M2i - Cadre réglementaire des IA Génératives : premiers éléments de...
M2i Formation5 vues
MNGTCOUT PROJET 04112023.pptx par HAIDI2
MNGTCOUT PROJET 04112023.pptxMNGTCOUT PROJET 04112023.pptx
MNGTCOUT PROJET 04112023.pptx
HAIDI25 vues
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23 par BenotGeorges3
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23Newsletter SPW Agriculture en province du Luxembourg du 13-11-23
Newsletter SPW Agriculture en province du Luxembourg du 13-11-23
BenotGeorges36 vues
Julia Margaret Cameron par Txaruka
Julia Margaret Cameron Julia Margaret Cameron
Julia Margaret Cameron
Txaruka5 vues
Formation M2i - Augmenter son impact en communication et en management grâce... par M2i Formation
Formation M2i - Augmenter son impact en communication et en management grâce...Formation M2i - Augmenter son impact en communication et en management grâce...
Formation M2i - Augmenter son impact en communication et en management grâce...
M2i Formation6 vues
Newsletter SPW Agriculture en province de LIEGE du 28-11-23 par BenotGeorges3
Newsletter SPW Agriculture en province de LIEGE du 28-11-23Newsletter SPW Agriculture en province de LIEGE du 28-11-23
Newsletter SPW Agriculture en province de LIEGE du 28-11-23
BenotGeorges321 vues
Présentation de lancement SAE105 par JeanLucHusson
Présentation de lancement SAE105Présentation de lancement SAE105
Présentation de lancement SAE105
JeanLucHusson19 vues

récursivité algorithmique et complexité algorithmique et Les algorithmes de tri

  • 1. La récursivité en algorithmique 1 I. Définition Un algorithme est dit récursif s'il est défini en fonction de lui-même. La récursion est un principe puissant permettant de définir une entité à l'aide d'une partie de celle-ci. Chaque appel successif travaille sur un ensemble d'entrées toujours plus précis, en se rapprochant de plus en plus de la solution d'un problème. Evolution d’un appel récursif L'exécution d'un appel récursif passe par deux phases, la phase de descente et la phase de la remontée :  Dans la phase de descente, chaque appel récursif fait à son tour un appel récursif. Cette phase se termine lorsque l'un des appels atteint une condition terminale. Condition pour laquelle la fonction doit retourner une valeur au lieu de faire un autre appel récursif.  La phase de la remontée, cette phase se poursuit jusqu'à ce que l'appel initial soit terminé, ce qui termine le processus récursif. II. Les types de la récursivité La récursivité simple La fonction contient un seul appel récursif dans son corps. Exemple : la fonction factorielle Trace d’exécution de la fonction factorielle (calcul de la valeur de 4! ) La récursivité multiple La fonction contient plus d'un appel récursif dans son corps. Exemple : le calcul du nombre de combinaisons en se servant de la relation de Pascal : La trace d’exécution des fonctions récursive multiple est sous forme d’arbre inversé. La récursivité mutuelle Des fonctions sont dites mutuellement récursives si elles dépendent les unes des autres Par exemple la définition de la parité d'un entier peut être écrite de la manière suivante : def pair(n) : if n>=1 : def imppair(n) : if n>=1 : def factorielle(n) : if n>0 : return n*factorielle(n-1) return 1 def combinaison(n,p) : if n>1 : return combinaison(n-1,p) + combinaison(n-1,p-1) return 1 Phase descente Condition d’arrêt Phase remontée
  • 2. 2 return impair(n-1) return True return pair(n-1) return False La récursivité imbriquée La récursivité imbriquée consiste à faire un appel récursif à l'intérieur d'un autre appel récursif. Exemple : La fonction d'Ackermann def ackermann(m, n): if m == 0: return n + 1 elif n == 0 and m>0: return ackermann(m - 1, 1) else: return ackermann(m - 1, ackermann(m, n - 1)) III. Transformer une boucle en une procédure récursive Soit la procédure suivante : def compter(n) : s=0 for i in range(1,n+1): s+=i return s Cette procédure peut être traduite en une procédure récursive, qui admet un paramètre ; l'instruction qui l'appellera sera "compter(1)": def compter(n) : if n>1 : return n + compter(n-1) else: return 1 IV. Transformer deux boucles imbriquées en une procédure récursive Supposons qu'on ait maintenant deux boucles imbriquées. Nous allons traduire progressivement cette procédure itérative en une procédure récursive avec deux paramètres : def compter02(n,p) : if n>0: P=0 for b in range(p): P+= Matrice[n-1,b] return P+compter02(n-1,p) return 0 print(compter02(3,5)) Pour supprimer les deux boucles, on commence par supprimer la première en suivant l'exemple ci-dessus ; on obtient la procédure suivante que l'on appelle avec "afficher(0)" : def compter02(n,p) : if n>0: P=0 for b in range(p): P+= Matrice[n-1,b] return P+compter02(n-1,p) return 0 print(compter02(3,5))
  • 3. 3 Il ne nous reste plus qu'à supprimer la deuxième boucle; en sachant que lorsque "b=10" dans la procédure initiale, le programme revient à la boucle sur "a" et remet "b" à zéro, alors on a 2 appels récursifs :  le premier dans le cas où "b" est inférieur à 10 et alors on appelle la procédure avec "a" inchangé et "b" incrémenté de 1  le deuxième où "b=10" et alors on appelle la procédure avec "a" incrémenté de 1 et "b" initialisé à 0. L'appel sera "affiche(0,0)". def compter03(n,p) : if n>0 : if p>0 : return Matrice[n-1,m-1]+compter03(n,m-1) return compter03(n-1,p) return 0
  • 4. 4 Initiation à la complexité algorithmique I. Introduction Un algorithme est un ensemble d’instructions permettant de transformer un ensemble de données en un ensemble de résultats avec un nombre fini d’étapes. Pour atteindre cet objectif, un algorithme utilise deux ressources machine :  le temps  l’espace mémoire. L’optimisation de l’efficacité en termes d’exécution va à l’encontre (en opposition) de l’optimisation en espace. Il n’y a pas de méthode ou d’échelle de mesure permettant d’évaluer la fiabilité ou la robustesse d’un algorithme. Par contre, il existe des méthodes rationnelles et rigoureuses pour évaluer l’efficacité en espace et en temps d’un algorithme, Analyse de la "complexité des algorithmes". La complexité en temps d’un algorithme se mesure essentiellement en calculant le nombre d’opérations élémentaires pour traiter une donnée de taille n. On note Dn l’ensemble des données de taille n et T(n) le coût de l’algorithme sur la donnée de taille n. II. Les types de complexités On définit 3 types de complexités : A. Complexité au meilleur Tmin(n) = mindϵDnT(d) C’est le plus petit nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu de données de taille fixée (ici n). C’est une borne inférieure de la complexité de l’algorithme sur un jeu de données de taille n. B. Complexité au pire Tmax(n) = maxdϵDnT(d) C’est le plus grand nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu de données de taille fixée (ici n). Avantage : il s’agit d’un maximum et l’algorithme finira donc toujours avant d’avoir effectué Tmax(n) opérations. Inconvénient : cette complexité peut ne pas refléter le comportement usuel de l’algorithme, le pire cas pouvant ne se produire que très rarement. C. Complexité en moyenne Tmoy(n) = ∑dϵDnT(d)/Dn C’est la moyenne des complexités de l’algorithme sur des jeux de données de taille n. Avantage : elle reflète le comportement général de l’algorithme si les cas extrêmes sont rares ou si la complexité varie peu en fonction des données. Inconvénient : la complexité sur un jeu de donnes particulier peut être nettement plus importante que la complexité en moyenne, dans ce cas la complexité moyenne ne donnera pas une bonne indication du comportement de l’algorithme. Un algorithme est dit optimal si sa complexité est la complexité minimale parmi les algorithmes de sa classe. III. Définitions A. La complexité asymptotique La complexité asymptotique d’un algorithme décrit le comportement de celui-ci quand la taille n des données du problème traité devient de plus en plus grande, plutôt qu’une mesure exacte du temps d’exécution. La complexité asymptotique de l’algorithme donne une mesure approximative du temps pris pour l’exécution d’un algorithme étudié. T : Dn → IN n → T(n)
  • 5. 5 Exemple On considère que un algorithme (dans le pire des cas) a pris le temps suivant : Tmax(n) = (n+1)*a + (n-1)*b + (2n+1)*c, Alors on dira que la complexité de cet algorithme est tout simplement en n. On élimine toute constante, et on suppose que les opérations d’affectation, de test et d’addition ont des temps constants. B. Notation de Grand-O Soit f(n) une fonction non négative. f(n) est en O(g(n)) (f est dominée par g) s’il existe deux constante positives c et n0 telles que: f(n) <= c*g(n) pour tout n >= n0. Utilité : Le temps d’exécution est borné Signification : Pour toutes les grandes entrées (i.e, n >= n0), on est assuré que l’algorithme ne prend pas plus de c(g(n)) étapes : borne supérieure. Exemple 1 Initialiser une liste d’entiers de taille n Il y a n itérations dont chacune nécessite un temps <= c où c’est une constante (test logique + accès à la liste + affectation + incrémentation). Le temps est donc T(n) <= c*n Donc T(n) = O(n) (grand-O de n) Exemple 2 T(n) = c On écrit T(n) = O(1). Exemple 3 T(n) = c1n2 + c2n c1n2 + c2n <= c1n2 + c2n2 = (c1 + c2)n2 pour tout n >= 1. T(n) <= c.n2 où c = c1 + c2 et n0 = 1. Donc : T(n) est en O(n2 ). Remarques  Si f(n) = O(g(n)) et g(n) = O(h(n)) alors f(n) = O(h(n)).  Si f(n) = O(kg(n)) où k > 0 est une constante alors f(n) = O(g(n)).  Si f1(n) = O(g1(n)) et f2(n) = O(g2(n)) alors (f1 + f2)(n) = O(max(g1(n), g2(n)))  Si f1(n) = O(g1(n)) et f2(n) = O(g2(n)) alors f1(n)f2(n) = O(g1(n) g2(n)) IV. Quelques règles de calcul de complexité Règle 1 La complexité d’un ensemble d’instructions est la somme des complexités de chacune d’elles. Règle 2 : instructions élémentaires Les opérations élémentaires telles que l’affectation, test, accès à une liste (ou tableau), opérations logiques et arithmétiques, lecture ou écriture d’une variable simple ... etc. Sont en O(1) Règle 3 : instructions conditionnelles Complexité de l’instruction Si: maximum entre le Alors et le Sinon : T(n) = max(T(Bloc1), T(Bloc2)) Complexité de l’instruction de Sélection (switch) : maximum parmi les différents cas. Règle 4 : Instructions de répétition La complexité de la boucle for est calculée par la complexité du corps de cette boucle multipliée par le nombre de fois qu’elle est répétée. En règle générale, pour déterminer la complexité d’une boucle while, il faudra avant tout déterminer le nombre de fois que cette boucle est répétée, ensuite le multiplier par la complexité du corps de cette boucle. Exemple 1 i = 0 while (i < n) : L[i] = 0 i += 1 Exemple 2 Print(“Message”) i=100*2 Exemple 3 S=0 for i in range(n) for j in range(i+1,n) S+=i*j Print(“message”) for i in range(n) print(“intération:”,i)
  • 6. 6 Règle 5 : Procédure et fonction Leur complexité est déterminée par celle de leur corps. L’appel à une fonction est supposé prendre un temps constant en O(1). On fait la distinction entre les fonctions récursives et celles qui ne le sont pas. Dans le cas de la récursivité, le temps de calcul est exprimé comme une relation de récurrence. V. Efficacité d’un algorithme A. Définitions Un algorithme est dit efficace si sa complexité (temporelle) asymptotique est dans O(P(n)) où P(n) est un polynôme et n la taille des données du problème considéré. On dit qu’un algorithme A est meilleur qu’un algorithme B si et seulement si : O(TA(n))<O(TB(n)). B. Classification Complexité constante O(1) : On rencontre cette complexité quand toutes les instructions du problème sont exécutées une seule fois quel que soit la taille du problème. Complexité linéaire O(n) : C’est le cas d’une boucle de 1 à n et le corps de la boucle effectue un travail de durée constante et indépendante de n. Exemple : calcul du produit scalaire de deux vecteurs. Complexité logarithmique O(log(n)) : La durée d’exécution croit légèrement avec n. Ce cas se rencontre quand la taille du problème est divisée par une constante à chaque itération. Exemple : recherche dichotomique dans une liste triée L[0,…,n-1]. Complexité quasi-linéaire ou n-logarithmique O(nlog(n)): se rencontre dans les algorithme où à chaque itération la taille du problème est divisée par une constante avec à chaque fois un parcours linéaire des données. Exemple : tri par fusion. Complexité quadratique O(n2) : C’est le cas des algorithmes avec deux boucles imbriquées chacune allant de 1 à n et avec le corps de la boucle interne qui est constant. Exemple : multiplication de deux matrices carrées d'ordre n Complexité cubique O(n3) : Quadratique mais avec trois boucles imbriquées. Complexité exponentielle O(2n) : Les algorithmes de ce genre sont dit naïfs car ils sont inefficaces et inutilisables dès que n dépasse 50. On rencontre typiquement ces algorithmes dans les parcours arborescents. Exemple : tours de Hanoï. VI. Exercices d’application Calculer la complexité des programmes suivants : Programme 1 def prog1(n): for i in range(n): print(i) T(n)=c*n=n La complexité est : O(n) Programme 2 def prog2(n): for i in range(n): for j in range(n): print(i) T(n)=c*n*n=cn²=n² La complexité est :O(n) Programme 3 def prog3(n): s=1 i=1 while s<=n: i+=1 s=s+i S 1 3 6 10 15 ………… n i 1 2 3 4 5 ………… k K*(K+1)/2=n , k²=n , k=√ donc t(n)= √ Normalement c’est 2n mais les valeurs constantes doivent être éliminer.
  • 7. 7 print(s) La complexité est : O(√ ) Programme 4 import math def prog4(n): n=int(math.sqrt(n)) for i in range(1,n+1): print(i) t(n)= √ La complexité est : O(√ ) Programme 5 for i in range(1,n+1): for j in range(1,i+1): for k in range(1,101): Print(“message”) i 1 2 3 … n j 1 fois 2 fois 3 fois … n fois k 100 fois 2*100 fois 3*100 fois … n*100 fois 100+2*100+3*100+….+n*100 100*(1+2+…+n) t(n)=n² La complexité est :O(n²) Programme 6 for i in range(1,n+1): for j in range(1,pow(i,2)): for k in range(1,(n//2)+1): Print(“message”) i 1 2 3 … n j 1 fois 4 fois 9 fois … n² fois k n/2 fois n/2*4 fois n/2*9 fois … n/2*n² fois n/2+n/2*4+n/2*9+…+n/2*n² n/2(1+2+9+…+n²) n/2((n(n+1)(2n+1))/6) t(n)=n4 La complexité est : O(n4 ) Programme 7 i,k=1,0 while i<n: i*=2 k+=1 print(k) i 1 2 3 … n j 20 21 22 … 2k n=2k t(n)=log2(n) La complexité est : O(log2(n)) Programme 8 def prog8(n): l=0 for i in range(n//2,n+1): for j in range(1,n//2+1): k=1 while k<n: k*=2; l+=1 i = n/2 fois j= n/2 fois k=log2(n) T(n)=n²log2(n) La complexité est : O(n²log2(n)) Programme 9 for i in range(n//2,n+1): j=1 while j<=n: j*=2;k=1 while k<=n: k*=2 La complexité est : O(n*log2(n)²)
  • 8. 8 Programme 10 while n>1: n/=2 n 2 2² 23 … n ité 1 2 3 … 2k La complexité est : O(log2(n)) Programme 11 for i in range(1,n+1): for j in range(1,n+1,j+i): Print(“message”) i 1 2 3 … k n J n n/2 n/3 … 2/k n/n t(n)=n*(1+1/2+1/3+…+1/n) t(n)=n*logn La complexité est :O(n*log(n)) Programme 12 n=2**2**k for i in range(1,n+1): j=2 while j<=n: j=j**2;print(“message”) K 1 2 3 … 2^2^k N 4 16 2^2^3=2^8 2^2^k=n J 2,4 2,4,16 2,2^2,2^4,2^8 ité N*2 N*3 N*4 N*(k+1) T(n)= N*(k+1)=N*2^2^k=n*log2(log2(n)) La complexité est : O(n*log2(log2(n)))
  • 9. 9 Les algorithmes de tri Introduction Le problème auquel nous allons nous confronter est très simple à comprendre : à partir d’une liste de données numériques, réordonner ces valeurs par ordre croissant. Tout sous-programme résolvant ce problème sera naturellement une procédure, il n'y a pas lieu ici de calculer une valeur à retourner. La liste à trier en sera alors un paramètre qui sera lu et modifié. Pour des raisons évidentes d'optimisation de la mémoire utilisée, nos tris se feront "sur place", c'est-à-dire que nous n'utiliserons pas de listes intermédiaires pour stocker provisoirement les valeurs. Il s’agit d’un des plus problèmes les plus classiques de l’algorithmique. De nombreuses résolutions en sont possibles avec des méthodes radicalement différentes. Méthodes utilisées Nous étudierons dans ce chapitre deux types d'algorithmes de tri :  Des algorithmes itératifs  Des algorithmes récursifs, basés sur le paradigme “diviser pour régner”, dans lesquels le tri d’une liste s’effectuera en la divisant plusieurs fois par deux jusqu’à obtention de sous-listes ne comportant qu’une seule valeur. I. Algorithmes itératifs On va présenter dans cette partie les trois algorithmes de tri itératifs les plus connus. A. Tri par sélection Le tri par sélection est assez intuitif du point de vue mathématique, puisqu'il consiste dans un premier temps à mettre à la première place le plus petit élément de la liste, puis à la seconde place le deuxième plus petit élément, etc. En voici sa description précise :  Rechercher dans la liste la plus petite valeur et la permuter avec le premier élément de la liste.  Rechercher ensuite la plus petite valeur à partir de la deuxième case et la permuter avec le second élément de la liste.  Et ainsi de suite jusqu’à avoir parcouru toute la liste. Exemple : Déroulement du tri par sélection sur un exemple On considère la liste suivante de cinq entiers : Première itération :  On détermine le minimum des éléments de la liste :  Et on le permute avec le premier élément de la liste : Seconde itération :  On détermine le minimum des éléments de la liste à partir de la deuxième case :  Et on le permute avec le second élément de la liste : Troisième itération :  On détermine le minimum des éléments de la liste à partir de la troisième case :  Et on le permute avec le troisième élément de la liste : Quatrième itération :  On détermine le minimum des éléments de la liste à partir de la quatrième case :  Et on le permute avec le quatrième élément de la liste :
  • 10. 10  Le cinquième et dernier élément la liste est de fait à sa place, le tri est terminé : 1. Version itérative Tri par sélection Complexité def triSelection(l): for i in range(len(l)-1): indMini=i for j in range(i+1,len(l)): if l[j]<l[indMini]: indMini=j l[i], l[indMini]= l[indMini], l[i] 2. Version récursive Tri par sélection Complexité def RecSelection(list, i, j=1, flag=True): print("i=",i,"j=",j) size = len(list) if (i < size - 1): if flag: j = i + 1; if (j < size): if (list[i] > list[j]): list[i],list[j]=list[j],list[i] RecSelection(list, i, j + 1, False); else: RecSelection(list, i + 1, i+2); list = [6, 2, 3, 7, 1, 6, 2, 3, 7, 1] print("Liste non triée : ",list) RecSelection(list, 0) print("Liste triée: ",list) B. Tri à bulles Le tri à bulles consiste à parcourir toute la liste en comparant chaque élément avec son successeur, puis en les remettants dans le "bon" ordre si nécessaire. Et à recommencer si besoin est. En voici sa description précise :  Parcourir la liste en comparant chaque élément avec son successeur.  Si ce dernier est le plus petit des deux, les permuter.  Si lors du parcours de la liste une permutation au moins a été effectuée, recommencer un nouveau parcours. Exemple : Déroulement du tri à bulles sur un exemple On considère la liste suivante de cinq entiers : Première itération :  On compare 5 et 8 :  Ils sont dans l'ordre, donc on ne les permute pas :  On compare 8 et 2 : Seconde itération :  On compare 5 et 2 :  On les permute pour les mettre dans l'ordre :  On compare 5 et 8 :  Ils sont dans l'ordre, donc on ne les permute pas :
  • 11. 11  On les permute pour les mettre dans l'ordre :  On compare 8 et 9 :  Ils sont dans l'ordre, donc on ne les permute pas :  On compare 9 et 5 :  On les permute pour les mettre dans l'ordre :  On compare 8 et 5 :  On les permute pour les mettre dans l'ordre : Troisième itération : On parcourt de nouveau la liste, mais cette fois-ci on constate qu’il n’y a plus de permutations à effectuer.  On n'effectue donc pas de quatrième itération, la liste est triée : 1. Version itérative Tri à bulles Complexité def bubbleSort(L): for dernier in range(len(L)-1,0,-1): for i in range(dernier): if L[i]>L[i+1]: L[i],L[i+1]=L[i+1],L[i] 2. Version récursive Tri à bulles Complexité def RecBubble(Array, f, d=0): if d < f-1: if Array[d] > Array[d+1]: Array[d],Array[d+1]=Array[d+1],Array[d] RecBubble(Array, f, d+1) elif f>1: RecBubble(Array, f-1, 0) list = [6, 4,3,1,5,2, 3, 7] print("Liste non triée : ",list) RecBubble(list, len(list)-1) print("Liste triée : ",list) C. Tri par insertion Le tri par insertion est celui naturellement utilisé par les joueurs de cartes pour trier leur jeu. Il consiste à insérer les éléments un par un en s'assurant que lorsque l'on rajoute un nouvel élément, les éléments déjà insérés restent triés. En voici sa description précise :  Considérer le premier élément de la liste. A lui tout seul il constitue une liste triée.  Insérer ensuite le second élément de telle sorte que les deux premiers éléments soient triés.  Continuer ainsi en insérant successivement chaque élément à sa “bonne” place dans la partie déjà triée de la liste. Exemple : Déroulement du tri par insertion sur un exemple On considère la liste suivante de cinq entiers : Insertion du second élément vis-à-vis du premier :  On considère la valeur 3 :  Que l’on retire provisoirement de la liste : Insertion du troisième élément vis-à-vis des deux premiers :  On considère la valeur 1 :  Que l’on retire provisoirement de la liste :
  • 12. 12  On décale le 5  Et on réinsère la valeur 3 :  On décale le 3 et le 5 :  Et on réinsère la valeur 1 : Insertion du quatrième élément vis-à-vis des trois premiers :  On considère la valeur 4 :  Que l’on retire provisoirement de la liste :  On décale le 5 :  Et on réinsère la valeur 4 : Insertion du cinquième élément vis-à-vis des quatre premiers :  On considère la valeur 2 :  Que l’on retire provisoirement de la liste :  On décale le 3, le 4 et le 5 :  Et on réinsère la valeur 2 : Chaque élément a été inséré à sa place, le tri est terminé : 1. Version itérative Tri par insertion Complexité def insertionsort(A): for i in range(len(A)): for k in range(len(A)-1, i, -1 ): if (A[k] < A[k-1]): A[k],A[k-1] = A[k-1],A[k] 2. Version récursive Tri par insertion Complexité def insertionSort(array,i=1): print("array",array) if i >= len(array): return array if array[i-1] > array[i]: temp = array[i] for a in range(0, i): print(array[a]) if temp < array[a]: array.insert(a,temp) del array[i+1] break return insertionSort(array, i+1) L=[4,5,2,1] ; print("Liste non triée : ",L) insertionSort(L); print("Liste triée : ",L) II. Algorithmes récursifs On va présenter dans cette partie les deux algorithmes de tri récursifs les plus connus. Ils utilisent tous les deux le paradigme « diviser pour régner ».
  • 13. 13 A. Tri fusion Le tri fusion consiste à trier récursivement les deux moitiés de la liste, puis à fusionner ces deux sous-listes triées en une seule. La condition d’arrêt à la récursivité sera l’obtention d'une liste à un seul élément, car une telle liste est évidemment déjà triée. Voici donc les trois étapes (diviser, régner et combiner) de cet algorithme : 1. Diviser la liste en deux sous-listes de même taille (à un élément près) en la "coupant" par la moitié. 2. Trier récursivement chacune de ces deux sous-listes. Arrêter la récursion lorsque les listes n'ont plus qu'un seul élément. 3. Fusionner les deux sous-listes triées en une seule. Exemple : Déroulement du tri fusion sur un exemple On considère la liste suivante de sept entiers : On la subdivise en deux sous-listes en la coupant par la moitié : Sous-listes que l’on scinde à leur tour : Sous-listes que l’on scinde à leur tour : Ces sous-listes sont triées car elles n’ont qu’un élément. On va maintenant les fusionner deux par deux en de nouvelles sous-listes triées : De nouveau une étape de fusionnement : Une dernière fusion : On a fusionné toutes les sous-listes obtenues lors des appels récursifs, le tri est terminé :
  • 14. 14 1. Algorithme Algorithme Tri par fusion def tri_fusion(liste): if len(liste)<2: return liste(:) else: milieu = len(liste)//2 listel = tri_fusion(liste[:milieu]) liste2 = tri_fusion(liste[milieu:]) return fusion(listel,liste2) Fonction de fusion def fusion(listel,liste2): liste=[] i, j=0,0 while i<len(listel)and j<len(liste2): if listel[i]<=liste2[j]: liste.append(listel[i]); i+=1 else: liste.append(liste2[j]); j+=1 while i<len(listel): liste.append(listel[i]); i+=1 while j<len(liste2): liste.append(liste2[j]); j+=1 return liste B. Tri rapide Il existe plusieurs algorithmes qui ont une complexité optimale en O(n*log(n)). On remarque cependant qu'en pratique, un de ces tris est souvent plus rapide que les autres. C'est le tri rapide, qui est un des algorithmes de tri les plus adoptés. Le tri rapide est un algorithme récursif. Le principe est de choisir un élément de la liste, que l'on nommera pivot, et de créer deux autres listes : une à gauche du pivot, composée des éléments inférieurs au pivot et une à droite du pivot, composée des éléments supérieurs au pivot. On applique la même méthode pour ces sous- listes et ainsi de suite jusqu'à avoir des listes d'un seul élément. Il existe plusieurs variantes du tri rapide selon la façon de choisir le pivot. Dans notre cas, nous choisirons comme pivot le premier élément de la liste. Voici donc les trois étapes (diviser, régner et combiner) de cet algorithme : 1. Considérer le premier élément de la liste et le positionner à sa place définitive, avec à sa gauche une sous-liste constituée d’éléments qui lui sont inférieurs ou et égaux et à sa droite une sous-liste constituée d’éléments qui lui sont strictement supérieurs. 2. Appliquer récursivement ce même traitement aux deux sous-listes ainsi obtenues. Arrêter la récursion lorsque les listes n'ont plus qu'un seul élément. 3. Pas de résultats à combiner. Exemple : Déroulement du tri rapide sur un exemple On considère la liste suivante de huit entiers : Lors du premier appel récursif, on va positionner le premier élément, à savoir 7, à sa place définitive. Pour faire cela, on va mettre à sa gauche les éléments de la liste qui lui sont inférieurs ou égaux, et à sa droite ceux qui lui sont strictement supérieurs :
  • 15. 15 Lors du second appel récursif, selon le même principe on va placer les éléments 4 et 10 qui sont les premiers éléments des sous-listes créées lors de l'appel précédent : Le placement des éléments 4 et 10 s'est fait à l’intérieur des deux sous-listes délimitées par l'élément 77. On voit bien ici le côté récursif de cet algorithme, puisque l’on applique le même traitement aux deux sous-listes qu’à la liste initiale. Lors du troisième appel récursif, on va placer les éléments 2, 5 et 14 qui sont les premiers éléments des sous-listes créées lors de l'appel précédent : Lors du quatrième appel récursif, on va placer les éléments 12 et 16 qui sont les premiers éléments des sous-listes créées lors de l'appel précédent : Chaque élément a été positionné à sa place, le tri est terminé :
  • 16. 16 1. Algorithme def quickSort(alist): quickSortHelper(alist,0,len(alist)-1) def quickSortHelper(alist,first,last): if first<last: splitpoint = partition(alist,first,last) quickSortHelper(alist,first,splitpoint-1) quickSortHelper(alist,splitpoint+1,last) def partition(alist,first,last): pivotvalue = alist[first] leftmark = first+1 rightmark = last done = False while not done: while leftmark <= rightmark and alist[leftmark] <= pivotvalue: leftmark = leftmark + 1 while alist[rightmark] >= pivotvalue and rightmark >= leftmark: rightmark = rightmark -1 if rightmark < leftmark: done = True else: temp = alist[leftmark] alist[leftmark] = alist[rightmark] alist[rightmark] = temp temp = alist[first] alist[first] = alist[rightmark] alist[rightmark] = temp return rightmark alist = [54,26,93,17,77,31,44,55,20] quickSort(alist) print(alist) III. Comparaison des algorithmes de tri Algorithme de tri Pire cas Cas moyen Meilleur cas (optimal) Sélection n² n² n² Bulle n² n² n Insertion n² n² n Fusion n logn n logn n logn Rapide n² n logn n logn