Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri...

7
IFT2015 :: H20 Miklós Cs ˝ urös 10 février 2020 8. Algorithmes de tri interne L E TRI est une tâche fondamentale en informatique. On a un fichier d’élé- ments avec clés comparables — on veut les ranger selon l’ordre les clés. Clés comparables en Java : public interface Comparable<T> { int compareTo(T autre _ objet); } x.compareTo(y) retourne une valeur négative si x précède y, ou positive si x suit y, selon l’ordre «naturel» des éléments. L’interface Comparator<T> permet de spéci- fier un ordre quelconque entre les instances d’une classe T. package java.util; public interface Comparator<T> { int compare(T un, T deux); } Tri externe et interne. Un algorithme de tri externe sert à ordonner les élé- ments d’un fichier stocké partiellement ou entièrement en mémoire externe (disque). Il existe des algorithmes spécialisés pour trier des fichiers trop grand pour la mémoire vive : en une telle application, on veut minimiser l’accès à mémoire externe. Le tri interne se fait sur un fichier stocké entièrement en mémoire vive. Le plus souvent, on considère le tri d’un tableau A[0..n - 1] ; parfois, on peut adapter un tel algorithme aux listes chaînées aussi. Temps de calcul. On caractérise le temps de calcul d’un algorithme de tri par le nombre d’opérations de comparaison entre éléments et d’échange d’élé- ments. En une caractérisation plus générale d’un tri de tableau, on compte le nombre d’accès aux cellules. Espace de travail. Un aspect important d’efficacité est l’usage de mémoire pendant le tri. Noter que cela inclut toutes les variables locales sur la pile d’appel dans récurrences. Un algorithme en place utilise O(log n) mémoire à part du fichier d’entrée. (En particulier, un tel algorithme ne crée pas de copies du tableau à trier.) Nos tris Méthode de tri interne liste chaînée temps de calcul espace de travail tri par sélection (selection sort) oui quadratique (toujours) constante — en place tri par insertion (insertion sort) oui quadratique (pire/moyen), linéaire (meilleur) constante — en place tri fusion (Mergesort) oui linéarithmique (toujours) linéaire tri par tas (Heapsort) non linéarithmique (toujours) constante — en place tri rapide (Quicksort) non linéarithmique (meilleur/moyen), quadratique (pire) logarithmique — en place borne inférieure linéarithmique

Transcript of Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri...

Page 1: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

IFT2015 :: H20 Miklós Csurös 10 février 2020

8. Algorithmes de tri interne

LE TRI est une tâche fondamentale en informatique. On a un fichier d’élé-ments avec clés comparables — on veut les ranger selon l’ordre les clés. Cléscomparables en Java :

public interface Comparable<T>

{

int compareTo(T autre_objet);

}

x.compareTo(y) retourne unevaleur négative si x précède y, oupositive si x suit y, selon l’ordre«naturel» des éléments.

L’interface Comparator<T> permet de spéci-fier un ordre quelconque entre les instancesd’une classe T.

package java.util;

public interface Comparator<T>

{

int compare(T un, T deux);

}

Tri externe et interne. Un algorithme de tri externe sert à ordonner les élé-ments d’un fichier stocké partiellement ou entièrement en mémoire externe(disque). Il existe des algorithmes spécialisés pour trier des fichiers trop grandpour la mémoire vive : en une telle application, on veut minimiser l’accès àmémoire externe. Le tri interne se fait sur un fichier stocké entièrement enmémoire vive. Le plus souvent, on considère le tri d’un tableau A[0..n− 1] ;parfois, on peut adapter un tel algorithme aux listes chaînées aussi.

Temps de calcul. On caractérise le temps de calcul d’un algorithme de tri parle nombre d’opérations de comparaison entre éléments et d’échange d’élé-ments. En une caractérisation plus générale d’un tri de tableau, on compte lenombre d’accès aux cellules.

Espace de travail. Un aspect important d’efficacité est l’usage de mémoirependant le tri. Noter que cela inclut toutes les variables locales sur la piled’appel dans récurrences. Un algorithme en place utilise O(log n) mémoireà part du fichier d’entrée. (En particulier, un tel algorithme ne crée pas decopies du tableau à trier.)

Nos tris

Méthode de tri interne liste chaînée temps de calcul espace de travailtri par sélection (selection sort) oui quadratique (toujours) constante — en placetri par insertion (insertion sort) oui quadratique (pire/moyen), linéaire (meilleur) constante — en placetri fusion (Mergesort) oui linéarithmique (toujours) linéairetri par tas (Heapsort) non linéarithmique (toujours) constante — en placetri rapide (Quicksort) non linéarithmique (meilleur/moyen), quadratique (pire) logarithmique — en placeborne inférieure linéarithmique

