POIRET Aurélien Algorithmique MPSI FFFF Chapitre No VII : Les algorithmes de tri Table des matières 1 - Le tri à bulles ..................................................................... 2 1.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 - L’algorithme du tri à bulles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2 - Le tri par insertion .............................................................. 4 2.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.3 - L’algorithme du tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 3 - Le tri rapide ...................................................................... 5 3.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 3.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.3 - L’algorithme de tri rapide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.4 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.5 - Choix du pivot et complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.5.1 - Pivot arbitraire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.5.2 - Pivot aléatoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.5.3 - Pivot Optimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 4 - Le tri fusion ....................................................................... 8 4.1 - Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 4.2 - Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 4.3 - L’algorithme de tri fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 9 4.4 - Étude sur un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4.5 - L’algorithme en langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4.6 - Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 5 - Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Dans ce chapitre, on présente divers algorithmes de tri en donnant leur principe, leur syntaxe Pythonienne et leurs complexités. 1 Le tri à bulles 1.1 Présentation Le tri à bulles ou tri par propagation est un algorithme de tri qui consiste à faire remonter progressivement les plus grands éléments d’un tableau, comme les bulles d’air remontent à la surface d’un liquide. L’intérêt du tri à bulles est que son son principe est simple. Cependant, sa complexité en moyenne est de l’ordre de n2 (où n est la taille du tableau), ce qui le classe parmi les mauvais algorithmes de tri. Il n’est donc quasiment pas utilisé en pratique. 1.2 Description de l’algorithme L’algorithme parcourt le tableau, et compare les couples d’éléments successifs. Lorsque deux éléments successifs ne sont pas dans l’ordre croissant, ils sont échangés. Après chaque parcours complet du tableau, l’algorithme recommence l’opération. Lorsqu’aucun échange n’a lieu pendant un parcours, cela signifie que le tableau est trié. On arrête alors l’algorithme. 1.3 L’algorithme du tri à bulles Entrées : T n ← longueur(T ) echange ← Vraie Tant que (n > 0 ET echange) faire echange ← Faux Pour j de 0 à n − 2 faire Si (T [j] > T [j + 1]) alors T [j + 1] ↔ T [j] echange ← Vraie Fin Si Fin Pour n ← n−1 Fait Retourner T Algorithme 1: Algorithme du tri à bulles 2 1.4 Étude sur un exemple Prenons la liste de chiffres [5, 1, 4, 2, 8] et trions-la de manière croissante en utilisant l’algorithme de tri à bulles. Première étape : [5, 1, 4, 2, 8] → [1, 5, 4, 2, 8] vertit. [1, 5, 4, 2, 8] → [1, 4, 5, 2, 8] [1, 4, 5, 2, 8] → [1, 4, 2, 5, 8] [1, 4, 2, 5, 8] → [1, 4, 2, 5, 8] Les éléments 5 et 1 sont comparés, et comme 5 > 1, l’algorithme les interInterversion car 5 > 4. Interversion car 5 > 2. Comme 5 < 8, les éléments ne sont pas échangés. Deuxième étape : [1, 4, 2, 5, 8] → [1, 4, 2, 5, 8] Même principe qu’à l’étape 1. [1, 4, 2, 5, 8] → [1, 2, 4, 5, 8] [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] À ce stade, la liste est triée, mais pour le détecter, l’algorithme doit effectuer un dernier parcours. Troisième étape : [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] [1, 2, 4, 5, 8] → [1, 2, 4, 5, 8] Comme la liste est triée, aucune interversion n’a lieu à cette étape, ce qui provoque l’arrêt de l’algorithme. 1.5 L’algorithme en langage Python # Tri à bulles def tri_bulles(T): n=len(T) echange=True while n>0 and echange: echange=False for j in range(0,n-1): (T[j],T[j+1])=(T[j+1],T[j]) echange=True n=n-1 return T 1.6 Complexité Pour un tableau de taille n, le nombre d’itérations de la boucle externe est compris entre 1 et n. En effet, on peut se convainc facilement qu’après la i-ème étape, les i derniers éléments du tableau sont à leur place. À chaque itération, il y a exactement n − 1 comparaisons et au plus n − 1 échanges. Le pire cas, n itérations, est atteint lorsque le plus petit élément est à la fin du tableau. La complexité est alors Θ(n2 ). Le meilleur cas, une seule itération, est atteint quand le tableau est déjà trié. Dans ce cas, la complexité est linéaire, c’est-à-dire, Θ(n). En moyenne, on peut montrer que la complexité est aussi Θ(n2 ). 3 2 Le tri par insertion 2.1 Présentation Le tri par insertion est un algorithme de tri classique, que la plupart des personnes utilisent naturellement pour trier des cartes : prendre les cartes mélangées une à une sur la table, et former une main en insérant chaque carte à sa place. En général, le tri par insertion est beaucoup plus lent que d’autres algorithmes comme le tri rapide et le tri fusion pour traiter de grandes séquences car sa complexité asymptotique est quadratique. Le tri par insertion est cependant considéré comme le tri le plus efficace sur des entrées de petite taille. Il est aussi très rapide lorsque les données sont déjà presque triées. Le tri par insertion est un tri stable (conservant l’ordre d’apparition des éléments égaux) et un tri en place (il n’utilise pas de tableau auxiliaire). 2.2 Description de l’algorithme Dans l’algorithme, on parcourt le tableau à trier du début à la fin. Au moment où on considère le i-ème élément, les éléments qui le précèdent sont déjà triés. Pour faire l’analogie avec l’exemple du jeu de cartes, lorsqu’on est à la i-ème étape du parcours, le i-ème élément est la carte saisie, les éléments précédents sont la main triée et les éléments suivants correspondent aux cartes encore mélangées sur la table. L’objectif d’une étape est d’insérer le i-ème élément à sa place parmi ceux qui précèdent. Il faut pour cela trouver où l’élément doit être inséré en le comparant aux autres, puis décaler les éléments afin de pouvoir effectuer l’insertion. En pratique, ces deux actions sont fréquemment effectuées en une passe, qui consiste à faire « remonter » l’élément au fur et à mesure jusqu’à rencontrer un élément plus petit. 2.3 L’algorithme du tri par insertion Entrées : T n ←longueur(T ) Pour i de 1 à n − 1 faire x ← T [i] j←i Tant que (j > 0 ET T [j − 1] > x) faire T [j] ← T [j − 1] j ←j−1 Fait T [j] ← x Fin Pour Retourner T Algorithme 2: Algorithme du tri par insertion 4 2.4 Étude sur un exemple Voici les étapes de l’exécution du tri par insertion sur le tableau T=[9,6,1,4,8] Le tableau est représenté au début et à la fin de chaque itération. i=1 i=2 i=3 i=4 2.5 T=[9,6,1,4,8] T=[6,9,1,4,8] T=[1,6,9,4,8] T=[1,4,6,9,8] → → → → T=[6,9,1,4,8] T=[1,6,9,4,8] T=[1,4,6,9,8] T=[1,4,6,8,9] L’algorithme en langage Python # Tri par par insertion def tri_insertion(T): for i in range(1,len(T)): x=T[i] j=i while j>0 and T[j-1]>x: T[j]=T[j-1] j=j-1 T[j]=x return T 2.6 Complexité La complexité du tri par insertion est en Θ(n2 ) dans le pire cas et en moyenne et en O(n) linéaire dans le meilleur cas. Plus précisément, — Dans le pire cas, atteint lorsque le tableau est trié à l’envers, l’algorithme effectue de l’ordre de n2 2 affectations et comparaisons. — Si le tableau est déjà trié, il y a n − 1 comparaisons et O(n) affectations. — La complexité du tri par insertion reste linéaire si le tableau est presque trié (par exemple, chaque élément est à une distance bornée de la position où il devrait être, ou bien tous les éléments sauf un nombre borné sont à leur place). Dans cette situation particulière, le tri par insertion surpasse d’autres méthodes de tri : par exemple, le tri fusion et le tri rapide (avec choix aléatoire du pivot) sont tous les deux en Θ(n ln(n)) même sur une liste triée. 3 3.1 Le tri rapide Présentation Le tri rapide (en anglais quicksort) est un algorithme de tri fondé sur la méthode de conception diviser pour régner. C’est un tri en place mais non stable. La complexité moyenne du tri rapide pour n éléments est proportionnelle à n ln(n), ce qui est optimal pour un tri par comparaison, mais la complexité dans le pire des cas est quadratique. Malgré ce désavantage théorique, c’est en pratique un des tris les plus rapides, et donc un des plus utilisés. Le pire cas est en effet peu probable lorsque l’algorithme est correctement mis en œuvre. Le tri rapide ne peut cependant pas tirer avantage du fait que l’entrée est déjà presque triée. Dans ce cas particulier, il est plus avantageux d’utiliser le tri par insertion. 5 3.2 Description de l’algorithme La méthode consiste à placer un élément du tableau (appelé pivot) à sa place définitive, en permutant tous les éléments de telle sorte que tous ceux qui sont inférieurs au pivot soient à sa gauche et que tous ceux qui sont supérieurs au pivot soient à sa droite. Cette opération s’appelle le partitionnement. Pour chacun des sous-tableaux, on définit un nouveau pivot et on répète l’opération de partitionnement. Ce processus est répété récursivement, jusqu’à ce que l’ensemble des éléments soit trié. Concrètement, pour partitionner un sous-tableau : — on place le pivot à la fin (arbitrairement), en l’échangeant avec le dernier élément du soustableau ; — on place tous les éléments inférieurs au pivot en début du sous-tableau ; — on place le pivot à la fin des éléments déplacés. 3.3 L’algorithme de tri rapide Fonction Partitionner( T , premier , dernier , pivot ) T [pivot] ↔ T [dernier] pivot← premier Pour i de premier à dernier−1 faire Si ( T [i] <= T [dernier] ) alors T [i] ↔ T [pivot] pivot←pivot+1 Fin Si Fin Pour T [dernier] ↔ T [pivot] Retourner T ,pivot Fin Fonction Trirap( T , premier , dernier ) Si (premier<dernier) alors pivot←choixpivot( T , premier, dernier ) T ,pivot←partitionner( T , premier, dernier, pivot) Trirap( T , premier, pivot−1 ) Trirap( T , pivot+1 , dernier) Fin Si Retourner T Fin Fonction Trirapide( T ) premier← 0 dernier←longueur(T ) Retourner Trirap( T , premier, dernier ) Fin Algorithme 3: Algorithme du tri rapide 3.4 L’algorithme en langage Python # Tri rapide def partitionner(T,pivot,premier,dernier): (T[pivot],T[dernier])=(T[dernier],T[pivot]) 6 pivot=premier for i in range(premier,dernier): if T[i]<=T[dernier]: (T[i],T[pivot])=(T[pivot],T[i]) pivot=pivot+1 (T[dernier],T[pivot])=(T[pivot],T[dernier]) return T,pivot def trirap(T,premier,dernier): if premier<dernier: pivot=(premier+dernier)//2 [T,pivot]=partitionner(T,pivot,premier,dernier) trirap(T,premier,pivot-1) trirap(T,pivot+1,dernier) return T def tri_rapide(T): trirap(T,0,len(T)-1) return T 3.5 3.5.1 Choix du pivot et complexité Pivot arbitraire Une manière simple de choisir le pivot est de prendre toujours le premier élément du sous-tableau courant (ou le dernier). Lorsque toutes les permutations possibles des entrées sont équiprobables, la complexité moyenne du tri rapide en sélectionnant le pivot de cette façon est Θ(n ln(n)). Cependant, la complexité dans le pire cas est Θ(n2 ), et celle-ci est atteinte lorsque l’entrée est déjà triée ou presque triée. Si on prend comme pivot le milieu du tableau, le résultat est identique, bien que les entrées problématiques soient différentes. Il est possible d’appliquer une permutation aléatoire au tableau pour éviter que l’algorithme soit systématiquement lent sur certaines entrées. Cependant, cette technique est généralement moins efficace que de choisir le pivot aléatoirement. 3.5.2 Pivot aléatoire Si l’on choisit le pivot aléatoirement de manière uniforme parmi tous les éléments, alors la complexité moyenne du tri rapide est Θ(n ln(n)) sur n’importe quelle entrée. Dans le pire cas, la complexité est Θ(n2 ). Néanmoins, l’écart type de la complexité est seulement Θ(n), ce qui signifie que l’algorithme s’écarte peu du temps d’exécution moyen. Dans le meilleur cas, l’algorithme est en Θ(n ln(n)). 3.5.3 Pivot Optimal En théorie, on pourrait faire en sorte que la complexité de l’algorithme soit Θ(n ln(n)) dans le pire cas en prenant comme pivot la valeur médiane du sous-tableau. Mais l’algorithme n’est pas suffisamment efficace dans la pratique et cette variante est peu utilisée. 7 4 4.1 Le tri fusion Présentation Le tri fusion est un algorithme de tri par comparaison stable. Sa complexité temporelle pour une entrée de taille n est de l’ordre de n ln n, ce qui est asymptotiquement optimal. Ce tri est basé sur la technique algorithmique diviser pour régner. L’opération principale de l’algorithme est la fusion, qui consiste à réunir deux listes triées en une seule. L’efficacité de l’algorithme vient du fait que deux listes triées peuvent être fusionnées en temps linéaire. 4.2 Description de l’algorithme L’algorithme est naturellement décrit récursivement. — On coupe en deux parties à peu près égales les données à trier. — On trie les données de chaque partie (pour cela, on coupe chaque partie en deux et on trie chacune) — On fusionne les deux parties La récursivité s’arrête car on finit par arriver à des listes composées d’un seul élément et le tri est alors trivial. 8 4.3 L’algorithme de tri fusion Fonction Insert( element , liste ) Si (liste = liste vide) alors Retourner liste formée par l’élément element Sinon, si (element < premier élément de liste) alors Retourner liste formée par les éléments element + liste Sinon Retourner liste formée par premier élément de liste + Insert(element , liste excepté son premier élément) Fin Si Fin Fonction Fusion( liste1 , liste2 ) Si (liste1 = liste vide) alors Retourner liste2 Sinon, si (liste2 = liste vide) alors Retourner liste1 Sinon Retourner Fusion(liste1 sans son premier élément , insert(premier élément liste1 , liste2)) Fin Si Fin Fonction Trifusion( liste ) n ←longueur(T ) Si (n = 0 OU n = 1) alors Retourner liste Sinon Retourner fusion( Trifusion(Première moitié liste) , Trifusion(Seconde moitié liste))) Fin Si Fin Algorithme 4: Algorithme du tri fusion 9 4.4 Étude sur un exemple 4.5 L’algorithme en langage Python # Tri fusion def insert(element,liste): if liste==[]: return [element] elif element<=liste[0]: return [element]+liste else: return [liste[0]]+insert(element,liste[1:]) def fusion(liste_1,liste_2): if liste_1==[]: return liste_2 elif liste_2==[]: return liste_1 else: return fusion(liste_1[1:len(liste_1)],insert(liste_1[0],liste_2)) def tri_fusion(T): n=len(T) if n==0 or n==1: return T else: return fusion(tri_fusion(T[0:n//2]),tri_fusion(T[n//2:])) 4.6 2T Complexité La complexité T (n) du tri fusion dans le pire cas pour une entrée de taille n vérifie T (n) = n 1 2 + n. Comme 2 = 2 , on peut affirmer que T (n) = Θ(n ln(n)). 10 5 Récapitulatif On peut résumer les différents résultats dans le tableau suivant : Complexité dans le meilleur des cas Complexité dans le pire des cas Complexité en moyenne Rapidité sur de petites listes presque triées Tri stable Tri en place Tri à bulles Tri par insertion Tri rapide pivot arbitraire Tri rapide pivot optimal Tri fusion Θ(n) O(n) Θ(n ln(n)) Θ(n ln(n)) Θ(n ln(n)) Θ(n2 ) Θ(n2 ) Θ(n2 ) Θ(n ln(n)) Θ(n ln(n)) Θ(n2 ) Θ(n2 ) Θ(n ln(n)) Θ(n ln(n)) Θ(n ln(n)) OUI OUI NON NON NON OUI OUI NON NON OUI OUI OUI OUI OUI NON 11