Tri rapide (quicksort)
1 L'algorithme
1.1 Principe
SoitL une liste à trier.
- On choisit par exemple le premier élément de la liste (le pivot).
- On sépare la liste en deux autour du pivot, à sa gauche les éléments plus petits, à sa droite les éléments plus grands.
- Ensuite on trie indépendamment les deux sous-listes.
1.2 Premier programme
Avec des listes auxiliaires.
1.3 Tri rapide en place
Le programme précédent utilise des listes auxiliaires.
On va voir ici qu'on peut eectuer un 'tri en place', qui n'utilise que très peu de mémoire.
On vérie au passage que seulement
n−1
comparaisons sont eectuées par la fonction partition sur une liste de longueurn.
1.4 Que trier ?
L'algorithme ne s'applique pas qu'à des listes de nombres.
Il sut que l'ensemble soit muni d'une relation d'ordre total : chaînes de caractères pour l'ordre lexicographique par exemple.
1
def tri_rapide(L):
if L == []:
return([]) a = L[0]
L1 = [k for k in L[1:] if k <= a]
L2 = [k for k in L[1:] if k > a]
return(tri_rapide(L1) + [a] + tri_rapide(L2))
def partition(t):
n, pivot = len(t), t[0]
limite = 1
for indice in range(1, n):
if t[indice] < pivot:
t[indice], t[limite] = t[limite], t[indice]
limite += 1
t[0], t[limite - 1] = t[limite - 1], t[0]
'''La fonction partition sépare les éléments de t en fonction du pivot.
Invariants, vérifiés à l'entrée de la boucle : 1 <= limite <= indice
t[: limite] ne contient que des éléments inférieurs ou égaux au pivot t[0]
t[limite : indice] ne contient que des éléments supérieurs ou égaux au pivot.'''
1
# tri rapide en place
'''La fonction partition sépare les éléments de t[debut:fin] en deux à l'aide du pivot.
Invariants, vérifiés à l'entrée de la boucle : 1 + debut <= limite <= k
t[debut : limite] ne contient que des éléments inférieurs ou égaux au pivot t[debut]
t[limite : k] ne contient que des éléments supérieurs ou égaux au pivot.'''
def partition(t, debut, fin):
pivot = t[debut]
limite = 1 + debut
for k in range(1 + debut, fin):
if t[k] < pivot:
t[k], t[limite] = t[limite], t[k]
limite += 1
t[debut],t[limite - 1] = t[limite - 1],t[debut]
return limite - 1 # retourne la nouvelle # position du pivot
def tri_rapide_en_place(t, debut, fin):
if fin > 1 + debut:
pos_pivot = partition(t, debut, fin) tri_rapide_en_place(t, debut, pos_pivot) tri_rapide_en_place(t, 1 + pos_pivot, fin) def tri(L):
tri_rapide_en_place(L, 0, len(L))
1
2 Le tri rapide avec une pile
4
def partition(t, debut, fin):
pivot = t[debut]
limite = 1 + debut
for k in range(1 + debut, fin):
if t[k] < pivot:
t[k], t[limite] = t[limite], t[k]
limite += 1
t[debut], t[limite - 1] = t[limite - 1], t[debut]
return limite - 1 # retourne la nouvelle # position du pivot def tri_rapide_a_mains_nues(t):
n = len(t)
tache = [0,n] # intervalle à trier pile_des_taches = [tache]
while pile_des_taches != []:
tache = pile_des_taches.pop()
[debut, fin] = tache # intervalle à trier if fin > 1 + debut:
pos_pivot = partition(t, debut, fin)
pile_des_taches.append([debut, pos_pivot]) pile_des_taches.append([1 + pos_pivot, fin])
1
3 Complexité
3.1 Cas défavorable
NotonsC(n)le nombre de comparaisons à eectuer entre éléments de la liste.
Liste déjà triée ;C(n) =n−1 +C(n−1); d'où : C(n) =n(n−1)
2 donc, complexité enn2.
Dans ce cas, le tri rapide est moins ecace que le tri par insertion.
Il est possible de diminuer le risque en choisissant un pivot aléatoire.
3.2 Cas favorable
Le cas où à chaque étape, le tableau se sépare miraculeusement en deux tableaux de même longueur.
Quelles sont les valeurs denpossibles ? Réponse
n= 2q−1; dans ce cas, le nombre de comparaisons vérie : C(n) =n−1 + 2.C
n−1 2
Notonsuq =C(2q−1) ; alorsuq = 2q−2 + 2.uq−1. Par simple substitution, on obtient
uq =C(n) = 2q(q−2) + 2
En résumé : C(n)∼n.logn.
3.3 En moyenne
On peut montrer que le nombre moyen de comparaisonsCn sur un tableau de taillenvérie :
Cn =n−1 + 2 n
n−1
X
j=0
Cj
avecC0=C1= 0 ; on en tire :
Cn= 2(n+ 1)
n−1
X
j=1
j (j+ 1) (j+ 2) puis
Cn∼2n.lnn
4 Complément : complexité dans le cas général
On appelle hauteur d'un noeud la longueur du chemin de la racine à ce noeud.
On montre par récurrence surN que pour tout arbre binaire àN feuilles, la hauteur moyenne d'une feuillehmvérie :
log2(N)≤hm
Remarque préliminaire
En utilisant la convexité dex→x.log2(x):
∀x >0,∀y >0,(x+y).log2x+y
2 ≤x.log2x+y.log2y
6
Démonstration
PourN = 1ouN = 2, c'est clair.
SoitN ≥3 etAun arbre binaire àN feuilles.
Ses deux sous-arbres ontN1et N2 feuilles, avecN =N1+N2. D'après l'hypothèse de récurrence :
log2(N1)≤h1,log2(N2)≤h2 D'où :
hm= 1 +N1h1+N2h2
N ≥1 + 1
N (N1.log2(N1) +N2.log2(N2)) Donc
hm≥1 + 1
N.(N1+N2).log2N1+N2
2 = log2(N) Conséquence
PourN =n!:
hm≥log2(n!) Par ailleurs,
ln (n!)≥ ˆ n
1
ln (t) dt=n.ln (n)−n+ 1
On en déduit que tout algorithme de tri nécessite en moyenne au moinsC.n.ln (n)comparaisons.
5 Un algorithme de tri de complexité linéaire ?
Proposer un algorithme de complexité linéaire (enO(n)),
pour trier un tableau denentiers, pas nécessairement distincts, contenus dans[0,3∗n]. Est-ce en contradiction avec ce qui précède ?
7