Le tri rapide est un algorithme de tri généralement efficace de type “diviser pour régner”. Son principe de fonctionnement est le suivant :
- choix d’une valeur pivot — le dernier élément du tableau dans la version de base présentée ici ;
- partitionement du tableau selon cette valeur en deux sous tableaux :
- éléments plus petits (ou plus grands pour un tri décroissant que la valeur pivot dans la partie gauche / inférieure,
- les autres dans éléments dans le partie droite / supérieure ;
- tri récursif des deux sous-tableaux.
A titre de comparaison, le tri fusion fait l’inverse : il découpe d’abord en sous tableaux, puis fusionne.
Opération de partitionnement
Trois variables (en plus du tableau) sont nécessaires pour effectuer le partitionnement :
- la valeur pivot
vp- choisie ici (arbitrairement) au niveau du dernier indice ; - l’indice du pivot
ipqui sert à partager le tableau en deux sous-tableaux ; après la dernière itération, tout ce qui est avantipest plus petit quevp(et ce qui est après plus grand). - l’indice
ide la valeur traitée.
Exemple :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 6 | 1 | 5 | 2 | 2 | 4 |
| i/ip | vp |
vp est initialisée à 4, i et ip à 0.
- A la première itération,
tab[i](3) est comparé àvp(4) ; commeipest égal ài, il n’y a rien d’autre à faire qu’incrémenterip. - A la deuxième itération,
tab[i](6) est comparé àvp(4) ; comme la valeur est plus grande, il n’y a rien à faire. - A la première itération,
tab[i](1) est comparé àvp(4) ; comme elle est plus petite et queip≠i,tab[i]ettab[ip]sont inversés, etipest ensuite incrémenté :- avant :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 6 | 1 | 5 | 2 | 2 | 4 |
| avant | ip | i | vp |
- après :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 1 | 6 | 5 | 2 | 2 | 4 |
| avant | i/ip | vp |
- L’algorithme continue ainsi jusqu’à l’avant dernier indice (le pivot est exclu de la comparaison avec lui même) :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 1 | 2 | 5 | 6 | 2 | 4 |
| avant | ip | i | vp |
Puis :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 1 | 2 | 2 | 6 | 5 | 4 |
| avant | ip | i | vp |
Enfin, la valeur pivot est inversée avec celle de tab[ip] :
| indices | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| valeurs | 3 | 1 | 2 | 2 | 4 | 5 | 6 |
| avant | ip |
La récursion est effectuée sur les sous-tableaux [3, 1, 2, 2] et [5, 6]
(la valeur pivot est à sa place définitive).
L’efficacité en moyenne est on ϴ(n.log2(n)), mais dans le pire cas la complexité
est en O(n²). Cet algorithme n’est donc pas stable.
Le pire cas survient quant la valeur pivot est la plus grande ou petite et que le partage en deux sous tableaux est disproportionné (un seul élément d’un côté, les autres de l’autre).
Cette fonction sert d’enveloppe pour initialiser la sous-fonction récursive.
from typing import List
import math #pour le calcul de la complexité théorique
def triRapide(tab: List[int]) -> List[int]:
"""
trie le tableau selon la méthode du tri rapide, par ordre croissant
@param tab le tableau de valeurs, en entrée-sortie (par adresse)
@return le tableau trié par ordre croissant (pour doctests)
>>> triRapide([])
[]
>>> triRapide([1])
[1]
>>> triRapide([2, 1])
[1, 2]
>>> triRapide([1, 3, 2])
[1, 2, 3]
>>> triRapide([4, 1, 3, 2])
[1, 2, 3, 4]
>>> triRapide([4, 2, 4, 2, 1, 4, 4])
[1, 2, 2, 4, 4, 4, 4]
"""
def _triRapide(tab: List[int], inf: int, sup: int):
cout += 1 #CPLX
if inf < sup - 1: #cas d'arrêt (condition pour continuer)
#partition:
vp = tab[sup - 1]
ip = inf
for i in range(inf, sup - 1):
cout += 1 #CPLX
if tab[i] < vp: #échange
if i != ip: #sauf si c'est inutile (test facultatif)
tab[i], tab[ip] = tab[ip], tab[i]
ip += 1
tab[sup - 1], tab[ip] = tab[ip], tab[sup - 1] #pivot
#division:
_triRapide(tab, inf, ip)
_triRapide(tab, ip + 1, sup)
global cout #CPLX
cout = 0 #CPLX
_triRapide(tab, 0, len(tab))
return tab
if __name__ == "__main__":
import doctest; doctest.testmod()
tab = [4, 7, 4, 8, 5, 9, 6] #meilleur cas
print("Avec tab =", tab)
triRapide(tab)
print(tab)
n = len(tab)
print("- complexité théorique n.log2(n) : " + str(int(n*math.log2(n))))
print("- complexité pire cas : " + str(int((n**2 - n)/2)))
print("- complexité effective : " + str(cout))
tab = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] #déjà trié
print("Avec tab =", tab)
triRapide(tab)
print(tab)
n = len(tab)
print("- complexité théorique n.log2(n) : " + str(int(n*math.log2(n))))
print("- complexité pire cas : " + str(int((n**2 - n)/2)))
print("- complexité effective : " + str(cout))