Page 2: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 2

8.1 Tri fusion

Le tri fusion 1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

une procédure récursive.

FIGURE 1: John von Neumann (1903–1957), inventeur de MergeSort.

Algo MERGESORT(A[0..n− 1], g, d) // premier appel avec g = 0, d = n// récursion pour trier le sous-tableau A[g..d− 1]

M1 if d− g < 2 then return // cas de base : tableau vide ou un seul élémentM2 m←

⌊(d + g)/2

⌋// m est au milieu

M3 MERGESORT(A, g, m) // trier partie gaucheM4 MERGESORT(A, m, d) // trier partie droiteM5 FUSION(A, g, m, d) // fusion des résultats

Fusion de sous-tableaux. Typiquement, on utilise un seul tableau auxi-liaire aux[0..n − 1] pour faire la fusion. Avec un arrangement bitonique,on peut simplifier les conditions dans la boucle.

4 6 8 9 1 2 5 7

4 6 8 9 7 5 2 1

A

aux

trié trié

bitonique

FIGURE 2: Arrangement bitonique dans letableau auxiliaire de fusion.

Algo FUSION(A[ ], g, m, d) // fusion pour A[g..m− 1] et A[m..d− 1]F1 for i← g, g + 1, . . . , m− 1 do aux[i]← A[i]F2 for j← m, m + 1, . . . , d− 1 do aux[m + d− 1− j]← A[j]F3 i← g ; j← d− 1 ; k← gF4 while k < d do // meilleure condition : i < mF5 if aux[i] ≤ aux[j] then A[k]← aux[i] ; i← i + 1; k← k + 1F6 else A[k]← aux[j] ; j← j− 1; k← k + 1

Tri hybride. En pratique, le tri fusion performe le mieux dans une approche

hybride : la récursion est trop couteuse pour les petits sous-tableaux, et le tripar insertion est plus rapide. Pour cette raison, il vaut implanter le tri fusionavec un seuil ` > 1 sur les petits sous-tableaux. On passe les sous-tableaux detaille d− g < ` en Ligne M1 directement à un tri par insertion.

tri fusion / mergesort tri rapide / quicksort

9 6 3 0 2 1 8 7 5 4

9 6 3 0 2 1 7 5 4

division 50-50% sans réarrangement

récursion

0 2 3 6 9 1

récursion

4 5 7 8

fusionner en O(n)

8

n/2 éléments n/2 éléments

0 1 2 3 4 5 6 7 8 9

partition en O(n)

≤ p ≥ p

p

récursion récursion

aucune combinaison nécessaire

FIGURE 3: Démarches du tri fusion et dutri rapide. Le tri fusion utilise la logique de«diviser pour régner» : le tableau est diviséen deux sous-tableaux (en temps O(1)) quisont triés ensuite dans des appels récursifs,et on combine les résultats (fusion) en untemps linéaire. En tri rapide, on choisit unpivot p, et à l’aide des échanges d’éléments,on place les éléments inférieurs à p à lagauche, et ceux supérieurs à p à la droite dutableau. Après une telle partition, on peutprocéder avec des appels récursifs aux deuxsous-tableaux gauche et droit. Noter que lepivot n’est pas nécessairement la médiane :les sous-tableaux gauche et droit résultantspeuvent avoir des tailles très différentes.

Page 3: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 3

8.2 Tri rapide

Tri binaire

Supposons qu’il y a juste deux clés possibles (0 et 1) dans un tableau à trier.Alors on peut performer le tri en un temps linéaire à l’aide de deux indices quibalayent à partir des deux côtés vers le milieu. 0 0 1 0 0 1 0 1 1 1

i j

échanger

FIGURE 4: Tri binaire.

TRI01(A[0..n− 1]) // tri binaire

B1 i← 0 ; j← n− 1B2 loop

B3 while i < j && A[i] = 0 do i← i + 1B4 while i < j && A[j] = 1 do j← j− 1B5 if i < j then échanger A[i]↔ A[j]B6 else return

Tri rapide(fr):tri rapide

L’idée principale est la partition autour d’un pivot. Les éléments plus petitsou plus grands sont triés antre eux par récursion. La partition même suit lalogique du tri binaire (v. Figure 5) Afin de trier un tableau A[0..n − 1] enordre croissant, on exécute QUICKSORT(A, 0, n− 1). C’est un tri en place(v. discussion de la profondeur de la pile d’exécution plus tard).

