Tri de Shell-Metzner
Le tri de Shell-Metzner, ou plus simplement tri de Shell (du nom de son inventeur Donald Shell en 1959), est une amélioration du tri par insertion, conçue pour augmenter son efficacité sur les grandes listes. Il fonctionne en divisant la liste à trier en sous-séries d'éléments séparés par un certain écart (ou "gap"), diminuant progressivement. Au sein de chaque sous-série, les éléments sont triés localement. Au fur et à mesure que l'écart est réduit, les sous-séries se rapprochent, jusqu'à ce que l'écart atteigne 1, ce qui équivaut à un tri par insertion final sur la liste entière. Ce procédé permet de déplacer rapidement les éléments sur de longues distances, ce qui accélère la convergence vers une liste triée.
Principe du tri de Shell-Metzner
Le tri de Shell-Metzner, ou tri de Shell, est une amélioration du tri par insertion, conçue pour mieux gérer les grandes quantités de données. Il utilise une technique de gap sequence (séquence d'écart), commençant par des comparaisons d'éléments éloignés avant de réduire progressivement ces écarts. L'idée est de trier les sous-séries d'éléments dans un premier temps, puis d'affiner l'ordre au fur et à mesure que l'écart diminue, ce qui permet de réduire les déplacements d'éléments vers leur position finale. Cette technique rend le tri de Shell beaucoup plus rapide que le tri par insertion classique pour les grandes listes de données non triées.
L'algorithme de Shell commence par définir un écart initial, souvent égal à la moitié de la taille du tableau, et divise ainsi le tableau en plusieurs sous-listes. Chaque sous-liste est triée individuellement en utilisant un tri par insertion. Ensuite, l'écart est réduit, et le processus est répété pour les nouvelles sous-listes générées. Au fur et à mesure que l'écart diminue, les sous-listes se rapprochent de l'ensemble complet, jusqu'à ce que l'écart atteigne finalement 1, moment auquel le tri final est effectué.
L'efficacité du tri de Shell dépend de la séquence d'écarts utilisée. Différentes séquences ont été étudiées pour améliorer la performance de l'algorithme, comme les séquences de Hibbard, Sedgewick ou de Knuth. Chaque séquence a ses avantages et inconvénients, mais elles partagent le même principe de réduire progressivement les écarts de manière à optimiser le nombre de comparaisons et de déplacements d'éléments dans le tableau. Par exemple, la séquence de Knuth, 3k-1, est réputée pour ses performances équilibrées dans la plupart des cas.
L'un des avantages du tri de Shell est qu'il devient très efficace lorsque les données sont partiellement triées. Contrairement à d'autres méthodes comme le tri rapide, le tri de Shell fonctionne bien pour les structures de données modérément ou très dispersées. En réduisant progressivement les déplacements d'éléments nécessaires pour atteindre leur position finale, l'algorithme limite le nombre d'opérations, ce qui rend le tri rapide même avec des listes de grande taille.
Voici les points principales du principe de fonctionnement du tri de Shell-Metzner :
- Division en sous-séries : La liste est divisée en plusieurs sous-séries d'éléments en fonction d'un écart initial, généralement une fraction de la taille de la liste.
- Tri des sous-séries : Chaque sous-série est triée individuellement avec un tri par insertion.
- Réduction de l'écart : L'écart est progressivement réduit selon une séquence prédéfinie jusqu'à atteindre 1.
- Tri final : Lorsque l'écart est de 1, la liste est triée comme dans le tri par insertion, mais avec des éléments déjà partiellement ordonnés, rendant l'opération plus rapide.
Avantages et complexité
Le tri de Shell-Metzner est plus rapide que le tri par insertion traditionnel, notamment sur de grands ensembles de données, bien qu'il ait une complexité de temps variable en fonction de la séquence d'écarts choisie. Certaines séquences, comme celles de Hibbard ou de Sedgewick, améliorent son efficacité, réduisant la complexité temporelle dans le meilleur des cas à environ O(n3/2) voire moins avec certaines optimisations.
Algorithme
La technique de tri nommée «Shell-Metzner», est en fait une technique de réduction du nombre de comparaison a effectuer pour trier un tableau. Comment si prend-on? C'est simple, la comparaison s'effectue entre 2 éléments séparer par un écart égal (au départ) à la moitié de la taille du tableau. Ensuite, la comparaison s'effectue entre des éléments séparées par un écart égal au nombre d'élément du tableau divisée par 4. Lorsque l'écart atteint finalement 1, la tri est terminer.
Écart ← Nombre d'élément BOUCLE FAIRE Écart ← Écart / 2 BOUCLE FAIRE Inversion ← Faux BOUCLE POUR I ← 1 JUSQU'A Nombre d'élément - Écart J ← I + Écart SI Tableau [J] < Tableau [I] ALORS Temporaire ← Tableau[I] Tableau[I] ← Tableau[J] Tableau[J] ← Temporaire Inversion ← Vrai FIN SI FIN BOUCLE POUR TANT QUE N'EST PAS Inversion TANT QUE Écart = 1 |
Exemple en langage de programmation Turbo Pascal
Voici un exemple d'un tri à bulle d'un tableau de 8 éléments écrit en Turbo Pascal :
- Program ShellTri;
-
- Const
- Tableau : Array[0..7] of Byte = (15, 10, 23, 2, 8, 9, 14, 16);
-
- Var
- K,L,I,J,T:Byte;
- Inversion,Ecart:Integer;
-
- BEGIN
- Write('Avant:');
- For K := 0 to High(Tableau) do Begin
- Write(Tableau[K],', ');
- End;
-
- Inversion := 0;
- Ecart := High(Tableau) + 1;
- Repeat
- Ecart := Ecart shr 1;
- Repeat
- Inversion := 0;
- For I := 0 to (High(Tableau) + 1) - Ecart - 1 do Begin
- J := I + Ecart;
- If(Tableau[J] < Tableau[I])Then Begin
- T := Tableau[I];
- Tableau[I] := Tableau[J];
- Tableau[J] := T;
- Inversion := 1;
- End;
- End;
- Until 1 <> Inversion;
- Until 1 = Ecart;
-
- WriteLn;
- Write('Après:');
- For L := 0 to High(Tableau) do Begin
- Write(Tableau[L],', ');
- End;
- WriteLn;
- END.
on obtiendra le résultat suivant :
Avant:15, 10, 23, 2, 8, 9, 14, 16,Après:2, 8, 9, 10, 14, 15, 16, 23,
Exemple en langage de programmation C
Voici un exemple d'un tri à bulle d'un tableau de 8 éléments écrit en C :
- #include <stdio.h>
- #include <stdlib.h>
-
- #define MaxTableau 8
-
- int main()
- {
- int K,L,I,J,Inversion,ecart,Temporaire;
- int Tableau[MaxTableau] = {15, 10, 23, 2, 8, 9, 14, 16};
- printf("Avant:");
-
- for(K = 0; K < MaxTableau; K++) printf("%i, ",Tableau[K]);
-
- Inversion = 0;
- ecart = MaxTableau;
- do {
- ecart >>= 1;
- do {
- Inversion = 0;
- for(I = 0; I <= MaxTableau - ecart - 1; I++) {
- J = I + ecart;
- if(Tableau[J] < Tableau[I]) {
- Temporaire = Tableau[I];
- Tableau[I] = Tableau[J];
- Tableau[J] = Temporaire;
- Inversion = 1;
- }
- }
- } while (1 == Inversion);
- } while (1 != ecart);
-
- printf("\nAprès:");
- for(L = 0; L < MaxTableau; L++) {
- printf(", %i",Tableau[L]);
- }
- printf("\n");
- return 0;
- }
on obtiendra le résultat suivant :
Avant:15, 10, 23, 2, 8, 9, 14, 16,Après:2, 8, 9, 10, 14, 15, 16, 23,
Exemple en langage de programmation C#
Voici un exemple d'un tri à bulle d'un tableau de 8 éléments écrit en C# :
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace TriShell
- {
- class Program
- {
- static void Main(string[] args)
- {
- const int MaxTableau = 8 ;
- int K,L,I,J,Inversion,ecart,Temporaire;
- int[] Tableau = {15, 10, 23, 2, 8, 9, 14, 16};
- Console.Write("Avant:");
- for(K = 0; K < MaxTableau; K++) Console.Write(Tableau[K] + ", ");
- Inversion = 0;
- ecart = MaxTableau;
- do {
- ecart >>= 1;
- do {
- Inversion = 0;
- for(I = 0; I <= MaxTableau - ecart - 1; I++) {
- J = I + ecart;
- if(Tableau[J] < Tableau[I]) {
- Temporaire = Tableau[I];
- Tableau[I] = Tableau[J];
- Tableau[J] = Temporaire;
- Inversion = 1;
- }
- }
- } while (1 == Inversion);
- } while (1 != ecart);
- Console.WriteLine();
- Console.Write("Après:");
- for(L = 0; L < MaxTableau; L++) {
- Console.Write(", " + Tableau[L]);
- }
- Console.WriteLine();
- }
- }
- }
-
on obtiendra le résultat suivant :
Avant:15, 10, 23, 2, 8, 9, 14, 16,Après:2, 8, 9, 10, 14, 15, 16, 23,
Exemple en langage de programmation Delphi
Voici un exemple d'un tri à bulle d'un tableau de 8 éléments écrit en Delphi :
- Program ShellTri;
-
- {$APPTYPE CONSOLE}
-
- Uses SysUtils;
-
- Const
- Tableau : Array[0..7] of Byte = (15, 10, 23, 2, 8, 9, 14, 16);
-
- Var
- K,L,I,J,T:Byte;
- Inversion,Ecart:Integer;
-
- BEGIN
- Write('Avant:');
- For K := 0 to High(Tableau) do Begin
- Write(Tableau[K],', ');
- End;
-
- Inversion := 0;
- Ecart := High(Tableau) + 1;
- Repeat
- Ecart := Ecart shr 1;
- Repeat
- Inversion := 0;
- For I := 0 to (High(Tableau) + 1) - Ecart - 1 do Begin
- J := I + Ecart;
- If(Tableau[J] < Tableau[I])Then Begin
- T := Tableau[I];
- Tableau[I] := Tableau[J];
- Tableau[J] := T;
- Inversion := 1;
- End;
- End;
- Until 1 <> Inversion;
- Until 1 = Ecart;
-
- WriteLn;
- Write('Après:');
- For L := 0 to High(Tableau) do Begin
- Write(Tableau[L],', ');
- End;
- WriteLn;
- END.
on obtiendra le résultat suivant :
Avant:15, 10, 23, 2, 8, 9, 14, 16,Après:2, 8, 9, 10, 14, 15, 16, 23,