Arbres et tas

I/ Trie

1) Implémentation des arbres binaires

a) Arbres binaires complets

Un arbre binaire est dit complet si tous ses niveaux sont remplis. Autrement dit, toutes ses feuilles sont à la même hauteur.

Voici un arbre binaire complet de hauteur 2 :

%3 a a b b a--b c c a--c d d b--d e e b--e f f c--f g g c--g

Un arbre binaire complet peut être représenté par un tableau, en utilisant le fait que

On introduit un décalage supplémentaire pour tenir compte du fait qu'on compte ) partir de 0 et pas de 1. En C l'arbre binaire ci-dessus peut être représenté ainsi :

char arbre [10];
int i;

int position(char c){
    return c-'a';
} 

char enfant_gauche(char c){
    return arbre[2*position(c)+1];
}

char enfant_droit(char c){
    return arbre[2*position(c)+2];
}

char parent(char c){
    return arbre[(position(c)-1)/2];
}

b) Arbres binaires parfaits

La même représentation peut s'appliquer aussi à un arbre qui n'est pas complet, du moment que

Un tel arbre est dit parfait. Voici un exemple :

%3 a a b b a--b c c a--c d d b--d e e b--e f f c--f g g c--g h h d--h i i d--i j j e--j

Un exemple important d'arbre binaire parfait est le tas (voir plus bas).

2) Trie

Un trie, ou arbre préfixe, est tel que deux mots ont le même père si et seulement s'ils ont le même préfixe :

%3 a b a--b c a--c d la b--d e le b--e f b--f g c--g h c--h i lie f--i j lit f--j k mal g--k l mat g--l m mer h--m n met h--n

Dans l'exemple ci-dessus, le trie n'est pas un arbre binaire. Mais on peut le représenter par un arbre binaire.

2) Représentation par un arbre binaire

On convient que

La représentation obtenue est plus compacte que celle de l'arbre initial.

%3 a b a--b c b--c d la b--d g c--g e le d--e f e--f i lie f--i h g--h k mal g--k m mer h--m j lit i--j l mat k--l n met m--n

3) Algorithme de Liv-Zempel

Un trie peut servir à la compression de fichiers.

L'algorithme de Huffman utilise un arbre préfixe pour coder un texte. Si le codage est binaire, l'arbre l'est aussi. Mais le fichier compressé doit contenir l'arbre ce qui diminue les performances de l'algorithme.

L'algorithme LZW utilise un dictionnaire qui peut être recréé par le décodage.

II/ Arbres bicolores

1) Arbres binaires de recherche

Un arbre binaire de recherche est un arbre binaire (si si !) étiqueté par des éléments d'un ensemble ordonné, et tel que chaque parent a une étiquette

Les arbres binaires de recherche sont implémentés de façon particulièrement efficace par des arbres rouges-noirs.

2) Arbres rouges-noirs

Un arbre (sous-entendu, binaire) rouge-noir est un arbre binaire colorié en deux couleurs et tel que :

Ces propriétés optimisent les opérations d'insertion, recherche et suppression.

Voici un exemple d'arbre rouge-noir :

%3 a b a--b c a--c d b--d e b--e f c--f g c--g h d--h i d--i j e--j

3) Représentation des tableaux associatifs

Les arbres rouges-noirs sont souvent utilisés (en tant qu'arbres binaires de recherche) pour implémenter des tableaux associatifs (struct en C, dict en Python, Object en Javascript, enregistrements en OCaml,...). La recherche d'une clé est rapide si l'arbre binaire de recherche est rouge-noir. Mais la recherche dichotomique nécessite que les clés puissent être ordonnées. La relation d'ordre doit même être totale. C'est le cas pour

III/ Tas

1) Définition

Un tas (heap) est un arbre binaire parfait tel que chaque nœud a une valeur (ou « priorité » ) supérieure à celle des ses deux enfants. Cette propriété de tas est récursive.

Voici un exemple de tas :

%3 a 89 b 55 a--b c 13 a--c d 21 b--d e 34 b--e f 2 c--f g 1 c--g h 3 d--h i 5 d--i j 8 e--j

2) Files de priorité

Une file de priorité est une file dont chaque élément est doté d'une priorité (les priorités sont munies d'un ordre total), et

Un tas permet d'implémenter une file de priorité, de la façon suivante :

Les files de priorité sont utilisées notamment (et notoirement) par le noyau Linux pour l'ordonnancement des tâches.

3) Tri par tas (Williams 1964)

a) Algorithme

La structure de tas a été créée pour un algorithme de tri particulièrement efficace. L'idée est de créer un tas à partir de la liste à trier (ce qui est de complexité n×log(n)) puis défiler à chaque étape l'élément de priorité maximale, qui est la racine.

Pour trier la liste [6,3,1,7,5,9,8,2,4] on obtient le tas suivant :

%3 a 9 b 7 a--b c 8 a--c d 3 b--d e 5 b--e f 1 c--f g 6 c--g h 2 d--h i 4 d--i

Comme chaque défilement amène à réorganiser le tas pour rétablir la condition de tas, sa complexité dans le pire cas est log(n) donc la complexité des défilements successifs est n×log(n). La complexité du tri par tas est donc optimale : n×log(n).

b) Prim

L'algorithme de Prim pour construire un arbre couvrant de poids minimal utilise une file de priorité. La priorité est le poids des arêtes que l'on garde pour l'arbre couvrant.

c) Dijkstra

L'algorithme de Dijkstra utilise une file de priorité, la priorité étant le coût des arêtes.