4 2 8 1 3 9 1 7 2 6 8 5pivot = 5

4 2 8 1 3 51 7 2 6 89

4 2 2 1 3 51 7 8 6 89

4 2 2 1 3 59 7 8 6 81

4 2 2 1 3 95 7 8 6 81

échanger

échanger

fin de balayer

remettre le pivot

i j

≤5

4 2 2 1 3 1 97 8 6 8

≥5

partition complète

FIGURE 5: Démarche de PARTITION.

Algo QUICKSORT(A[0..n− 1], g, d) // tri de A[g..d− 1]Q1 if d− g ≤ 1 then return // cas de base

Q2 i← PARTITION(A, g, d) // pivot à l’indice iQ3 QUICKSORT(A, g, i)Q4 QUICKSORT(A, i + 1, d)

Algo PARTITION(A, g, d) // partition de A[g..d− 1]P1 chosir le pivot p← A[d− 1]P2 i← g− 1 ; j← d− 1P3 loop // condition terminale à P6

P4 do i← i + 1 while A[i] < pP5 do j← j− 1 while j > i et A[j] > pP6 if i ≥ j then sortir de la boucle (continuer à P8)

P7 échanger A[i]↔ A[j]P8 échanger A[i]↔ A[d− 1] // mettre le pivot en place

P9 return i

FIGURE 6: Tony Hoare (1934–), inventeurde QuickSort

Performances

Soit m = d − g, le nombre des éléments dans le sous-tableau à trier,avec m > 1. La partition (Lignes P3–P7) se fait par (m− 1) comparaisons. Lenombre de comparaisons est donc

C(m) = (m− 1) + C(i) + C(m− 1− i).

La récurrence dépend de la case finale i du pivot : v. Table 1. Le pire cas, avectemps quadratique, est celui d’un tableau trié à l’entrée ! Le temps moyen (et

Page 4: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 4

typique) est linéarithmique.

pivot i récurrence C(n) solution C(n)Meilleur cas (n− 1)/2 2 · C

((n− 1)/2

)+ n− 1 ∼ n lg n

Pire cas 0, n− 1 C(n− 1) + (n− 1) ∼ n2/2Moyen cas aléatoire EC(n) = 2EC(i) + (n− 1) ∼ 2n ln n

TABLE 1: Récurrences pour le nombre decomparaisons C(n) dans le tri rapide selon laposition du pivot. EC dénote l’espérance dela variable aléatoire C.

Génie algorithmique

Petits sous-tableaux. Le tri par insertion est plus rapide que quicksort quandd − g est petit (g ≥ d − `∗ avec `∗ = 5..20). En Ligne Q1, c’est mieuxdonc de faire le tri par insertion pour tels petits tableaux. En fait, on peutjuste ignorer les petits sous-tableaux entièrement (retourner si g ≥ d − `∗

en Ligne Q1). À la fin, il faut lancer tri par insertion sur le tableau entier quiprendra alors un temps linéaire (car chaque élément doit être déplacé par ≤ `∗

cases).

Choix du pivot. Deux choix performent très bien en pratique : médiane oualéatoire. Le pivotage aléatoire produit le même résultat comme si l’ordreinitial était une permutation uniforme (moyen cas de pivotage simple).

Médiane de trois Aléatoire

P1.1 if d ≥ g + 2 then

P1.2 if A[g] > A[d− 2] then échanger A[g]↔ A[d− 2]P1.3 if A[d− 1] > A[d− 2] then échanger A[d− 1]↔ A[d− 2]P1.4 if A[g] > A[d− 1] then échanger A[g]↔ A[d− 1]P1.5 p← A[d− 1] // A[g] ≤ A[d− 1] ≤ A[d− 2]

// rnd(k) : pseudo-aléatoire 0, 1, . . . , k− 1.P1.1 k← g + rnd(d− g)P1.2 p← A[k]P1.3 A[k]← A[d− 1]P1.4 A[d− 1]← p

et on se sert des sentinelles qui sont maintenant en place à A[g], A[d− 2] :

P2’ i← g; j← d− 2P5’ do j← j− 1 while A[j] > p

Profondeur de la pile d’exécution. En une implantation efficace, on se sert de laposition terminale du deuxième appel récursif (Ligne Q4).

Algo QUICKSORT_ITER(A[0..n− 1], g, d) // tri de A[g..d− 1]QI1 while d− g > 1 do

QI2 i← PARTITION(A, g, d)QI3 QUICKSORT_ITER(A, g, i)QI4 g← i + 1 // boucler au lieu de l’appel récursif

