Structures de données IFT-2000 Abder Alikacem Semaine 14 Les algorithmes de tri Département d’informatique et de génie logiciel Édition septembre 2009
Plan Définitions et propriétés Les algorithmes de tri simples et leur complexité Les algorithmes de tri complexes et leur complexité Le tri rapide (QuickSort) Le tri-fusion (MergeSort) Le tri par monceau (HeapSort)
Traitement de données triées Le traitement de données déjà triées est plus rapide. Nécessaire pour le traitement séquentiel d’intervalles. Il faut trier des données à bas coût ! nb de comparaisons nb de déplacements d’éléments espace mémoire requis Besoin : attribut de tri (clé) :, que l’on peut comparer { } (c.-à-d. ordre total) déplacer toute l’information : ou, + indirection vers l’information
Critères d'analyse Complexité en temps : 2 critères de base nombre de comparaisons entre clés nombre de transferts (ou d’échanges) d'éléments Complexité en place mémoire : nécessaire au tri tri sur place (in situ) : en O(1) tri à côté : en O(n) tri récursif : utilise une pile Stabilité conserve l'ordre originel pour éléments de clés égales Progressivité à l'étape k trie les k premiers éléments de la liste Configuration particulière des données : aléatoires, triées, inversées, égales, presque triées
Les algorithme de tri Les algorithmes de tri «classiques»: Tris élémentaires tri insertion; tri sélection; Etc.. Tris dichotomiques tri rapide (Quick Sort); tri fusion; Tris par structure tri par arbre (Tree Sort); tri par monceau (Heap Sort) tri pigeonnier (Pigeon Hole). tri basique Tri aléatoire … et le Bogo tri
Méthodes simples de tri Intérêts pédagogiques (terminologie et mécanismes fondamentaux) Tris simples plus efficaces sur des petites listes (moins de 50 éléments) sur des listes presque triées sur des listes contenant un grand nombre de clés égales Certains tris complexes sont améliorés en faisant appel à des tris simples sur une partie des données
Tris simples Algorithme#1 : Tri par insertion (trace en classe) insérer chacun des n éléments à la bonne place dans le tableau créé jusqu’ici 44,55,12,42,94,18,06,67
Tris simples Tri par insertion template void TriInsertion( T Tableau[], int n) { int j; T Tmp; for (int i=1; i<n; i++ ) { //Placer l'element courant dans la variable tampon Tmp = Tableau[i]; j=i; //Tant que la position d'insertion n'est pas atteinte... while ( (--j>=0) && (Tmp<Tableau[j]) ) Tableau[j+1]=Tableau[j]; Tableau[j+1]=Tmp; }
Tris simples Tri par insertion dichotomique : Idée au lieu de parcourir les i premiers éléments triés pour faire l'insertion du i + 1 ième, rechercher la place où insère par dichotomie. Complexité nb de transferts est identique à celui de l'insertion séquentielle O(n 2 ) dans le pire des cas et en moyenne nb de comparaisons en O(n log n) (au pire et en moyenne) En pratique : n'améliore pas vraiment les performances
Tris simples Tri par insertion : insérer chacun des n éléments à la bonne place dans le tableau créé jusqu’ici … tri stable? tri in situ? progressif? Meilleur cas : séquence triée nb de comparaisons ? O(n) O(n log n) nb de déplacements ? O(1) Pire cas : séquence triée inverse nb de comparaisons ? O(n 2 ) O(n log n) nb de déplacements ? O(n 2 )
Tri shell, Donald Shell Une variante du tri par insertion Lenteur du tri par insertion les échanges ne portent que sur des voisins si le plus petit élément est à la fin il faut n étapes pour le placer définitivement Principe du tri shell éliminer les plus grands désordres pour abréger le travail aux étapes suivantes Idée ordonner des séries d'éléments éloignés de h, h prenant de valeurs de plus en plus petites (jusqu'à 1) Exemple (suite déterminée empiriquement) h n = (1093, 384, 121, 40, 13, 4, 1) H n+1 = 3 h n +1 Tris simples
Tri shell template void triShell(T Tableau[], int n) { … while (p <= n) { p = (3 * p) + 1; } while (p > 0) { …. p = p/3; }
Tri shell On a démontré que la complexité dans le pire des cas est O(n 3/2 ). En choisissant pour suite d'incréments 1, 3, 7, 15, 31 ( h i+1 = 2h i + 1) on obtient expérimentalement une complexité en O(n 1,2 ). Très efficace jusqu'à 5000 éléments que les données soient aléatoires, triées ou en ordre inverse, code facile et compact, conseillé quand on n'a aucune raison d'en choisir un autre. Tris simples tri stable? tri in situ? progressif?
reste à classer MIN échange Tris simples Recherche du minimum par balayage séquentiel Algorithme#2 : Tri par sélection remplir les cases de 1 à n avec le minimum des éléments du tableau restant
Tris simples Tri par sélection (trace en classe): 44,55,12,42,94,18,06,67 Algorithme : remplir les cases de 1 à n avec le minimum des éléments du tableau restant
Tri par sélection template template void TriSelection(T Tableau[], int n) { int min; int min; T Tmp; T Tmp; for (int i=0 ; i<n-1 ; i++ ) { for (int i=0 ; i<n-1 ; i++ ) { //Recherche de l'indice du plus petit element //Recherche de l'indice du plus petit element min = i; min = i; for (int j=i+1 ; j<n ; j++ ) { for (int j=i+1 ; j<n ; j++ ) { if ( Tableau[j]<Tableau[min] ) if ( Tableau[j]<Tableau[min] ) min = j; min = j; } //Permutation des elements //Permutation des elements Tmp = Tableau[i]; Tmp = Tableau[i]; Tableau[i] = Tableau[min]; Tableau[i] = Tableau[min]; Tableau[min] = Tmp; Tableau[min] = Tmp; }} Tris simples
Tri par sélection remplir les cases de 1 à n avec le minimum des éléments du tableau restant Meilleur cas : séquence triée nb de comparaisons ? O(n 2 ) nb de déplacements ? O(1) Pire cas : séquence triée inverse nb de comparaisons ? O(n 2 ) nb de déplacements ? O(n) tri in situ ? progressif ? stable ? Stable? 21,17,21,5,2,9,2,7
Tris simples Les balayages peuvent être éliminés Algorithme#3 : Tri à bulles (« Bubblesort ») remonter les plus petites valeurs à la surface, et ce, à chaque itération
Tris simples Tri à bulles (« Bubblesort ») : trace en classe 44,55,12,42,94,18,06,67 Algorithme : remonter les plus petites bulles à la surface, et ce, à chaque itération
Tri à bulles (« Bubblesort ») template template void TriBulle( T Tableau[], int n) { T Tmp; for (int i=0; i<n-1; i++) for (int j=n-1; j>i; j--) if ( Tableau[j-1]> Tableau[j]) { Tmp = Tableau[j-1]; Tableau[j-1] = Tableau[j]; Tableau[j] = Tmp; }} Tris simples
Tri à bulles (« Bubblesort ») remonter les plus petites bulles à la surface, et ce, à chaque itération Meilleur cas : séquence triée nb de comparaisons ? O(n 2 ) nb de déplacements ? O(1) Pire cas : séquence triée inverse nb de comparaisons ? O(n 2 ) nb de déplacements ? O(n 2 ) tri stable? tri in situ? progressif?
Tris simples Tri à bulles (« Bubblesort ») Première amélioration Une première amélioration consiste à arrêter le processus s'il n'y a aucun échange dans une passe. Deuxième amélioration : ShakerSort Une deuxième amélioration consiste à alterner le sens des passes. Troisième amélioration Une troisième qui consiste à reprendre au début chaque fois qu’il y a une permutation.
Complexité en temps : en O(n 2 ) en moyenne et au pire des cas en nombre de transferts ou en nombre de comparaisons en O(n²). en espace : en O(1) (tris sur place) Sauf tri shell ne conviennent pas pour un nombre important de données Faciles à comprendre et à mettre en œuvre Tris simples
Tri rapide (« Quicksort ») Tri dichotomique appelé aussi tri des bijoutiers Idée : trier les perles avec des tamis plus ou moins fins pour chaque tamis on sépare les perles en deux sous-ensembles (celles qui passent et celles qui restent dans le tamis) puis on trie celles qui sont passées avec un tamis plus fin et les autres avec un tamis plus gros Tris complexes
Tri rapide (« Quicksort ») Partage avec pivot = TRI < 3 3 Suite du tri Algorithme choisir un pivot p (élément médian) partager le tableau en 2 : sous-tableau de gauche : éléments ≤ p sous-tableau de droite : éléments > p l’élément entre les 2 le pivot est à sa place définitive appel récursif avec le sous-tableau de gauche appel récursif avec le sous-tableau de droite Trace en classe: 44,55,12,42,94,18,06,67
Tris complexes 06,12,18,42,44,55,67,94 Meilleur cas : pivot = médiane nb de comparaisons ? O(n log n) nb de déplacements ? O(1) Pire cas : nb de comparaisons ? nb de déplacements ? n/4 tri rapide (« Quicksort ») : 06,12,18,42,44,55,67,94 n/2
Tris complexes n-4 n-3 06,12,18,42,44,55,67,94 n-2 n-1 tri rapide (« Quicksort ») : 06,12,18,42,44,55,67,94 Meilleur cas : pivot = médiane nb de comparaisons ? O(n log n) nb de déplacements ? O(1) Pire cas : pivot = min. ou max. nb de comparaisons ? O(n 2 ) nb de déplacements ? O(n 2 )
Problèmes du « Quicksort » Algorithme récursif taille de la pile = ? meilleur cas :O(log n) pire cas :O(n) en moyenne : O(log n) Comment faire pour minimiser la taille de la pile ? empiler toujours de sous-tableaux de taille optimum Tout dépend du choix du pivot du pire cas : O(n 2 ) au meilleur cas : O(n log n) Comment choisir un bon pivot ?
la solution idéale est de trouver à chaque fois la valeur médiane du sous-tableau à trier, mais sa recherche précise rend le tri plus lent que sans elle. Une solution quelquefois utilisée est de prendre par exemple trois valeurs, pour en prendre la valeur médiane, par exemple tab[droite], tab[gauche] et tab[(droite+gauche)/2] (dans le cas d'un tableau parfaitement mélangé, le choix de trois positions n'a pas d'importance, mais dans des tableaux presque triés le choix ci- dessus est plus judicieux). La totalité de ces améliorations peut apporter un gain de l'ordre de 20% par rapport à la version de base. Problèmes du « Quicksort » Choix du pivot : du pire cas : O(n 2 ) au meilleur cas : O(n log n) Comment choisir un bon pivot ?
Tris complexes Tri rapide (« Quicksort ») Meilleur cas : pivot = médiane nb de comparaisons ? O(n log n) nb de déplacements ? O(1) En moyenne nb de comparaisons ? O(n log n) nb de déplacements ? O(n log n) Pire cas : pivot = min. ou max. nb de comparaisons ? O(n 2 ) nb de déplacements ? O(n 2 ) tri stable? tri in situ? progressif? Très bien compris, étudié tant au niveau théorique qu’expérimental. Très bonnes performances s'il est optimisé dérécursivé tri par insertion sur les petites listes
Tris complexes Tri rapide (« Quicksort ») template template void quickSort(T tableau[], int inf, int sup) { int milieu; if (sup>inf) // s'il y a au moins 2 éléments { milieu = partition(tableau, inf, sup); // trier la partie de gauche quickSort(tableau, inf, milieu); // trier la partie de droite quickSort(tableau, milieu+1, sup); }}
Tris complexes Tri rapide (« Quicksort ») template template int partition(T tableau[], int inf, int sup) { T pivot, tempo; int i,j; pivot = tableau[(sup+inf)/2]; i = inf-1; j = sup+1; // Suite a la page suivante
Tris complexes Tri rapide (« Quicksort ») while ( i<j ) // tant que les index ne croisent pas { // conserver les éléments plus petits ou égaux { // conserver les éléments plus petits ou égaux //au pivot à sa gauche //au pivot à sa gauche do do { i++; { i++; } while (tableau[i]<pivot); } while (tableau[i]<pivot); // conserver les éléments plus grands ou // conserver les éléments plus grands ou //égaux au pivot à sa droite //égaux au pivot à sa droite do do { j--; { j--; } while (tableau[j]>pivot); } while (tableau[j]>pivot); // Permuter les éléments qui ne sont pas // Permuter les éléments qui ne sont pas // à leur place // à leur place if ( i<j) if ( i<j) {tempo = tableau[i]; {tempo = tableau[i]; tableau[i]= tableau[j]; tableau[j]= tempo; } } return j; } return j; }
Tri par fusion Diviser Diviser la séquence de n éléments à trier en 2 sous-séquences de taille n/2 Régner Trier les 2 sous-séquences Combiner Fusionner les 2 sous-séquences triées Tris complexes
Tri par fusion
1n1m Trié 1 n+m Trié t1 t3 t2 template template void triFusion(T t[], int debut, int fin) { if(debut<fin) { int milieu=(debut+fin)/2; triFusion(t, debut, milieu); triFusion(t, milieu+1, fin); fusionner(t, debut, milieu, fin); }}
void fusionner(T t[], int debut, int milieu, int fin) { int i1=debut; // indice courant dans t[debut..milieu] int i2=milieu+1; // indice courant dans t[milieu+1..fin] int i3=0; // indice courant dans t3 (tableau auxiliaire) T *t3; // tableau auxiliaire dynamique de longueur fin-debut+1 t3= new T[fin-debut+1]; while((i1<=milieu)&&(i2<=fin)) { if(t[i1]<t[i2]) { t3[i3]=t[i1]; i1++;} else { t3[i3]=t[i2]; i2++; } i3++; } if(i1>milieu) // on a epuisé t[debut..milieu] for(;i2<=fin;i2++,i3++) t3[i3]=t[i2]; else // on a epuisé t[milieu+1..fin] for(;i1<=milieu;i1++,i3++) t3[i3]=t[i1]; // il faut transférer t3 à sa place dans t for(i1=0;i1<i3;i1++){ t[i1+debut]=t3[i1]; } delete [] t3; }
Tri par fusion Comparaison avec le tri QuickSort Tris complexes Stabilité? in situ? Progressivité? Complexité?
Tri par arbre (« Heapsort ») : trace en classe 44,55,12,42,94,18,06,67 Algorithme bâtir une structure d’arbre sur place h i h 2i h i h 2i+1 structure appelée « monceau » (« heap ») Tris complexes
Monceau - définition Tris complexes Un arbre complet tel que pour chaque noeud N dont le parent est P, la clé de P est plus grande que celle de N Normalement implémenté dans un tableau Insertion et retrait sont O(log n) dans le pire cas Insertion est O(1) en moyenne Recherche du plus petit élément est O(1) dans le pire cas
Tris complexes Tri par arbre (« Heapsort »)
Tris complexes Création du monceau
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes Monceaux dans la STL Fonction pour créer un monceau: make_heap(Iterator deb, Iterator fin) Fonctions pour insérer et retirer un élément push_heap(Iterator deb, Iterator fin) pop_heap(Iterator deb, Iterator fin) Toutes ces fonctions ont une seconde version avec un troisième argument qui est un prédicat qui permet de redéfinir l’opérateur de comparaison utilisé Note: l’itérateur doit être un itérateur à accès direct, ce qui limite les possibilités de conteneurs (vector et deque)
Tris complexes Monceaux dans la STL Il existe aussi un adaptateur priority_queue On peut choisir le type de conteneur et de fonction de comparaison Offre les fonctions suivantes: –push() –top() –pop() Voir les exemples fournis en exemple dans la semaine 14
Tris complexes Le tri
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes
Tris complexes « Heapsort » Algorithme créer le monceau en descendant les clés trier en échangeant le maximum et le dernier + redescendre complexité ? O(n log n) en tout temps! aucun espace additionnel requis !!! Stabilité ? in situ ? Progressivité ?
Autres tris Si le tableau contient un grand nombre de valeurs similaires: algorithme par comptage ou tri par pigeonnier. Dans la cas où les clefs sont bornées (c'est à dire comprises entre un minimum et un maximum connus à l'avance) et en nombre fini: algorithme de tri basique (tri par bacs). ..et bien sûr le bogo tri! Conclusion
Ils sont présents dans la bibliothèque sort(): Doit garantir un temps moyen dans O(n lg n) Le tri rapide (quicksort) répond à ce critère stable_sort(): Doit garantir O(n lg n) dans le pire cas Peut utiliser de la mémoire supplémentaire Le tri par fusion (mergesort) répond à ce critère partial_sort() Doit garantir un temps moyen dans O(n lg n) Doit permettre le tri partiel Le tri par monceau (heapsort) répond à ce critère Les tris dans la STL