Vous êtes sur la page 1sur 16

Algorithmes de tri quadratiques en java

Mickaël Péchaud
8 novembre 2008

Table des matières


1 Tri à bulles 4
1.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Exemples et code java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 Correction de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 Tri boustrophédon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2 Tri sélection 12
2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3 Code java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4 Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Correction de l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3 Tri insertion 14
3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 Code java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.4 Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.5 Correction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.6 Illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4 Conclusion 16

5 Exercices 16

1
Ce document est sous licence Creative Commons ccpnc2.0 :
http ://creativecommons.org/licenses/by-nc/2.0/fr/
En gros, vous pouvez faire ce que bon vous semble avec ce document, y compris l’utiliser pour faire des
papillotes, ou faire une performance publique (gratuite) durant lequel vous le mangez feuille par feuille (ce
que je déconseille tout de même), aux conditions expresses que :
• vous en citiez l’auteur.
• vous n’en fassiez pas d’utilisation commerciale.

Par respect pour l’environnement, merci de ne pas imprimer ce document si ça n’est pas indispensable !

2
Introdution
On s’intéresse ici à des algorithmes permettant de trier des tableaux d’entiers.
Trier un tableau d’entier

4 3 6 1 2 8 2 1 4 6 3 2

signifie remettre les entiers constituant ce tableau dans l’ordre croissant.

1 1 2 2 2 3 3 4 4 6 6 8

Outre l’intérêt pédagogique des algorithmes de tri, ils sont d’une utilité immense en informatique : ils
constituent une façon de classer automatiquqment des données, en vue par exemple de permettre un accés
plus rapide.
Un seul exemple : la recherche générale dans un tableau à une complexité linéaire, alors que la recherche
dichotomique dans un tableau trié a une complexité logarithmique. Si l’on a une base de données (un annuaire
par exemple) dans laquelle on souhaite effectuer de nombreuses recherches, le fait de trier les données va
donc amener un gain de temps substanciel.

Pourquoi des entiers ?


Pourquoi s’intéresser seulement au tri des entiers ? En fait, tous les algorithmes que nous allons décrire
sont basés uniquement sur des comparaisons 2 à 2 d’entiers et peuvent donc s’appliquer à d’autres types
d’objets que des entiers. La seule condition pour pouvoir utiliser de tels algorithmes est que ces objets soient
comparables entre eux, c’est-à-dire que l’on puisse pour 2 objets donnés dire lequel est le plus grand (au sens
large) - on dit alors qu’on dispose d’un ordre total sur les objets.
Nous nous intéressons ici seulement aux tris quadratiques en place (sans recours à un tableau auxiliaire),
qui sont les tris les plus simples d’un point de vue théorique, même si ce ne sont pas les plus efficaces.
Enfin, nous allons utiliser une méthode permettant de permuter 2 éléments d’un tableau :

static void permuter(int[] t, int i, int j)


{
int a=t[i];
t[i]=t[j];
t[j]=a;
}

Ainsi, si

t = {0, 1, 3, 5, 7, 3, 7};
et que l’on fait appel à
permuter(t, 1, 4)
on aura après l’exécution

t = {0, 7, 3, 5, 1, 3, 7};

Pour tous les tris expliqués ici, nous considèrerons le tableau-exemple suivant :

4 3 6 1 2 8

3
1 Tri à bulles
1.1 Principe
Le tri à bulles est oganisé en un certain nombre de passes.
Une passe de tri à bulles consiste à parcourir le tableau de gauche à droite et à permuter les éléments
contigüs mal placés.
Voici une passe sur le tableau-exemple :

4 3 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
4 3 1 6 2 8
4 3 1 2 6 8
4 3 1 2 6 8

et le code correspondant

for (int i=0; i<t.length-1; i++)


{
if (t[i]>t[i+1])
{
permuter (t, i, i+1);
}
}

Attention : il faut bien aller de 0 à t.length − 2, étant donné que nous comparons i et i + 1.
On effectue donc t.length−1 comparaisons lors d’une passe complète. Le nombre de permutations dépend
du tableau : il n’y en aura aucune sur un tableau déjà trié, et t.length − 1 sur le tableau {2, 1, 1, . . . , 1}
Une autre façon d’écrire ceci :