La profondeur de la pile d’exécution dépend donc du nombre d’appelsrécursifs en Ligne QI3 ce qui est ∼ n au pire (p.e., on a i = d− 1 toujours).On peut facilement modifier le code pour toujours faire l’appel récursif avec

Page 5: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 5

le plus court entre A[g..i− 1] et A[i + 1..d− 1] qui assure que la profondeurmaximale est logarithmique (car la taille est < n/2 à chaque appel récursif).

Moyen cas de QuickSort

Lemme 8.1. Soit D(n) l’espérance du nombre de comparaisons lors du tri de néléments avec pivotage aléatoire. Alors,

D(n) = 2(n + 1)Hn − 4n

où Hn = ∑ni=1 i−1 est le n-ème nombre harmonique.

Démonstration. On a D(0) = D(1) = 0. Autrement, supposons que le pivot est le i-ème plus grand élément de A.Le pivot est comparé à (n − 1) autre éléments pour la partition. Les deux partitions sont de tailles i − 1 et (n − i).Comme i prend les valeurs 1, 2, . . . , n avec la même probabilité, on a la récurrence numérique

D(n) = n− 1 +1n

n−1

∑i=0

(D(i) + D(n− 1− i)

)= n− 1 +

2n

n−1

∑i=0

D(i).

En comparant les récurrences pour D(n) et D(n− 1) :

nD(n)− (n− 1)D(n− 1) =(

n(n− 1) + 2n−1

∑i=0

D(i))−

((n− 1)(n− 2) + 2

n−2

∑i=0

D(i))

= 2(n− 1) + 2D(n− 1).

D’où on aD(n)n + 1

=D(n− 1)

n+

2n− 2n(n + 1)

=D(n− 1)

n+

4n + 1

− 2n

,

avec la solutionD(n)n + 1

=n+1

∑j=2

4j−

n

∑j=1

2j= 2

n

∑j=1

1j− 4 +

4n + 1

,

donc D(n) = 2(n + 1)Hn − 4n. �

Théorème 8.2. Le tri rapide fait C(n) ∼ 2n ln n ≈ 1.39n lg n comparaisons enmoyenne pour trier n éléments.

Démonstration. On utilise Hn ∼ ln n avec Lemme 8.1. �

8.3 Permutation aléatoire en place

Au lieu de choisir le pivot au hasard, on peut randomiser l’ordre au début,et procéder par pivotage simple déterministe. L’algorithme suivant met leséléments d’un tableau dans un ordre aléatoire en place, selon la distributionuniforme sur toutes (n!) permutations.

SHUFFLE(A[0..n− 1]) // met les éléments de A dans un ordre aléatoire

U1 for i← 0, 1, . . . , n− 1 do

U2 j← i + rnd(n− i) // rnd(m) entier pseudo-aléatoire sur 0..m− 1U3 échanger A[i]↔ A[j] //j ∈ {i, i + 1, . . . , n− 1}

Page 6: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 6

8.4 QuickSelect

Supposons qu’on veut trouver le k-ème plus petit élément dans un ta-bleau A[0..n− 1]. Il existe des algorithmes qui le font en temps linéaire mêmeau pire cas. Ici, on se sert plutôt de la partition autour d’un pivot pour ache-ver un temps de calcul linéaire en moyen cas (voir Théorème 8.3 ci-bas), maisquadratique au pire. En pratique, l’algorithme par partition est souvent plusperformant que l’algorithme avec un temps linéaire théoriquement guaranti.

Idée de clé : après avoir appellé i ← PARTITION(A, 0, n), on trouvele k-ème élément en A[0..i − 1] si k < i ou en A[i + 1..n − 1] si k > i.En même temps, on réorganise le tableau pour que A[k] soit le k-ème pluspetit élément. Comme c’est une récursion terminale, on peut transformer lecode en forme itérative très facilement.

Algo SELECTION(A[0..n− 1], g, d, k)S1 if d− g ≤ 2 then // cas de base : 1 ou 2 éléments

S2 if d = g + 2 et A[d− 1] < A[g] then échanger A[g]↔ A[d− 1] // 2 éléments

S3 return A[k]S4 i← PARTITION(A, g, d)S5 if k = i then return A[k] // on l’a trouvé

S6 if k < i then return SELECTION(A, g, i, k) // continuer à la gauche

S7 if k > i then return SELECTION(A, i + 1, d, k) // continuer à la droite

Théorème 8.3. Avec un pivot aléatoire, algorithme SELECTION fait ∼ 2ncomparaisons en moyenne.

8.5 Le minimum de comparaisons dans un tri

