Les algorithmes de tri

publicité
POIRET Aurélien
Algorithmique
MPSI
FFFF
Chapitre No VII : Les algorithmes de tri
Table des matières
1 - Le tri à bulles
.....................................................................
2
1.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 - L’algorithme du tri à bulles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 - Le tri par insertion
..............................................................
4
2.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.3 - L’algorithme du tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 - Le tri rapide
......................................................................
5
3.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
3.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 - L’algorithme de tri rapide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
3.4 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
3.5 - Choix du pivot et complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.5.1 - Pivot arbitraire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.5.2 - Pivot aléatoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.5.3 - Pivot Optimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
4 - Le tri fusion
.......................................................................
8
4.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
4.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4.3 - L’algorithme de tri fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
9
4.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5 - Récapitulatif
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Dans ce chapitre, on présente divers algorithmes de tri en donnant leur principe, leur syntaxe
Pythonienne et leurs complexités.
1
Le tri à bulles
1.1
Présentation
Le tri à bulles ou tri par propagation est un algorithme de tri qui consiste à faire remonter progressivement les plus grands éléments d’un tableau, comme les bulles d’air remontent à la surface d’un
liquide.
L’intérêt du tri à bulles est que son son principe est simple. Cependant, sa complexité en moyenne est
de l’ordre de n2 (où n est la taille du tableau), ce qui le classe parmi les mauvais algorithmes de tri.
Il n’est donc quasiment pas utilisé en pratique.
1.2
Description de l’algorithme
L’algorithme parcourt le tableau, et compare les couples d’éléments successifs. Lorsque deux éléments successifs ne sont pas dans l’ordre croissant, ils sont échangés. Après chaque parcours complet
du tableau, l’algorithme recommence l’opération. Lorsqu’aucun échange n’a lieu pendant un parcours,
cela signifie que le tableau est trié. On arrête alors l’algorithme.
1.3
L’algorithme du tri à bulles
Entrées : T
n ← longueur(T )
echange ← Vraie
Tant que (n > 0 ET echange) faire
echange ← Faux
Pour j de 0 à n − 2 faire
Si (T [j] > T [j + 1]) alors
T [j + 1] ↔ T [j]
echange ← Vraie
Fin Si
Fin Pour
n ← n−1
Fait
Retourner T
Algorithme 1: Algorithme du tri à bulles
2
1.4
Étude sur un exemple
Prenons la liste de chiffres [5, 1, 4, 2, 8] et trions-la de manière croissante en utilisant l’algorithme
de tri à bulles.
Première étape :
[5, 1, 4, 2, 8] → [1, 5, 4, 2, 8]
vertit.
[1, 5, 4, 2, 8] → [1, 4, 5, 2, 8]
[1, 4, 5, 2, 8] → [1, 4, 2, 5, 8]
[1, 4, 2, 5, 8] → [1, 4, 2, 5, 8]
Les éléments 5 et 1 sont comparés, et comme 5 > 1, l’algorithme les interInterversion car 5 > 4.
Interversion car 5 > 2.
Comme 5 < 8, les éléments ne sont pas échangés.
Deuxième étape :
[1, 4, 2, 5, 8] → [1, 4, 2, 5, 8] Même principe qu’à l’étape 1.
[1, 4, 2, 5, 8] → [1, 2, 4, 5, 8]
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
À ce stade, la liste est triée, mais pour le détecter, l’algorithme doit effectuer un dernier parcours.
Troisième étape :
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
[1, 2, 4, 5, 8] → [1, 2, 4, 5, 8]
Comme la liste est triée, aucune interversion n’a lieu à cette étape, ce qui provoque l’arrêt de l’algorithme.
1.5
L’algorithme en langage Python
# Tri à bulles
def tri_bulles(T):
n=len(T)
echange=True
while n>0 and echange:
echange=False
for j in range(0,n-1):
(T[j],T[j+1])=(T[j+1],T[j])
echange=True
n=n-1
return T
1.6
Complexité
Pour un tableau de taille n, le nombre d’itérations de la boucle externe est compris entre 1 et n.
En effet, on peut se convainc facilement qu’après la i-ème étape, les i derniers éléments du tableau
sont à leur place. À chaque itération, il y a exactement n − 1 comparaisons et au plus n − 1 échanges.
Le pire cas, n itérations, est atteint lorsque le plus petit élément est à la fin du tableau. La complexité
est alors Θ(n2 ).
Le meilleur cas, une seule itération, est atteint quand le tableau est déjà trié. Dans ce cas, la complexité
est linéaire, c’est-à-dire, Θ(n).
En moyenne, on peut montrer que la complexité est aussi Θ(n2 ).
3
2
Le tri par insertion
2.1
Présentation
Le tri par insertion est un algorithme de tri classique, que la plupart des personnes utilisent
naturellement pour trier des cartes : prendre les cartes mélangées une à une sur la table, et former
une main en insérant chaque carte à sa place.
En général, le tri par insertion est beaucoup plus lent que d’autres algorithmes comme le tri rapide et
le tri fusion pour traiter de grandes séquences car sa complexité asymptotique est quadratique.
Le tri par insertion est cependant considéré comme le tri le plus efficace sur des entrées de petite taille.
Il est aussi très rapide lorsque les données sont déjà presque triées.
Le tri par insertion est un tri stable (conservant l’ordre d’apparition des éléments égaux) et un tri en
place (il n’utilise pas de tableau auxiliaire).
2.2
Description de l’algorithme
Dans l’algorithme, on parcourt le tableau à trier du début à la fin. Au moment où on considère le
i-ème élément, les éléments qui le précèdent sont déjà triés. Pour faire l’analogie avec l’exemple du jeu
de cartes, lorsqu’on est à la i-ème étape du parcours, le i-ème élément est la carte saisie, les éléments
précédents sont la main triée et les éléments suivants correspondent aux cartes encore mélangées sur
la table.
L’objectif d’une étape est d’insérer le i-ème élément à sa place parmi ceux qui précèdent. Il faut pour
cela trouver où l’élément doit être inséré en le comparant aux autres, puis décaler les éléments afin de
pouvoir effectuer l’insertion. En pratique, ces deux actions sont fréquemment effectuées en une passe,
qui consiste à faire « remonter » l’élément au fur et à mesure jusqu’à rencontrer un élément plus petit.
2.3
L’algorithme du tri par insertion
Entrées : T
n ←longueur(T )
Pour i de 1 à n − 1 faire
x ← T [i]
j←i
Tant que (j > 0 ET T [j − 1] > x) faire
T [j] ← T [j − 1]
j ←j−1
Fait
T [j] ← x
Fin Pour
Retourner T
Algorithme 2: Algorithme du tri par insertion
4
2.4
Étude sur un exemple
Voici les étapes de l’exécution du tri par insertion sur le tableau T=[9,6,1,4,8] Le tableau est
représenté au début et à la fin de chaque itération.
i=1
i=2
i=3
i=4
2.5
T=[9,6,1,4,8]
T=[6,9,1,4,8]
T=[1,6,9,4,8]
T=[1,4,6,9,8]
→
→
→
→
T=[6,9,1,4,8]
T=[1,6,9,4,8]
T=[1,4,6,9,8]
T=[1,4,6,8,9]
L’algorithme en langage Python
# Tri par par insertion
def tri_insertion(T):
for i in range(1,len(T)):
x=T[i]
j=i
while j>0 and T[j-1]>x:
T[j]=T[j-1]
j=j-1
T[j]=x
return T
2.6
Complexité
La complexité du tri par insertion est en Θ(n2 ) dans le pire cas et en moyenne et en O(n) linéaire
dans le meilleur cas.
Plus précisément,
— Dans le pire cas, atteint lorsque le tableau est trié à l’envers, l’algorithme effectue de l’ordre de
n2
2 affectations et comparaisons.
— Si le tableau est déjà trié, il y a n − 1 comparaisons et O(n) affectations.
— La complexité du tri par insertion reste linéaire si le tableau est presque trié (par exemple,
chaque élément est à une distance bornée de la position où il devrait être, ou bien tous les
éléments sauf un nombre borné sont à leur place). Dans cette situation particulière, le tri par
insertion surpasse d’autres méthodes de tri : par exemple, le tri fusion et le tri rapide (avec
choix aléatoire du pivot) sont tous les deux en Θ(n ln(n)) même sur une liste triée.
3
3.1
Le tri rapide
Présentation
Le tri rapide (en anglais quicksort) est un algorithme de tri fondé sur la méthode de conception
diviser pour régner. C’est un tri en place mais non stable.
La complexité moyenne du tri rapide pour n éléments est proportionnelle à n ln(n), ce qui est optimal
pour un tri par comparaison, mais la complexité dans le pire des cas est quadratique. Malgré ce
désavantage théorique, c’est en pratique un des tris les plus rapides, et donc un des plus utilisés. Le
pire cas est en effet peu probable lorsque l’algorithme est correctement mis en œuvre.
Le tri rapide ne peut cependant pas tirer avantage du fait que l’entrée est déjà presque triée. Dans ce
cas particulier, il est plus avantageux d’utiliser le tri par insertion.
5
3.2
Description de l’algorithme
La méthode consiste à placer un élément du tableau (appelé pivot) à sa place définitive, en permutant tous les éléments de telle sorte que tous ceux qui sont inférieurs au pivot soient à sa gauche
et que tous ceux qui sont supérieurs au pivot soient à sa droite.
Cette opération s’appelle le partitionnement. Pour chacun des sous-tableaux, on définit un nouveau
pivot et on répète l’opération de partitionnement. Ce processus est répété récursivement, jusqu’à ce
que l’ensemble des éléments soit trié.
Concrètement, pour partitionner un sous-tableau :
— on place le pivot à la fin (arbitrairement), en l’échangeant avec le dernier élément du soustableau ;
— on place tous les éléments inférieurs au pivot en début du sous-tableau ;
— on place le pivot à la fin des éléments déplacés.
3.3
L’algorithme de tri rapide
Fonction Partitionner( T , premier , dernier , pivot )
T [pivot] ↔ T [dernier]
pivot← premier
Pour i de premier à dernier−1 faire
Si ( T [i] <= T [dernier] ) alors
T [i] ↔ T [pivot]
pivot←pivot+1
Fin Si
Fin Pour
T [dernier] ↔ T [pivot]
Retourner T ,pivot
Fin
Fonction Trirap( T , premier , dernier )
Si (premier<dernier) alors
pivot←choixpivot( T , premier, dernier )
T ,pivot←partitionner( T , premier, dernier, pivot)
Trirap( T , premier, pivot−1 )
Trirap( T , pivot+1 , dernier)
Fin Si
Retourner T
Fin
Fonction Trirapide( T )
premier← 0
dernier←longueur(T )
Retourner Trirap( T , premier, dernier )
Fin
Algorithme 3: Algorithme du tri rapide
3.4
L’algorithme en langage Python
# Tri rapide
def partitionner(T,pivot,premier,dernier):
(T[pivot],T[dernier])=(T[dernier],T[pivot])
6
pivot=premier
for i in range(premier,dernier):
if T[i]<=T[dernier]:
(T[i],T[pivot])=(T[pivot],T[i])
pivot=pivot+1
(T[dernier],T[pivot])=(T[pivot],T[dernier])
return T,pivot
def trirap(T,premier,dernier):
if premier<dernier:
pivot=(premier+dernier)//2
[T,pivot]=partitionner(T,pivot,premier,dernier)
trirap(T,premier,pivot-1)
trirap(T,pivot+1,dernier)
return T
def tri_rapide(T):
trirap(T,0,len(T)-1)
return T
3.5
3.5.1
Choix du pivot et complexité
Pivot arbitraire
Une manière simple de choisir le pivot est de prendre toujours le premier élément du sous-tableau
courant (ou le dernier). Lorsque toutes les permutations possibles des entrées sont équiprobables, la
complexité moyenne du tri rapide en sélectionnant le pivot de cette façon est Θ(n ln(n)). Cependant,
la complexité dans le pire cas est Θ(n2 ), et celle-ci est atteinte lorsque l’entrée est déjà triée ou presque
triée.
Si on prend comme pivot le milieu du tableau, le résultat est identique, bien que les entrées problématiques soient différentes.
Il est possible d’appliquer une permutation aléatoire au tableau pour éviter que l’algorithme soit systématiquement lent sur certaines entrées. Cependant, cette technique est généralement moins efficace
que de choisir le pivot aléatoirement.
3.5.2
Pivot aléatoire
Si l’on choisit le pivot aléatoirement de manière uniforme parmi tous les éléments, alors la complexité moyenne du tri rapide est Θ(n ln(n)) sur n’importe quelle entrée. Dans le pire cas, la complexité
est Θ(n2 ). Néanmoins, l’écart type de la complexité est seulement Θ(n), ce qui signifie que l’algorithme
s’écarte peu du temps d’exécution moyen.
Dans le meilleur cas, l’algorithme est en Θ(n ln(n)).
3.5.3
Pivot Optimal
En théorie, on pourrait faire en sorte que la complexité de l’algorithme soit Θ(n ln(n)) dans le
pire cas en prenant comme pivot la valeur médiane du sous-tableau. Mais l’algorithme n’est pas
suffisamment efficace dans la pratique et cette variante est peu utilisée.
7
4
4.1
Le tri fusion
Présentation
Le tri fusion est un algorithme de tri par comparaison stable. Sa complexité temporelle pour une
entrée de taille n est de l’ordre de n ln n, ce qui est asymptotiquement optimal. Ce tri est basé sur
la technique algorithmique diviser pour régner. L’opération principale de l’algorithme est la fusion,
qui consiste à réunir deux listes triées en une seule. L’efficacité de l’algorithme vient du fait que deux
listes triées peuvent être fusionnées en temps linéaire.
4.2
Description de l’algorithme
L’algorithme est naturellement décrit récursivement.
— On coupe en deux parties à peu près égales les données à trier.
— On trie les données de chaque partie (pour cela, on coupe chaque partie en deux et on trie
chacune)
— On fusionne les deux parties
La récursivité s’arrête car on finit par arriver à des listes composées d’un seul élément et le tri est
alors trivial.
8
4.3
L’algorithme de tri fusion
Fonction Insert( element , liste )
Si (liste = liste vide) alors
Retourner liste formée par l’élément element
Sinon, si (element < premier élément de liste) alors
Retourner liste formée par les éléments element + liste
Sinon
Retourner liste formée par premier élément de liste + Insert(element , liste excepté son
premier élément)
Fin Si
Fin
Fonction Fusion( liste1 , liste2 )
Si (liste1 = liste vide) alors
Retourner liste2
Sinon, si (liste2 = liste vide) alors
Retourner liste1
Sinon
Retourner Fusion(liste1 sans son premier élément , insert(premier élément liste1 , liste2))
Fin Si
Fin
Fonction Trifusion( liste )
n ←longueur(T )
Si (n = 0 OU n = 1) alors
Retourner liste
Sinon
Retourner fusion( Trifusion(Première moitié liste) , Trifusion(Seconde moitié liste)))
Fin Si
Fin
Algorithme 4: Algorithme du tri fusion
9
4.4
Étude sur un exemple
4.5
L’algorithme en langage Python
# Tri fusion
def insert(element,liste):
if liste==[]:
return [element]
elif element<=liste[0]:
return [element]+liste
else:
return [liste[0]]+insert(element,liste[1:])
def fusion(liste_1,liste_2):
if liste_1==[]:
return liste_2
elif liste_2==[]:
return liste_1
else:
return fusion(liste_1[1:len(liste_1)],insert(liste_1[0],liste_2))
def tri_fusion(T):
n=len(T)
if n==0 or n==1:
return T
else:
return fusion(tri_fusion(T[0:n//2]),tri_fusion(T[n//2:]))
4.6
2T
Complexité
La complexité T (n) du tri fusion dans le pire cas pour une entrée de taille n vérifie T (n) =
n
1
2 + n. Comme 2 = 2 , on peut affirmer que T (n) = Θ(n ln(n)).
10
5
Récapitulatif
On peut résumer les différents résultats dans le tableau suivant :
Complexité dans
le meilleur des cas
Complexité
dans le pire des cas
Complexité
en moyenne
Rapidité sur de petites
listes presque triées
Tri
stable
Tri
en place
Tri
à bulles
Tri par
insertion
Tri rapide
pivot arbitraire
Tri rapide
pivot optimal
Tri
fusion
Θ(n)
O(n)
Θ(n ln(n))
Θ(n ln(n))
Θ(n ln(n))
Θ(n2 )
Θ(n2 )
Θ(n2 )
Θ(n ln(n))
Θ(n ln(n))
Θ(n2 )
Θ(n2 )
Θ(n ln(n))
Θ(n ln(n))
Θ(n ln(n))
OUI
OUI
NON
NON
NON
OUI
OUI
NON
NON
OUI
OUI
OUI
OUI
OUI
NON
11
Téléchargement