for (int i=1; i<t.length; i++)


{
if (t[i-1]>t[i])
{
permuter (t, i-1, i);
}
}

1.2 Exemples et code java


Une première solution consiste à effectuer des passes tant que le tableau n’est pas trié.
Les différentes passes sont séparées par des doubles lignes. Les éléments comparés et éventuellement
permutés sont désignés en rouge :

4
4 3 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
4 3 1 6 2 8
4 3 1 2 6 8
4 3 1 2 6 8
3 4 1 2 6 8
3 1 4 2 6 8
3 1 2 4 6 8
3 1 2 4 6 8
3 1 2 4 6 8
1 3 2 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8

Lors de la dernière passe, aucune permutation n’est effectuée, et l’algorithme s’arrête.


Voici le code java correspondant :

static void triBulle(int[] t)


{
boolean fin=false;
while(!fin)
{
fin=true;
for (int i=0; i<t.length-1; i++)
{
if (t[i]>t[i+1])
{
permuter (t, i, i+1);
fin=false;
}
}
}
}

On peut aussi constater qu’à la fin de la première passe, le maximum du tableau M est correctement
placé à la fin, et on n’a plus besoin d’y toucher. En effet, lorsque i correspond à l’indice du maximum du
tableau (t[i] = M ), on a t[i] ≥ t[i + 1] et le maximum est permuté avec son voisin. Le maximum va ainsi
(( remonter ))tout le tableau.
De la même façon, on peur remarquer qu’à la fin de la deuxième passe, les deux derniers éléments sont
bien placés, etc, etc. . . (Cette remarque va également permettre de prouver la correction du tri à bulles).
En utilisant cette remarque, on peut réduire le nombre de comparaison par passe (les éléments fixés sont
indiqués en vert).

5
4 3 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
4 3 1 6 2 8
4 3 1 2 6 8
4 3 1 2 6 8
3 4 1 2 6 8
3 1 4 2 6 8
3 1 2 4 6 8
3 1 2 4 6 8
1 3 2 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 3 2 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8

static void triBulle(int[] t)


{
for (int i=t.length-1; i>=0; i--)
for (int j=0; j<i; j++)
{
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
}
}
}

Pour profiter des avantages des deux méthodes, on peut rajouter une condition d’arrêt stoppant l’algo-
rithme si aucune permutation n’est effectuée lors d’une passe :

static void triBulle(int[] t)


{
for (int i=t.length-1; i>=0; i--)
{
boolean fin=true;
for (int j=0; j<i; j++)
{
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
fin=false;
}
}
if (fin) break; //sort de la boucle
}
}

6
1.3 Complexité
Considérons le deuxième algorithme proposé :

static void triBulle(int[] t)


{
for (int i=t.length-1; i>=0; i--)
for (int j=0; j<i; j++)
{
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
}
}
}

Durant la première passe, on effectue t.length − 1 comparaisons, dans la deuxième t.length − 2, etc. . .
Le nombre de comparaisons nc est ici toujours le même quelquesoit le tableau passé en argument : si
def.
n = t.length

n(n − 1)
nc = (n − 1) + (n − 2) + · · · + 1 =
2
Le nombre de permutation dépend du tableau, mais n’est pas plus grand que nc .
La complexité de cet algorithme est donc quadratique, que ce soit dans le meilleur cas, en moyenne1 ou
dans le cas le pire.
Si l’on rajoute la condition permettant d’arrêter lorsque l’on effectue aucune permutation durant une
passe, la complexité dans le meilleurs des cas devient linéaire (si on a un tableau déjà trié, on effectuera
seulement n − 1 comparaisons). En revanche, si on a un tableau trié en sens inverse, on effectuera toujours
n(n−1)
2 comparaisons et permutations. La complexité au pire reste donc la même (ainsi que la complexité en
moyenne - la démonstration dépasse le cadre de cette petite introduction).
En pratique, le tri à bulle est extrèmement lent, et n’est donc pas utilisé.

1.4 Correction de l’algorithme


Ce genre de preuves de correction est rapidement pénible. Je vais détailler la preuve dans un cas,
accrochez-vous au pinceau.
Considérons la deuxième version de l’algorithme :

static void triBulle(int[] t)


