Vous êtes sur la page 1sur 8

Tri rapide (quicksort)

1 L'algorithme

1.1 Principe

Soit L 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 longueur n.

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

Notons C (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ù :
n (n − 1)
C (n) =
2
donc, complexité en n2 .
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 de n possibles ?

Réponse
n = 2q − 1 ; dans ce cas, le nombre de comparaisons vérie :
 
n−1
C (n) = n − 1 + 2.C
2

Notons uq = C (2q − 1) ; alors uq = 2q − 2 + 2.uq−1 .


Par simple substitution, on obtient
uq = C (n) = 2q (q − 2) + 2

En résumé : C (n) ∼ n. log n.

3.3 En moyenne

On peut montrer que le nombre moyen de comparaisons Cn sur un tableau de taille n vérie :
n−1
2X
Cn = n − 1 + Cj
n j=0

avec C0 = C1 = 0 ; on en tire :
n−1
X j
Cn = 2(n + 1)
j=1
(j + 1) (j + 2)

puis
Cn ∼ 2n. ln n

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 sur N que pour tout arbre binaire à N feuilles,
la hauteur moyenne d'une feuille hm vérie :
log2 (N ) ≤ hm

Remarque préliminaire
En utilisant la convexité de x → x. log2 (x) :
x+y
∀x > 0, ∀y > 0, (x + y) .log2 ≤ x.log2 x + y.log2 y
2

6
Démonstration
Pour N = 1 ou N = 2, c'est clair.
Soit N ≥ 3 et A un arbre binaire à N feuilles.
Ses deux sous-arbres ont N1 et N2 feuilles, avec N = N1 + N2 .
D'après l'hypothèse de récurrence :
log2 (N1 ) ≤ h1 , log2 (N2 ) ≤ h2

D'où :
N1 h1 + N2 h2 1
hm = 1 + ≥1+ (N1 . log2 (N1 ) + N2 . log2 (N2 ))
N N
Donc
1 N1 + N2
hm ≥ 1 + . (N1 + N2 ) .log2 = log2 (N )
N 2

Conséquence
Pour N = n! :
hm ≥ log2 (n!)

Par ailleurs, ˆ n
ln (n!) ≥ ln (t) dt = n.ln (n) − n + 1
1

On en déduit que tout algorithme de tri nécessite en moyenne au moins C.n. ln (n) comparaisons.

5 Un algorithme de tri de complexité linéaire ?

Proposer un algorithme de complexité linéaire (en O (n)),


pour trier un tableau de n entiers, pas nécessairement distincts, contenus dans [0, 3 ∗ n].
Est-ce en contradiction avec ce qui précède ?

7
import random as rd

N = 10

def tri(t):
n = len(t)
compteur = [0 for k in range(3*n + 1)]
for k in range(n):
compteur[t[k]] += 1
indice = 0
for i in range(3*n + 1):
for j in range(compteur[i]):
t[indice] = i
indice += 1

t = [rd.randint(0, 3*N) for k in range(N)]


print(t)
tri(t)
print(t)

Vous aimerez peut-être aussi