Le principe du tri à bulles (bubble sort ou sinking sort en anglais) est très simple : pour trier une liste, on compare son premier et son second élément et on les échange si nécessaire. Puis on fait la même chose pour le second et le troisième, puis pour le troisième et le quatrième… jusqu’à ce qu’on arrive à la fin de la liste. Après cela, on recommence à partir du début. Au bout d’un certain nombre d’itérations, on ne fait plus de permutations. Alors la liste est triée.

On remarque, qu’après le premier passage, l’élément le plus grand se retrouve à sa place définitive. Au deuxième passage, il sera donc inutile de le comparer avec le précédent. A chaque itération, on aura un élément de plus qui sera correctement placé à la fin de la liste. On pourra donc, à chaque fois, s’arrêter un peu plus tôt.

L’animation ci-dessous illustre le fonctionnement de ce tri.

Vous trouverez ici une petite vidéo qui montre cet algorithme.

Ordinogramme du tri bulle

Travaux pratiques

Le mini-jeu ci-dessous vous permet d’essayer de trier, par ordre de poids croissant, une suite de 5 tonneaux, avec juste une balance pour les comparer. Vous pouvez essayer de le résoudre avec la méthode du tri bulle.

Variantes

La version originale de Donald Knuth est un peu plus simple, mais l’idée est la même : on compare les éléments adjacents et on échange si nécessaire. Elle se présentait ainsi :

void swap(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
void bubble_sort(int* lst, int size) {
    for(int i=size-1; i > 0; i--)
        for(int j=0; j < i;j++)
            if (lst[j+1] < lst[j]){
                swap(&lst[j],&lst[j+1]);
            }
}

Toutefois, cette version a l’inconvénient de toujours faire le même nombre d’opérations, quel que soit le tableau en entrée. L’implémentation suivante s’arrête dès qu’elle a fait un parcours sans aucune permutation. C’est la plus courante aujourd’hui.

On peut encore l’améliorer un peu car, si dans une itération le dernier échange s’est fait à la position N, alors tous les éléments situés après cette position N sont dans le bon ordre. Donc, pour les itérations suivantes, il est inutile de les explorer à nouveau. Or aurait alors quelque chose comme ça :

void bubble_sort1(int* lst, int size)
{
    int swapped = 1;
    int lastswap = size ;
    int j;
    while (swapped) {
        swapped = 0;
        for (j=0;j<lastswap - 1;j++) {
            if (lst[j]>lst[j+1]){
                swapped = j+1;
                swap(&lst[j],&lst[j+1]);
            }
        }
        lastswap = swapped;
    }
}

Complexité

D’un point de vue pédagogique, cet algorithme est très intéressant. Il est facile à comprendre et donc tout aussi facile à expliquer. Il est facile à coder dans la plupart des langages informatiques et donne l’occasion de manipuler des vecteurs ou des listes. Il peut servir de base à de nombreux exercices d’optimisation . Et en plus, il a un nom sympa.

Mais, dans le monde réel, il faut bien dire qu’il n’est pas très performant. Il est souvent décrié, voire considéré comme “naïf” et à “à proscrire absolument”. Toutefois, il a quand même le mérite d’être suffisamment performant sur de petites listes ou des listes déjà partiellement triées.

Dans le pire des cas, avec des données triées à l’envers, les parcours successifs du tableau imposent d’effectuer (n2 - n) / 2 échanges. Par exemple, pour une liste de 10 éléments, il faudra, dans le pire des cas, faire 45 comparaisons et 45 échanges [ (102 - 10) / 2 ]. La complexité en temps est donc quadratique, de l’ordre de O(n2).

En moyenne, lorsque l’ordre initial des éléments à trier est aléatoire, on considère qu’il faudra faire (n2 - n) / 4 échanges. La complexité sera donc aussi de O(n2).

Dans le meilleur des cas, quand la liste est déjà triée, il faudra (n - 1) comparaisons et aucune permutation. La complexité en temps est linéaire, en O(n).

En espace utilisé (coût en mémoire de l’algorithme), la complexité du tri bulle est linéaire. Elle croit à la même vitesse que le nombre de données en entrée. Elle est donc de O(n).

L’animation ci-dessous permet de vérifier, de manière empirique, cette évolution du nombre d’opérations en fonction du nombre d’éléments à trier.

Taille données Comparaisons Echanges Total

Pour vous faire une idée plus concrète des performances de cet algorithme, supposez que vous deviez trier par ordre alphabétique le répertoire des habitants de plusieurs grandes villes. Et que la machine dont vous disposez peut effectuer disons dix millions d’instructions par seconde (ce qui n’est pas si mal si on tient compte des problèmes d’accès à la mémoire, aux disques etc…). Voici le temps qui serait nécessaire avec cette méthode :


Ville Nombre d'habitants Durée du tri
Chicoutimi 69 004
Bruxelles 174 383
Paris 2 220 445
Madrid 3 207 247
Berlin 3 520 031
Toronto 6 555 205
Londres 8 416 535
Mexico 20 879 830
Le Caire 24 439 785
Delhi 26 454 086
Jakarta 35 143 473
Sao Paulo 36 315 271
Tokyo 42 796 714

Vous pouvez changer le nombre d’instructions par seconde, pour tester.

Comics Strip