{
for (int i=t.length-1; i>=0; i--)
for (int j=0; j<i; j++)
{
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
}
}
}
1 La complexité (( moyenne ))est la moyenne de la complexité sur l’(( ensemble des tableaux possibles ))- en fait sur l’esemble

des ordres de départ possibles

7
• La terminaison est triviale
def.
• Nous allons commencer par montrer qu’à la fin d’une boucle interne, P ′ = (( t[i] est l’élément maximal
parmi t[0] . . . t[i] )).
//(1)
for (int j=0; j<i; j++)
{ //(2)
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
}
} //(3)
def.
Considérons la proposition P = (( t[j] est supérieur à tous les t[k] pour k ≤ j )).

Précondition La précondition en (1) s’écrit t[0] est supérieur à tous les t[k] pour k ≤ 0, ce qui est
trivialement vrai.

Invariant Montrons que notre proposition P est un invariant de boucle. Supponsons la proposition
P vrai en (2) et effectuons une itération de la boucle. Deux cas peuvent se produire :
– si t[j] ≤ t[j +1] on ne fait rien. On a alors t[j] est supérieur à tous les t[k] pour k ≤ j et t[j] ≤ t[j +1],
d’où l’on déduit t[j + 1] est supérieur à tous les t[k] pour k ≤ j + 1.
– si t[j] > t[j + 1], notons a et b les valeurs de t[j] et t[j + 1] respectivement au début de la boucle.
On a donc a > b et a est supérieur à tous les t[k] pour k ≤ j.
Après la permutation, on a t[j] = b et t[j + 1] = a.
On a donc t[j + 1] est supérieur à tous les t[k] pour k ≤ j d’après P , et t[j + 1] = a > b = t[j] soit
t[j + 1] est supérieur à tous les t[k] pour k ≤ j + 1.
P est donc bien un invariant de boucle.

Postcondition En sortant de la boucle (3), on a donc j = i et t[j] est supérieur à tous les t[k] pour
k ≤ j soit
t[i] est supérieur à tous les t[k] pour k ≤ i, C.Q.F.D.
On a donc bien P ′ .
• Nous allons maintenant nous occuper de la boucle externe
static void triBulle(int[] t)
{
//(1)
for (int i=t.length-1; i>=0; i--) //(2)
for (int j=0; j<i; j++)
{
if (t[j]>t[j+1])
{
permuter (t, j, j+1);
}
}
//(3)
}
def.
Considérons la proposition P = (( le tableau est trié de i + 1 à la t.length − 1 & t[i + 1] ≥ t[k] pour
tout k ≤ i + 1 )).

Précondition La précondition en (1) n’a pas vraiment de sens. après un passage dans la boucle,
P s’écrit en (2) (( le tableau est trié de t.length − 1 à t.length − 1 & t[t.length − 1] ≥ t[k] pour tout

8
k ≤ t.length − 1 )). La première partie de la proposition est triviale, le sous-tableau considéré étant de
taille 1. La seconde partie découle de P ′ .

Invariant Montrons que notre proposition P est un invariant de boucle. Supponsons la proposition
P vrai en (2) et effectuons une itération de la boucle.
Au départ on a : (( le tableau est trié de i + 1 à t.length − 1 & t[i + 1] ≥ t[k] pour tout k ≤ i + 1 )).
À la fin de la boucle interne, t[i] est l’élément maximal parmi t[0] . . . t[i] d’après P ′ .
On a donc
– (( le tableau est trié de i+1 à t.length−1 & t[i+1] ≥ t[i] ))⇒ (( le tableau est trié de i à la t.length−1 )).
– P ′ (( t[i] ≥ t[k] pour k ≤ i ))⇒ t[i] ≥ t[k] pour tout k ≤ i
P est donc bien un invariant de boucle.

Postcondition À la sortie de la boucle externe (3) on a donc


i = −1
et
(( le tableau est trié de i + 1 à t.length − 1 & t[i + 1] ≥ t[k] pour tout k ≤ i + 1 ))
soit en particulier
(( le tableau est trié de 0 à t.length − 1 ))
C.Q.F.D.

1.5 Illustration
Voici une illustration de l’évolution du tri à bulle sur un tableau à 1000 éléments. L’abscisse représente
la position dans le tableau, l’ordonnée la valeur.

