Algorithmes de tris
Judicaël Courant 2018-W38-6 (22 septembre)
Table des matières
1 Introduction 2
1.1 Généralités . . . 2
1.2 Trier . . . 2
1.3 Remarques . . . 3
2 Tri par sélection 4 2.1 Algorithme . . . 4
2.2 Implantation en Python . . . 5
2.3 Correction de l’algorithme . . . 6
2.4 Complexité . . . 7
2.5 Conclusion . . . 7
3 Tri par insertion 7 3.1 Algorithme . . . 7
3.2 Implantation en Python . . . 8
3.3 Correction . . . 9
3.4 Complexité . . . 9
3.5 Conclusion . . . 11
4 Tri fusion 11 4.1 Algorithme . . . 11
4.2 Implantation en Python . . . 11
4.3 Correction . . . 12
4.4 Complexité . . . 12
4.5 Conclusion . . . 15
5 Tri par pivot 15 5.1 Algorithme . . . 15
5.2 Implantation en Python . . . 16
5.3 Correction . . . 17
5.4 Complexité . . . 17
5.5 Conclusion . . . 19
1 Introduction
1.1 Généralités
Un problème fondamental
Letriest un problème fondamental de l’algorithmique. En général, son but est de préparer des données pour permettre d’y rechercher rapidement une information.
Motivations
Dans des données triées, il est facile de :
— chercher rapidement si un élément est présent ou non ;
— trouver les doublons ;
— trouver une information associée à une donnée (exemple d’utilisation : index d’un livre) ;
— chercher le k-ième plus grand élément d’un ensemble.
Notion de clé
— Les données qu’on veut trier ne sont pas nécessairement munies d’un ordre total (ca- nonique).
— En général, pour trier des données, on leur appliquera une fonction, appeléeclé de tri oucritère de tri à valeurs dans un ensemble totalement ordonné
— On dira que les données d’un tableau t sont triées (par ordre croissant) si elles sont rangées par ordre croissant des clés.
Exemples
— Tri de données représentant des personnes par nom de famille croissant pour l’ordre lexicographique.
— Tri de données représentant des personnes par date de naissance croissante.
Remarque
Dans la suite de ce document :
— on triera des données déjà munies d’un ordre en Python et on les triera avec cet ordre
— on supposera que la complexité en temps et en espace de la comparaison de deux éléments est constante.
1.2 Trier Cadre
Considérons un algorithme de tri qui prend en entrée un tableau contenant des données u0, . . . , up−1 et :
— soit retourne un nouveau tableau (appelons t0, . . . , tq−1 son contenu) ;
— soit modifie ce tableau (appelons t0, . . . , tq−1 son contenu après l’exécution de l’algo- rithme).
Ce que trier veut dire
Définition 1 (Tri d’un tableau). On dit que l’algorithme a trié le tableau si les deux propriétés suivantes sont vérifiées :
1. test trié par ordre croissant1 de la clé choisie.
2. tetu contiennent les mêmes éléments avec les mêmes multiplicités.
Une caractérisation Proposition
Deux listes de donnéesu0, . . . , up−1 ett0, . . . , tq−1 contiennent les mêmes éléments avec les mêmes multiplicités si et seulement si p =q et il existe une permutation σ :J0, pJ tel que ti=uσ(i) pour touti∈J0, pJ.
Conséquence
Pour montrer qu’un algorithme trie un tableau, il suffit donc de montrer les deux points suivants :
1. Le tableau obtenu est rangé par ordre croissant.
2. Le tableau obtenu est une permutation du tableau initial.
1.3 Remarques Stabilité
Définition 2 (Stabilité d’un tri). Un tri est dit stable s’il ne modifie jamais l’ordre relatif de deux éléments de même clé.
Utilité des tris stables
Permettre de trier par ordre lexicographique de deux clés en triant pour la deuxième puis la première clé.
Question
On trie la liste suivante par ordre lexicographique de nom :
— Dupont, Pierre,
— Martin, Jacques,
— Durand, Jacques,
— Martin, Claude,
— Dupont, Jean.
On obtient :
— Dupont, Pierre,
— Dupont, Jean,
— Durand, Jacques,
— Martin, Claude,
— Martin, Jacques.
1. ou décroissant suivant le contexte.
L’algorithme de tri utilisé était-il stable ?
— A. Oui.
— B. Non (cas des Dupont).
— C. Non (cas des Martin).
— D. On ne peut pas savoir.
Tri en place
Définition 3(Algorithme en place). On dit qu’un algorithme s’effectueen places’il travaille en utilisant au plus un espace mémoire constant (en plus de ses données d’entrée).
NotePar abus de langage, on considère parfois qu’un tri s’effectue en place s’il utilise un faible es- pace mémoire supplémentaire. En particulier, s’il utilise au plus un espace mémoireO(logn) car en pratique log2nne dépasse pas quelques dizaines .
2 Tri par sélection
2.1 Algorithme Principe
— Étant donné un tableau de nvaleursL[0], …,L[n−1], on cherche le minimum : L[i].
On échangeL[0] etL[i].
— On cherche le minimum deL[1],…,L[n−1], et on l’échange avecL[1].
— . . .
— On cherche le minimun de L[k],…,L[n−1], et on l’échange avec L[k].
— . . .
— On cherche le minimum deL[n−2]etL[n−1]et on l’échange avecL[n−2].
Exemple 2 1 5 9 0 4
| {z }
min=0
: min(T[0 :]) = 0 que l’on échange avecT[0] = 2 0 1 5 9 2 4
| {z }
min=1
: min(T[1 :]) = 1qu’on laisse en place 0 1 5 9 2 4
| {z }
min=2
: min(T[2 :]) = 2que l’on échange avec T[2] = 5 0 1 2 9 5 4
| {z }
min=4
: min(T[3 :]) = 4que l’on échange avec T[3] = 9 0 1 2 4 5 9
min=5|{z}
: min(T[4 :]) = 5qu’on laisse en place 0 1 2 4 5 9
min=9|{z}
: min(T[5 :]) = 9qu’on laisse en place.
QCM Stabilité
Le tri par sélection est-il stable ?
En place
S’agit-il d’un tri en place ? 2.2 Implantation en Python Échange d’éléments
def swap(t, i, j):
"""Échange les éléments d'indice i et j dans t, tableau.
Précondition : t tableau et i et j entiers dans l'intervalle range(len(t)).
"""
t[i], t[j] = t[j], t[i]
return
Indice de l’élément minimum d’un sous-tableau def imin(t, i):
"""Retourne j tel que t[j] == min(t[i], ..., t[n-1]) où n est la longueur de t
Précondition : t tableau de taille n et i entier avec 0 <= i < n"""
j = i
for k in range(i+1, len(t)):
# t[j] == min(t[i], ..., t[k-1]) if t[k] < t[j]:
j = k return j
QCML’exécution de imin(t, i), où test un tableau de taillenprend un temps
— A. O(1);
— B. O(n−i);
— C. O(n);
— D. O(n2).
Fonction principale def tri_selection(t):
"""Trie le tableau t par ordre croissant"""
n = len(t)
for i in range(n-1):
# les i premiers éléments de t sont triés
# et inférieurs ou égaux aux autres éléments de t.
j = imin(t, i) swap(t, i, j) return
QCML’exécution de tri_selection(t), où test un tableau de taille nprend un temps
— A. O(n);
— B. O(n(n−1)/2);
— C. O(n(n+ 1)/2);
— D. O(n2).
2.3 Correction de l’algorithme Correction
Proposition
Ce programme est correct : il trie par ordre croissant le tableau t qui lui est passé en argument
Il y a deux points à montrer :
1. Le contenu de t après l’exécution du programme est obtenu par application d’une permutation à son contenu avant l’exécution.
2. Après l’exécution de la procédure,t est trié.
Démonstration
1. Les seules modifications qu’on effectue sur le tableau sont des échanges de deux élé- ments. À la fin du programme on a donc appliqué au tableau la composée d’un nombre fini de transpositions. On a donc simplement permuté les éléments du tableau.
2. On va :
— Montrer que l’invariant de boucle est bien vérifié.
— En déduire que le tableautest trié à la fin de l’exécution.
Justification de l’invariant de boucle
1. Au début du premier tour de boucle (pouri= 0), l’invariant de boucle est vérifié : les 0premiers éléments de tsont triés, inférieurs ou égaux auxn suivants.
2. Si au début du touri, l’invariant est vérifié, notonst00, . . . , t0n−1 les éléments du tableau au début du tour de boucle ett000, . . . , t00n−1 ceux à la fin. Alors :
— (t000, . . . , t00i−1) = (t00, . . . , t0i−1)donc lesipremiers éléments det00sont triés par ordre croissant.
— t00i est le plus petit des élémentst0i, . . . , t0n−1qui sont tous plus grands quet00, . . . , t0i−1, donct000, . . . , t00i est trié par ordre croissant.
— Les éléments de t000, . . . , t00i−1 sont plus petits que ceux de t00i+1, . . . , t00n−1 et t00i est plus petit que t00i+1, . . . , t00n−1 donc les i+ 1 premiers éléments de t00 sont tous plus petits que lesn−i−1autres.
L’invariant est donc vérifié au début du tour suivant.
Conclusion
D’après l’invariant de boucle, à la fin de l’exécution :
— lesn−1 premiers éléments detsont triés
— ils sont plus petits que t[n−1](qui est le nième) Donct est trié.
2.4 Complexité Complexité de imin
— On supposera que les comparaisons d’éléments de tsont des opérations élémentaires.
— Le nombre de d’opérations élémentaires effectuées par imin(t, i)est dominépar le nombre de comparaisons entre éléments det.
— Un appel àimin(t, i) effectuelen(t)-(i+1) comparaisons.
— Donc le temps d’exécution deimin(t, i) est unO(len(t)-i). Complexité de tri_selection
— Le temps d’exécution de tri_selection(t) est dominé par le temps des appels à imin(t, i), car l’appel àswap s’effectue en temps constant.
— Pour un tableau de taille n, il est donc dominé par
n−2
X
i=0
(n−i−1) =
n−1
X
k=1
k= n(n−1) 2 Complexité temporelle du tri par sélection
La complexité en temps de tri_selection(t) pour un tableau t de taille n est donc un O(n2), dans le cas le pire comme dans le cas le meilleur.
Complexité spatiale
— Pas d’appels récursifs : espace de pile utilisé O(1).
— Aucune structure de données auxiliaire : espace mémoire utilisé autre que la pileO(1) Complexité spatiale du tri par sélection
La complexité spatiale du tri par sélection est constante.
2.5 Conclusion
Conclusion sur le tri par sélection
— Le tri par sélection est assez facile à écrire.
— Sa complexité spatiale est constante.
— Il a une complexité temporelleO(n2)dans le cas le pire comme dans le cas le meilleur, il ne permet donc pas de trier de grands tableaux.
— Il ne sait pas tirer parti du fait que les données à trier sont déjà triées, ou presque triées.
3 Tri par insertion
3.1 Algorithme Principe
On procède élément par élément. Si lesipremiers éléments sont triés, on prend le(i+ 1) ième et on l’insère dans le paquet des éléments déjà triés en le faisant glisser vers la gauche et en s’arrêtant dès que :
— on est en première position
— ou l’élément à sa gauche lui est inférieur ou égal
Exemple
D F A G B
D F A G B
D F A G B
A D F G B
A D F G B
A B D F G Stabilité
Le tri par insertion est-il stable ?
— A. Oui.
— B. Non.
3.2 Implantation en Python Position
def position(t, n):
"""t[:n] étant supposé trié, retourne la position à laquelle insérer t[n] dans t[:n].
Précondition: n < len(t)."""
for k in range(n, 0, -1):
# les éléments de t[k:n] sont str. supérieurs à t[n]
if t[k-1] <= t[n]:
return k
# les éléments de t[:n] sont str. supérieurs à t[n]
return 0 Décalage
def decale(t, k, n):
"""Décale les éléments de t[k:n] d'un cran vers la droite. t[k] est remplacé par la valeur de t[n].
Précondition: 0 <= k <= n < len(t)."""
v = t[n]
for i in range(n, k, -1):
t[i] = t[i-1]
t[k] = v Insertion
def insere(t, n):
"""Précondition: t[:n] trié et len(t) >= n+1.
Postcondition: t[:n+1] trié. Les n+1 premiers éléments de t sont préservés mais pas nécessairement leur ordre.
Les autres éléments de t sont inchangés."""
k = position(t, n) decale(t, k, n)
QCMinsere(t, x), où test un tableau de taillen prend un temps
— A. O(1);
— B. O(n);
— C. O(nlogn);
— D. O(n2). Fonction principale def tri_insertion(u):
"""Trie le tableau u par insertion."""
for k in range(1, len(u)):
# invariant : u[:k] est trié.
insere(u, k)
# u est trié.
QCMtri_insertion(t), oùt est un tableau de taillenprend un temps
— A. O(1);
— B. O(n);
— C. O(nlogn);
— D. O(n2). 3.3 Correction
Correction du tri par insertion Le tableau est trié à la fin
C’est une conséquence directe de l’invariant de boucle.
— Cet invariant est vrai avant l’entrée dans la boucle.
— Il est préservé par chaque tour de boucle.
Le tableau final est une permutation du tableau initial
En effet, le tableau n’est modifié que dans la fonction decalage(t, k, n), qui applique une permutation circulaire àt[k:n+ 1].
3.4 Complexité Calcul de la position
— La boucle dansposition(t, n) ne comporte que des opérations élémentaires
— Donc le temps d’exécution de cette fonction est dominé par le nombre de tours de boucle effectués.
— Ce dernier est égal au nombre de comparaisons entre éléments de t, qui est compris entre1 etn.
— Il est aussi égal à n−k où kest la valeur retournée.
Décalage
— Le temps d’exécution de decalage(t, k, n) est dominé par le nombre de tours de boucle.
— Ce nombre vaut n−k.
Insertion
— Dansinsere(t, n), on affecte àkla valeur retournée par l’appel àposition(t, n).
— Le temps de calcul de position(t, n) est dominé par n−k.
— Celui dedecale(t, k, n) aussi.
— Donc le temps de calcul deinsere(t, n)est dominé par le nombre de comparaisons effectuées dansposition(t, n).
Complexité du tri
— Le temps d’exécution de tri_insertion(t) sur un tableau t de taille n est donc dominé par le nombre de comparaisons effectuées.
NotonsC(t) ce nombre de comparaisons.
Cas le pire
C(t)≤
n−1
X
k=1
= n(n−1) 2
Cette inégalité est une égalité pour tout tableautinitialement trié par ordre décroissant.
Conclusion
La complexité temporelle du tri par insertion dans le cas le pire sur un tableau de taillen estO(n).
Cas le meilleur
C(t)≥
n−1
X
k=1
=n−1
Cette inégalité est une égalité pour tout tableau tinitialement trié par ordre croissant.
Conclusion
La complexité temporelle du tri par insertion dans le cas le meilleur sur un tableau de taille nest O(n).
Remarque
Cela se produit lorsque le tableau est trié mais aussi lorqu’il est presque trié, à l’exception de kvaleurs, avec kborné indépendamment den.
Complexité spatiale
— Pas d’appels récursifs : espace de pile utilisé O(1).
— Aucune structure de données auxiliaire : espace mémoire utilisé (autre que la pile) : O(1)
Complexité spatiale du tri par insertion
La complexité spatiale du tri par insertion est constante.
3.5 Conclusion
Conclusion sur le tri par insertion
— Simple à écrire et à justifier.
— Sans doute le tri naïf le plus efficace : travaille en tempslinéairelorsque le tableau est trié ou presque trié (nombre fini d’éléments mal placés).
— Complexité temporelle quadratique et spatiale constante dans le cas le pire.
Tri recommandé pour trier quelques dizaines d’éléments.
4 Tri fusion
4.1 Algorithme Principe
Le principe est de partager le tableau en deux moitiés, de les trier récursivement, puis de fusionner ces deux moitiés triées de façon à obtenir un tableau trié.
QCM Stabilité
Est-ce un tri stable ? En place
Est-ce un tri en place ?
4.2 Implantation en Python Fusion de deux tableaux triés def fusion(t1, t2):
"""Retourne un tableau t trié de mêmes élts que t1+t2.
Précondition : t1 et t2 triés par ordre croissant."""
n1, n2 = len(t1), len(t2) n = n1 + n2; t = n * [None]
i1, i2 = 0, 0 for k in range(n):
# t[:k] trié, d'éléments ceux de t1[:i1] + t2[:i2]
# tous inf. ou égaux à ceux de t1[i1:] et t2[i2:]
if i2 >= n2 or (i1 < n1 and t1[i1] <= t2[i2]):
t[k] = t1[i1]; i1 += 1
else:
t[k] = t2[i2]; i2 += 1 return t
Fonction principale def tri_fusion(t):
"""Retourne une copie de t triée par ordre croissant."""
n = len(t) if n <= 1:
return t.copy() k = n // 2
return fusion(tri_fusion(t[:k]), tri_fusion(t[k:])) 4.3 Correction
Correction de la fusion
C’est une conséquence immédiate de l’invariant de boucle.
— Cet invariant est vrai à l’entrée dans la boucle
— Il est préservé par chaque tour de boucle.
Correction de la fonction principale
On montre, par récurrence forte, que pour tout entier n, elle trie tous les tableaux de taille n:
— C’est évident sur les tableaux de taille 0et1.
— Soitn≥2,tun tableau de taillen. Supposons qu’elle est correcte sur tous les tableaux strictement plus petits. Alors en posantk=bn/2c, on ak < netn−k=dn/2e< n.
Lorsqu’on exécutetri_fusion(t), les deux appels récursifs se font sur des tableaux strictement plus petits que t. Par hypothèse de récurrence, ils retournent donc des tableaux triés contenant les mêmes éléments quet[:k]ett[k:]. La fusion de ces deux tableaux est donc triée et contient les mêmes éléments quet[:k] +t[k:], donc que t.
4.4 Complexité Complexité
L’opération de fusion de deux tableaux de longueurs n1 etn2 demande un tempsO(n1+ n2)dans le cas le pire comme dans le cas le meilleur.
Notons P(n) (resp. M(n)) le temps d’exécution du tri fusion sur un tableau de taille n dans le cas le pire (resp. le meilleur).
Inégalité de complexité
P(n)≤ O
n→+∞(n) +P(bn/2c) +P(dn/2e)
Croissance de la complexité Rappel de l’inégalité :
P(n)≤ O
n→+∞(n) +P(bn/2c) +P(dn/2e)
Pour résoudre l’inéquation surP, le mieux est de voir ce qu’il se passe pourn= 2k, puis de dire qu’on aP(n)≤P(2dlog2ne)… sauf que rien ne dit que P est croissante !
Pour s’en sortir, deux possibilités :
— Faire l’hypothèse explicitementque P est croissante (c’est mieux que ne rien dire)
— Noter P0(n) le temps d’exécution du tri fusion sur un tableau de taille inférieure ou égaleà ndans le cas le pire. P0 est alors croissante (pourquoi ?)
Croissance de la complexité (bis) Si nous prenons la deuxième solution.
Pour un entier n fixé, P0(n) est le temps d’exécution d’un tableau tn de taille kn avec kn≤n.
Alors on a
P0(n)≤ O
n→+∞(kn) +P0(bkn/2c) +P0(dkn/2e) Inégalité de complexité
P0(n)≤ O
n→+∞(n) +P0(bn/2c) +P0(dn/2e) Autrement dit,P0 est croissante et vérifie la même inégalité queP. Résolution : cas simples
Pour résoudre
P0(n)≤ O
n→+∞(n) +P0(bn/2c) +P0(dn/2e)
On constate que le cas des puissances de2doit être plus simple, donc on poseuk=P0(2k).
Inégalité pour les puissances de 2 On en déduit, pourk au voisinage de+∞ :
uk≤ O
k→+∞(2k) + 2uk−1
Équation homogène
C’est une inégalité du type :
uk−2uk−1 ≤ O
k→+∞(2k) Équation homogène associée
∀k∈N∗ uk = 2uk−1
dont une solution est 2k
k∈N.
Variation de la constante Inégalité :
uk−2uk−1 ≤ O
k→+∞(2k) Une solution de l’équation homogène associée : 2k
k∈N. Méthode de variation de la constante
On pose pour tout k∈N, λk =uk/2k (méthode de variation de la constante) et on divise l’inégalité précédente par2k. On obtient :
λk−λk−1 ≤ O
k→+∞(1) Variation de la constante (fin)
λk−λ0≤
k
X
i=1
(λi−λi−1)
≤ O
k→+∞(k)(k) (cours sur les séries)
etλk ≥0, doncλk=O(k). D’oùuk=λk2k=O(k2k). Solution du cas général
P0(n)≤P0(2dlog2ne) =udlog
2ne=O(dlog2ne2dlog2ne) =O(nlogn) CommeP est à valeurs positives, on en déduit :
Complexité temporelle du tri fusion dans le cas le pire P(n) =O(nlogn)
Cas le meilleur
Dans le cas le meilleur, on peut de la même façon montrer que le temps d’exécution est M(n) = Ω(nlogn).
Remarque
M(n) = Ω(nlogn) etM(n)≤P(n)
etP(n) =O(nlogn)
D’où M(n) = Θ(nlogn) etP(n) = Θ(nlogn)
4.5 Conclusion
Conclusion sur le tri fusion Complexité temporelle
Dans le cas le meilleur comme dans le cas dans le pire, le tri fusion demande un temps d’exécution O(nlogn), où nest la longueur du tableau à trier.
Complexité spatiale (admise)
La complexité spatiale du tri fusion sur un tableau de taille n est O(n) : car on a besoin d’espace mémoire pour stocker les morceaux de tableau et pour effectuer la fusion.
NoteLe tri fusion est le tri à implanter si vous cherchez un algorithme plus efficace que les tris naïfs et que vous ne voulez pas y passer trop de temps.
Remarque 1 : Timsort
La fonctionsortedde Python utilise une version améliorée du tri fusion appeléeTimsort.
Remarque 2 : tri fusion itératif Celui-ci consiste à fusionner
— les éléments d’indices2ket2k+ 1, le tableau se trouve ainsi composé de sous-tableaux de taille2 commençant à l’indice2k, pour toutk, et tous triés.
— les tableaux d’indices 4k,4k+ 1 avec ceux d’indices 4k+ 2,4k+ 3, pour obtenir des sous-tableaux de taille4commençant à chaque indice de la forme 4k
— etc.
Facile à écrire si le tableau est de taille 2p, sinon il convient de faire attention pour les derniers éléments.
Remarque 3 : tri fusion externe
Le tri fusion est très utile pour un tableau tellement gros qu’il ne peut tenir en mémoire vive :
— on stocke le tableau dans un fichier ;
— on découpe le fichier en morceaux qui tiennent en RAM ;
— on trie chaque morceau (et on le stocke dans un fichier) ;
— on fusionne deux-à-deux les morceaux (on lit les premiers octets des fichiers à trier et on écrit dans un fichier résultat) pour obtenir des fichiers de taille double ;
— on fusionne deux à deux ces fichiers de taille double pour obtenir des fichiers de taille quadruple ;
— etc.
5 Tri par pivot
5.1 Algorithme Introduction
— Aussi appelé tri rapideou quicksort.
— Inventé par Tony Hoare en 19602.
— Toujours un des tris les plus rapides en pratique aujourd’hui…
— … à condition de l’optimiser de façon subtile.
— En pratique, il est préférable d’utiliser notre tri fusion plutôt que notre tri par pivot naïf.
Principe
Étant donné un tableautetgetddeux indices avec0≤g≤d<len(t),tri_rapide(t, g, d)va trier la portion du tableau d’indices appartenant àJg,dJ.
Sid−g≤1, il n’y a rien à faire. Sinon :
— On choisit une valeurvdu tableau appeléepivotet on réordonne la portion du tableau à trier de façon à ce que :
1. Le pivot arrive à une positioni∈Jg,dJ.
2. Toutes les valeurs strictement plus petite que v sont avant l’indice i, les autres après.
On dit qu’on apartitionnéla partie à trier.
— On trie récursivement les portions du tableau d’indices appartenant àJg, iJetJi+ 1,dJ.
Algorithme de partition
On parcourt le sous-tableau à partitionner de la gauche vers la droite de façon à avoir, à chaque itération :
— le pivot và l’indiceg;
— des valeurs strictement plus petites quevpour les indices de Jg+ 1,mJ;
— des valeurs supérieures ou égales à vpour les indices de Jm,iJ;
— des valeurs qui restent à répartir pour les indices de Ji,dJ.
(cf dessin et exercice du drapeau hollandais) QCM
Stabilité
Est-ce un tri stable ? En place
Est-ce un tri en place ?
5.2 Implantation en Python Implantation de la fonction partition def partition(t, g, d):
assert g < d v = t[g]
m = g+1
for i in range(g+1, d):
2. Hoare a aussi inventé les notions de pré-condition, post-condition et d’invariant de boucle.
# en g on a v, en range(g+1,m), des valeurs < v
# en range(m, i), des valeurs >= v if t[i] < v:
swap(t, i, m) m += 1
# replacer v si au moins une valeur est < v : swap(t, g, m-1) # ne fait rien si m == g+1.
# on retourne la position finale du pivot return m-1
Programme principal Le tri rapide s’écrit alors : def tri_rapide_rec(t, g, d):
if d - g <= 1: return i = partition(t, g, d) tri_rapide_rec(t, g, i) tri_rapide_rec(t, i+1, d) def tri_rapide(t):
tri_rapide_rec(t, 0, len(t)) 5.3 Correction
Correction de la fonction de partition
C’est une conséquence immédiate de l’invariant de boucle.
— Cet invariant est vrai à l’entrée dans la boucle
— Il est préservé par chaque tour de boucle.
Correction de tri_rapide_rec
— Évidente pour les sous-tableaux de taille 0ou 1.
— Soitn≥2, etgetddélimitant un sous-tableau de taillen. Supposonstri_rapide_rec sur tous les sous-tableaux strictement plus petits. Alors après l’exécution de i = partition(t, g, d), on a g≤i < det :
1. tous les éléments de t[g:i]sont inférieurs ou égaux à t[i]
2. tous ceux det[i+ 1 :d]sont supérieurs ou égaux.
De plus,i−g < d−g=netd−(i+ 1)< d−i≤d−g=n. Donc, par hypothèse de récurrence, les appels récursifs trient respectivement les sous-tableaux de t d’indices dans Jg, iJ et Ji+ 1, dJ. À l’issue de ces appels récursifs, les propriétés 1. et 2. sont encore vérifiées par ces sous-tableaux et comme ils sont de plus triés,t[g :d] est trié et possède les mêmes éléments qu’initialement.
5.4 Complexité
Domination par le nombre de comparaisons
— Comme pour les tris par insertion et par sélection, la complexité temporelle est domi- née par le nombre de comparaisons effectuées.
— L’appel àpartition(t, g, d)effectue toujoursd−g−1comparaisons et partitionne le sous-tableau à trier en deux sous-tableaux de taillep etq avec p+q=d−g−1.
Cas le pire
— On peut montrer que, pour un sous tableau de longueur n, le cas où un des sous- tableaux construit par la partition est vide et l’autre de longueur n−1 est un pire
— C’est notamment le cas si le tableau de départ ne contient que des éléments égaux.cas.
Remarquons que le nombre de comparaisons effectuées par tri_rapide_recpour un tableau trié de taillenne dépend pas du contenu du tableau mais juste denet notons C(n) ce nombre.
∀n∈N∗ C(n) =n−1 +C(n−1) D’où∀n∈N∗ C(n)−C(0) =
n
X
k=1
(k−1) =n(n−1)/2 =O(n2)
Cas le pire (bis)
Le cas où le tableau initial est trié par ordre décroissant constitue également un cas où la complexité est O(n2).
Exercice
Le démontrer (il faudra s’intéresser à deux appels récursifs successifs pour comprendre).
Cas le meilleur
— À l’inverse, le cas où le sous-tableau est coupé en deux moitiés de tailles à peu près égales est un cas le meilleur.
— En notant C0(n) le nombre de comparaisons effectuées dans le meilleur des cas pour un tableau de taillen, on peut montrer
∀n∈N∗ C0(n) =n−1 +C0
n−1 2
+C0
n−1 2
— On peut en déduire C0(n) =O(nlogn) (commencer par s’intéresser aux tableaux de taille 2k−1).
Conclusion (provisoire)
— Dans le cas le pire : O(n2). Ce cas se produit notamment lorsque tous les éléments sont égaux ou lorsqu’ils sont triés par ordre croissant ou décroissant.
— Dans le cas le meilleur :O(nlogn)(en pratique souvent plus rapide que le tri fusion).
— En moyenne3 : O(nlogn) (en pratique souvent plus rapide que le tri fusion) ; écart- typeO(n).
3. Espérance du temps de calcul pour des donnéesdistinctes, mélangées aléatoirement de façon équipro- bable (résultat hors-programme).
Réparer les défauts
1. Mieux choisir le pivot. Deux méthodes classiques :
a) La médiane du premier élément, du dernier élément et de l’élément du milieu du tableau (idéal si le tableau est déjà trié, pas mauvais en général sinon).
b) En prendre un au hasard dans le tableau (théoriquement efficace mais très coû- teux en pratique).
2. Partitionnement trois voies : les éléments strictement plus petits que le pivot, les strictement plus grands et les égaux.
Complexité avec ces améliorations Avec le partitionnement trois voies :
— O(nlogn)en moyenne pour une permutation aléatoire de données, non nécessairement distinctes.
Avec le pivot aléatoire :
— O(nlogn) en moyenne pour des données fixées toutes distinctes.
Avec pivot aléatoire et partitionnement trois voies :
— O(nlogn) en moyenne pour des données fixées non nécessairement distinctes.
Dans tous les cas :
— O(nlogn) dans le cas le meilleur ;
— O(n2) dans le cas le pire.
Complexité en espace
— C’est la profondeur maximale des appels récursifs.
— O(logn) dans le cas le meilleur.
— O(n)dans le cas le pire. Peut être ramenée à O(logn) grâce à une idée due à R. Sed- gewick4.
5.5 Conclusion
Conclusion sur le tri par pivot
— Souvent préféré au tri par fusion pour son efficacité pratique.
— L’implanter correctement est très délicat (cas enn2).
— À éviter sur des données d’origine mal intentionnée.
Complexité temporelle du tri par pivot
— O(nlogn) dans le cas le meilleur et en moyenne.
— O(n2) dans le cas le pire.
Complexité spatiale du tri par pivot
— Dans le cas le meilleur : O(logn)
— Dans le cas le pire : O(n) pour la version naïve, O(logn) avec l’optimisation de Sed- gewick.
4. Pour cela, après avoir partitionné, on commence par trier (récursivement) la partition la plus petite avant de trier l’autre,sansappel récursif. Voir le livreInformatique pour tous en CPGEpour plus de détails.