Un tri par comparaison n’utilise que des comparaisons entre les élémentsd’un tableau (genre A[i] < A[j]) pour le trier. L’exécution de l’algorithmene dépend que l’ordre initial du tableau. On imagine l’arbre de décision quimontre la séquence de comparaisons pour toute exécution possible. Un nœudinterne de l’arbre contient une comparaison entre les éléments de A ; il a tou-jours deux enfants qui correspondent au branchement de l’exécution selon lerésultat de la comparaison. Tout nœud externe correspond à une permutationce qui est l’ordre final des éléments. La Figure 7 montre l’exemple du tri parinsertion de A[0..3].

Page 7: Algorithmes de tri interne - WordPress.com · ALGORITHMES DE TRI INTERNE 2 8.1Tri fusion Le tri fusion1 (mergesort) utilise le principe de diviser pour régner dans 1. (fr):tri fusion

ALGORITHMES DE TRI INTERNE 7

A[0]<A[1]

non1023

oui

i = 1, j=0

i = 2, j=1;A'[1]<A'[2]

i = 2, j=0;A'[0]<A'[1]

i = 3, j=2;A'[2]<A'[3]

i = 3, j=1;A'[1]<A'[2]

i = 3, j=0;A'[0]<A'[1]

A[0]<A[2]

non1203

A[1]<A[2]

non2103

A[0]<A[3] A[2]<A[3]

oui 1023

non

A[1]<A[3]

2130

non

A[2]<A[3]

2310

non

3210

oui

2310

oui

2130

oui

2103

A[0]<A[3]

oui 1203

non

A[2]<A[3]

1230

non

A[1]<A[3]

1320

non

3120

oui

1320

oui

1230

oui

1203

non

A[0]<A[3]

1032

non

A[1]<A[3]

1302

non

3102

oui

1302

oui

1032

oui

1023

A[1]<A[2]

0123

non

oui

2301

non

0213

A[0]<A[2]

non2013

A[1]<A[3] A[2]<A[3]

0123

non

A[0]<A[3]

2031

non

A[2]<A[3]

3201

oui

2301

oui

2031

oui

2013

A[1]<A[3]

oui 0213

non

A[2]<A[3]

0231

non

A[0]<A[3]

0321

non

3021

oui

0321

oui

0231

oui

0213

non

A[1]<A[3]

0132

non

A[0]<A[3]

0312

non

3012

oui

0312

oui

0132

oui

0123

i = 2, j=1;A'[1]<A'[2]

i = 2, j=0;A'[0]<A'[1]

i = 3, j=2;A'[2]<A'[3]

i = 3, j=1;A'[1]<A'[2]

i = 3, j=0;A'[0]<A'[1]

d=6 d=6 d=5 d=4 d=6 d=6 d=5 d=4 d=5 d=5 d=4 d=3

d=6 d=6 d=5 d=4 d=6 d=6 d=5 d=4 d=5 d=5 d=4 d=3

FIGURE 7: Arbre de décision du tripar insertion (version qui échangeles éléments à chaque comparaison)appliqué à 4 éléments. Les nœudsinternes montrent les indices originauxdes éléments. A′ est le tableau réarrangépendant l’exécution, le résultat deséchanges sur l’ordre initial est indiquéà chaque branche (p.e., à l’échec de lapremière comparaison A[0] < A[1],on échange les deux qui donne l’ordre1023 sur la branche “non”). C’est unarbre de hauteur 6 — l’algorithmeutilise 6 comparaisons au pire. Laprofondeur des nœuds externes (d =)est entre 3 (meilleur cas) et 6 (pire cas).

Théorème 8.4. Pour tout algorithme déterministe de tri par comparaison, il existeun arrangement initial de n éléments distincts qui prend au moins lg(n!) ∼ n lg ncomparaisons à trier.

Démonstration. Par définition, chaque chemin de la racine jusqu’à un nœud externe correspondà la séquence de comparaisons performées pour établir l’ordre final. Le tableau doit être trié à lasortie, et chaque ordre est possible : il faut avoir au moins un nœud externe pour toute permu-tation. Le nombre de nœuds externes dans cet arbre binaire est donc ≥ n!. En conséquence, lahauteur de l’arbre est au moins dlg(n!)e (voir Théorème 5.2). Dans d’autres mots, il existe unordre à l’entrée pour lequel l’algorithme fait au moins dlg(n!)e comparaisons. Pour la croissanceasymptotique, on se sert de la formule de Stirling

n! ∼√

2πn( n

e

)n

Alors

lg(n!) ∼ n lg n− n lg e +12

lg n + lg√

2π ∼ n lg n.