Le nom du tri à bulles provient du fait que si l’on regarde une animation de l’algorithme en train de se
dérouler, on voit la plupart les éléments se déplacer lentement de droite à gauche, un peu comme des bulles
en train de remonter dans une boisson gazeuse, à une rotation de 90o près.

Il existe de très nombreuses variantes du tri à bulle. En voici une.

1.6 Tri boustrophédon


Pour effectuer un tri à bulles, nous parcourons le tableau de gauche à droite, et permutons les éléments
contigus mal classés. Ce procédé est répété jusqu’à ce que le tableau soit trié.
Le tri boustrophédon (du nom de certaines écritures, comme les hyéroglyphes dans lesquelles on lit une
ligne de gauche à droite, et la suivante de droite à gauche) est une variante du tri à bulles, où l’on parcours
une fois le tableau de gauche à droite, et une fois de droite à gauche.

9
4 3 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
4 3 1 6 2 8
4 3 1 2 6 8
4 3 1 2 6 8
4 3 1 2 6 8
4 3 1 2 6 8
4 3 1 2 6 8
4 1 3 2 6 8
1 4 3 2 6 8
1 4 3 2 6 8
1 3 4 2 6 8
1 3 2 4 6 8
1 3 2 4 6 8
1 3 2 4 6 8
1 3 2 4 6 8
1 3 2 4 6 8
1 3 2 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8

L’itération suivante ne permute aucun élément.

Voici le code correspondant :

static void boustrophedon(int[] t)


