CPGE OUJDA                                                                                                                                                                                  SPE

Tri Maximier (tri en utilisant un arbre binaire)

Cette méthode de tri (aussi appelée "tri par tas" ou "heapsort" ou "tri de Williams") est basée sur la notion de tas. Un tas est un arbre binaire parfait tel que l’information contenue dans tout nœud est supérieure à celle contenue dans ses fils. La méthode du tri par tas comporte deux étapes :

1.      Construction par insertions successives d’un tas à partir du vecteur à trier.

2.      On remplace la racine, qui est nécessairement le plus grand élément du tableau par son fils le plus grand. La racine est transférée à la place de la dernière feuille de l’arbre et celle-ci est repositionnée. On réitère avec la nouvelle racine autant de fois qu’il y a de nœuds dans l’arbre.

Exemple

5

7

3

8

12

6

Par exemple, le tableau ci-dessus devra être vu ainsi :

Représentation en arbreReprésentation en arbre

Si l'on est rendu au nœud numéro n (indice dans le tableau initial), ses deux feuilles porteront les numéros 2n et 2n+1.

L'arbre n'est pas trié pour autant. Pour cela nous devrons le tamiser, c'est-à-dire prendre un nombre et lui faire descendre l'arbre jusqu'à ce que les nombres en dessous lui soient inférieurs. Pour l'exemple, nous allons tamiser le premier nœud (le 5).

Arbre avant tamisage de la racineArbre avant tamisage de la racine

En dessous de lui se trouvent deux nœuds : 7 et 3. On choisit le plus grand des deux : 7. Comme 7 est plus grand que 5, on les inverse.

Après une première phase de tamisage de la racineAprès une première phase de tamisage de la racine

On recommence : les deux nœuds situés en dessous sont 8 et 12. Le plus grand est 12. Comme 12 est plus grand que 5, on les inverse.

Arbre après tamisage de la racineArbre après tamisage de la racine

Pour trier notre arbre par tas, nous allons le tamiser en commençant, non pas par la racine de l'arbre, mais par les derniers nœuds (en commençant par la fin en quelque sorte). Bien sûr, il est inutile de tamiser les feuilles de l'arbre : elles ne descendront pas plus bas ! Pour limiter les tests inutiles, nous commencerons par tamiser le 3, puis le 7 et enfin le 5. Du coup, nous ne tamiserons que la moitié des nœuds de l'arbre. Faites l'essai, cela devrait vous donner ceci :

Tamisage complet de l'arbreTamisage complet de l'arbre

 Plus on descend dans l'arbre plus les nombres sont petits, mais c'est pas trié pour autant ?! 

En effet. il reste encore une étape. Nous allons parcourir notre arbre en sens inverse encore une fois. Nous allons échanger le dernier nœud de notre arbre tamisé (le 3) avec le premier (le 12) qui est également le plus grand nombre le l'arbre.

Échange du dernier et du premier nœudÉchange du dernier et du premier nœud

Une fois cet échange effectué, nous allons tamiser notre nouvelle racine, en considérant que la dernière feuille ne fait plus partie de l'arbre (elle est correctement placée désormais). Ce qui devrait nous donner le résultat suivant (voir la figure suivante).

Retamisage de l'arbre privé du 12Retamisage de l'arbre privé du 12

Cette manœuvre a ainsi pour but de placer sur la dernière feuille, le nombre le plus grand, tout en gardant un sous-arbre qui soit tamisé. Il faut ensuite répéter cette manœuvre avec l'avant-dernière feuille, puis l'avant-avant-dernière feuille… Jusqu'à arriver à la deuxième (et oui, inutile d'échanger la racine avec elle-même). Nous devrions ainsi obtenir le résultat suivant (voir la figure suivante).

Arbre triéArbre trié

Cette fois, l'arbre est trié dans le bon ordre.

Mise en œuvre

il nous faut une procédure tamiser, en plus de notre fonction Tri_tas.. pour passer d'un nœud n à celui du dessous, il faut multiplier n par 2 (pour accéder au nœud en dessous à gauche) et éventuellement lui ajouter 1 (pour accéder à celui en dessous à droite).

 

Algorithme :

fonction tamiser(arbre, nœud, n) :

(* descend arbre[nœud] à sa place, sans dépasser l'indice n *)

   k := nœud

   j := 2k+1

tant que j ≤ n-1

si j < n et arbre[j] < arbre[j+1]

         j := j+1

fin si

si arbre[k] < arbre[j]

         échanger arbre[k] et arbre[j]

         k := j

         j := 2k+1

sinon

         terminer

fin si

fin tant que

fin fonction

 

fonction triParTas(arbre, longueur) :

pour i := longueur/2 à 1

       tamiser(arbre, i, longueur)

fin pour

pour i := longueur à 2

       échanger arbre[i] et arbre[1]

       tamiser(arbre, 1, i-1)

fin pour

fin fonction

En Python :

def faire_tas(tab, debut, n):

# transforme le tableau "tab" en un tas

    racine = debut

    while racine*2 + 1 < n:

        fils = racine*2 + 1

        if fils < n-1 and tab[fils] < tab[fils+1]:

            fils += 1

        if tab[racine] < tab[fils]:

            tab[racine], tab[fils] = tab[fils], tab[racine]

            racine = fils

        else:

                return

def tripartas(tab):

    n = len(tab)

    debut = n//2 - 1

    fin = n - 1

    while debut >= 0:

        faire_tas(tab, debut, n)

        debut -= 1

    while fin > 0:

        tab[fin], tab[0] = tab[0], tab[fin]

        faire_tas(tab, 0, fin)

        fin -= 1

    return tab

L=[2,7,4,8,0,3,6,5,10]

L=tripartas(L)

print(L)Complexité

Cet algorithme permet de trier sur place les éléments d'un tableau en un temps de l'ordre de nlog(n), n est le nombre d'éléments à trier. La complexité entre le meilleur des cas et le pire des cas ne varie que d'un facteur constant. L'étape la plus coûteuse de l'algorithme est la seconde boucle, c'est-à-dire l'extraction des éléments du tas. La première étape, consistant à construire le tas, est effectuée en temps linéaire en n.

Les principaux atouts de cette méthode sont la faible consommation mémoire et l'efficacité, optimale étant donné qu'on ne fait aucune hypothèse sur la nature des données à trier.