{
boolean b=false;
while (!b)
{
b=true;
for (int i=0; i<t.length-1; i++)
{
if (t[i]>t[i+1])
{
permute(t, i, i+1);
b=false;
}
}

for (int i=t.length-1; i>0; i--)

10
{
if (t[i-1]>t[i])
{
permute(t, i-1, i);
b=false;
}
}

Ici aussi, il serait et d’éviter des comparaisons inutiles en bout de tableau.


Les complexités (moyennes, dans le meilleur des cas, et dans le pire des cas) de ce tri sont les mêmes que
celles du tri à bulles.
La principale différence avec le tri à bulles est la suivante : le tri à bulle peut faire remonter très rapidement
des éléments dans un tableau, mais par contre un élément ne va descendre que de 1 au plus à chaque itération.
Le tri boustrophédon (( rééquilibre ))ce comportement. Par exemple, il va être plus efficace par exemple sur
le tableau suivant {2, 3, 4, 5, . . . , n − 1, n, 1}.

11
2 Tri sélection
2.1 Principe
On parcours le tableau de gauche à droite (avec un indice i ). À chaque étape, on échange l’élément situé
en i avec le minimum du tableau à partir de i (i.e. on sélectionne le minumum).

2.2 Exemple
Les éléments permutés sont indiqués en rouge, les éléments déjà classés en vert.

4 3 6 1 2 8
1 3 6 4 2 8
1 2 6 4 3 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8

2.3 Code java


Voici l’algorithme correspondant :

static void triSelection(int[] t)


{
for (int i=0; i<t.length-1; i++)
{
int min=t[i];
int indicemin=i;
for (int j=i+1; j<t.length; j++)
{
if (t[j]<min)
{
min=t[j];
indicemin=j;
}
}
permuter(t, i, indicemin);
}
}

2.4 Complexité
Le nombre de permutations est t.length − 1 (une à chaque étape).
À chaque étape, trouver le minimum requiert t.length − 1 − i comparaisons.
En faisant le même calcul que pour le tri à bulles, la complexité de cet algorithme est donc quadratique,
dans le cas moyen, dans les cas le meilleur et le pire.
Ce tri nécessite moins de permutations que le tri à bulle, et est en pratique plus rapide.
Néanmoins, sur un tableau déjà trié ou presque trié, cet algorithme est très mauvais, puisqu’il reste de
complexité quadratique. Il n’y a pas comme dans le cas du tri à bulles de façon simple de s’arrêter avant la
fin.

12
2.5 Correction de l’algorithme
Je ne vais pas rentrer dans les détails.
Pour la boucle interne, le bon invariant est (( min est le minimum de {t[i], . . . , t[j − 1]} & t[indicemin] =
min )).
Pour la boucle externe, le bon invariant est (( le tableau est trié de 0 à la i − 1 & t[i − 1] ≤ t[k] pour tout
k ≥ i − 1 )).

2.6 Illustration

13
3 Tri insertion
3.1 Principe
Le tri par insertion consiste à parcourir le tableau de gauche à droite, en maintenant le sous-tableau
situé à gauche de i trié. On cherche ensuite à insérer le ième élément dans ce sous-tableau, en le faisant
(( descendre ))vers la gauche dans le sous-tableau trié.

3.2 Exemple
L’élément que l’on va insérer (i) est désigné en vert, les comparaisons en rouge.

4 3 6 1 2 8
4 3 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
3 4 6 1 2 8
3 4 1 6 2 8
3 1 4 6 2 8
1 3 4 6 2 8
1 3 4 6 2 8
1 3 4 2 6 8
1 3 2 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8
1 2 3 4 6 8

3.3 Code java


Voici deux façons de coder cet algorithme :

static void triInsertion(int[] t)


{
for (int i=1; i<t.length; i++)
{
for(int j=i; j>0; j--)
{
if (t[j-1]>t[j])
permuter(t, j-1, j);
else break; //quitte la boucle
// facultatif, mais élimine des opérations
// inutiles
}
}
}

ou encore

static void triInsertion(int[] t)


{

14
for (int i=1; i<t.length; i++)
{
int j=i;
while(j>0 && t[j-1]>t[j])
{
permuter(t, j-1, j);
j--;
}
}
}

3.4 Complexité
Ici, dans le bon cas où l’on a un tableau déjà trié, ces algorithmes vont effectuer n − 1 comparaisons, et
pas de permutation.
La complexité dans le meilleur des cas est donc linéaire.
Par contre dans le cas d’un tableau trié à l’envers, le nombre de comparaisons et de permutations va être,
comme dans les cas précédents, proportionnel à n2 .
La complexité dans le pire des cas est donc quadratique.
On peut montrer que la complexité moyenne est également quadratique.
En pratique, cet algorithme est le plus rapide des trois algorithmes de bases que nous avons vus - il est
notamment possible de coder un peu plus intelligemment le décalage vers la gauche pour supprimer des accés
au tableau.

3.5 Correction
Ici encore, je ne vais pas rentrer dans les détails.
Considérons la première version de l’algorithme :
Pour la boucle interne, le bon invariant est (( t est trié de 0 à j − 1 & t est trié de j à i.
Pour la boucle externe, le bon invariant est (( le tableau est trié de 0 à la i − 1 )).

3.6 Illustration

15
4 Conclusion
Voici un petit tableau récapitulatif des complexités :
tri meilleure moyenne pire pratique
Tri à bulles Θ(n) Θ(n2 ) Θ(n2 ) très lent
Tri par sélection Θ(n2 ) Θ(n2 ) Θ(n2 ) un peu mieux
Tri par insertion n Θ(n2 ) Θ(n2 ) encore mieux
En pratique, le tri à bulles est très peu utilisé. Les deux autres tris peuvent être intéressant pour de très
petits tableaux (de l’ordre d’une ou quelques dizaines d’éléments), mais dès que l’on a des tableaux plus
grands, on leur préférera des tris plus rapides tels que le Tri Fusion(Θ(nlogn) au pire, pas en place), Tri
Shell (Θ(nlog 2 n) au pire, en place), ou Tri Rapide(Θ(n2 ) au pire, mais Θ(nlogn) en moyenne, en place).

5 Exercices
Quelques exercices possibles :
• réexpliquez le fonctionnement de ces algorithmes.
• Déroulez les algorithmes sur le tableau suivant :
5 2 4 3 1
• Recoder les tris ci-dessus de droite à gauche. Attention aux indices !
• Coder une variante du tri sélection où l’on sélectionne à la fois le plus petit et le deuxième plus petit
élément.
• Prouver les tris par sélection et insertion proprement.
• Le décallage vers la gauche dans le tri par insertion nécessite trop d’accés au tableau lors des permu-
tations. Pouvez vous écrire une variante nécessitant à peu près deux fois moins d’accés ?

16

Vous aimerez peut-être aussi