Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
Un tas (monceau), en anglais heap, (ou plus précisément un tas binaire) est une structure de données
répondant aux conditions suivantes :
• c'est un arbre binaire complet (ou presque complet): tous les niveaux sauf le dernier doivent être
totalement remplis et si le dernier ne l'est pas totalement, alors il doit être rempli de gauche à droite (toutes ses feuilles sont de
même profondeur).
• il est ordonné en tas
On dit qu'un arbre est ordonné en tas lorsque la propriété suivante est vérifiée : les nœuds sont ordonnés par
leurs clés respectives,
- On parle d’un tas-max (Max-heap) si tout nœud a une valeur plus grande ou égale à celles de ses
deux fils. Pour tous A et B nœuds de l'arbre tels que B soit un fils de A clé(A) ≥ clé(B)
Dans ce cas, le plus grand élément de l’arbre est à la racine
Exemple :
- On parle d’un tas-min (Min-heap) si tout nœud a une valeur plus petite ou égale à celles de ses deux
fils. Dans ce cas, le plus petit élément de l’arbre est à la racine.
Exemple :
Module : Algorithmique et structure de données - Année universitaire : 2016-2017
Ils sont ainsi très utilisés pour implémenter les files à priorités car ils permettent des insertions en temps
logarithmique et un accès direct au plus grand élément.
L'efficacité des opérations effectuée sur des tas est très importante dans de nombreux algorithmes sur les
graphes.
En d’autre terme :
Un tas (heap) est un arbre binaire T qui emmagasine une collection de clés (ou paires clé-élément) comme
nœuds internes et qui satisfait les deux propriétés suivantes:
- Propriété d’ordre: clé(parent) ≤ clé(enfant) pour le tas-min
clé(parent) ≥ clé(enfant) pour le tas-max
- Propriété structurelle: arbre binaire complet
Le fait qu'un tas soit un arbre binaire complet permet de le représenter d'une manière intuitive par un tableau
unidimensionnel.
Le cas du tas est l'un des rares où un vecteur dont le premier élément a l'indice 1 pourrait être avantageux.
Un arbre binaire parfait a une représentation compacte sans trous dans le tableau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
a b C e F G h i j
Explication détaillée :
Considérons que l'on veuille ajouter le nœud à notre tas binaire :
On insère à la prochaine position libre (la position libre la plus à droite possible sur le dernier niveau) puis
on effectue l'opération suivante (que l'on appelle percolation vers le haut ou percolate-up) pour rétablir si
Module : Algorithmique et structure de données - Année universitaire : 2016-2017
nécessaire la propriété d'ordre du tas binaire : tant que n'est pas la racine de l'arbre et que est strictement
supérieur à son père on échange les positions entre et son père.
Soit la hauteur de notre tas binaire, lors de l'algorithme ci-dessus on effectue au plus échanges. Or
comme un tas binaire est un arbre binaire parfait on a (où est le nombre de nœuds du
tas binaire et une constante) donc la complexité est bien .
On compare 50 et son père 28, comme 50>28 on échange On compare 50 et son père 41, comme 50>41 on échange
les positions de 50 et 28 les positions de 50 et 41
Explication détaillée :
On souhaite retirer la racine de notre tas binaire (c'est-à-dire le maximum de notre tas selon la relation
d'ordre associée). Cependant il faut conserver la structure de tas binaire après la suppression, on procède
donc de la manière suivante :
On supprime la racine et on met à sa place le nœud qui était en dernière position de l'arbre binaire (donc le
nœud le plus à droite sur le dernier niveau) que l'on notera . Puis on fait l'opération suivante (que l'on
Module : Algorithmique et structure de données - Année universitaire : 2016-2017
appelle percolation vers le bas ou percolate-down) : tant que a des fils et que est strictement inférieur à
un de ses fils, on échange les positions entre et le plus grand de ses fils.
Algorithme Supprimer
Entrée : P : entier // P nombre d'éléments contenu dans l'arbre
Tas[1..max] : tableau d'entiers // le tas
Sortie : P : entier // P nombre d'éléments contenu dans l'arbre
Tas[1..max] : tableau d'entiers // le tas
Lemini : entier // le plus petit de tous les éléments du tas
Local i, j, temp : entiers ;
début
Lemini Tas[1] ; // retourne la racine (minimum actuel) pour stockage éventuel
Tas[1] Tas[P] ; // la racine prend la valeur de la dernière feuille de l'arbre
PP-1;
j1;
Tantque j <= (P div 2) faire
// recherche de l'indice i du plus petit des descendants de Tas[j]
si (2 * j = P ) ou (Tas[2 * j] < Tas[2 * j + 1])
alors i 2 * j ;
sinon i 2 * j +1 ;
Fsi;
// Echange éventuel entre Tas[j] et son fils Tas[i]
si Tlab[j] > Tlab[i] alors
temp Tlab[j] ;
Tlab[j] Tlab[i] ;
Tlab[i] temp ;
ji;
sinon Sortir ;
Fsi;
finTant
FinSupprimer
Complexité : Par le même argument que pour l'algorithme de ajouter, on fait au plus échange donc la
complexité est bien
Exemple :
Application de tri
Ces opérations sur les tas permettent de résoudre un problème de tri à l’aide d’un algorithme appelé:
HeapSort. Cet algorithme de tri possède la bonne propriété d'avoir une complexité temporelle de 0(n log n).
Il utilise une structure de données temporaire dénommée "tas" comme mémoire de travail. C'est une variante
de méthode de tri par sélection où l'on parcourt le tableau des éléments en sélectionnant et conservant les minimas
successifs (plus petits éléments partiels) dans un arbre parfait partiellement ordonné.
Il reste à comprendre comment on peut construire un tas à partir d'un tableau quelconque.
On remarque d'abord que l'élément de gauche du tableau est à lui seul un tas. Puis on ajoute à ce tas, le
deuxième élément avec la procédure Ajouter que nous venons de voir, puis le troisième, .... etc. A la fin,
on obtient bien un tas de N éléments dans le tableau à trier. Le programme est:
Algorithme
Le procédé récursif s'arrête car on manipule à chaque appel de la fonction des tableaux de taille moitié. En
détail :
Procédure tri_fusion(tableau t)
n = longueur(t)
si n > 1 alors
u = tri_fusion(t[1], …, t[n / 2]) // première moitié
v = tri_fusion(t[n / 2 + 1], …, t[n]) // deuxième moitié
a = 1 // pointeur pour parcourir les éléments de u
b = 1 // pointeur pour parcourir les éléments de v
pour i allant de 1 à n faire
si (a <= longueur(u)) // si tous les éléments de u n'ont pas été parcourus
et (b > longueur(v) // et si tous les éléments de v ont été parcourus
ou u[a] ≤ v[b]) alors // ou si l'élément courant de u est plus petit
t[i] = u[a] // on choisit l'élément courant de u comme élément suivant de t
a = a + 1 // on avance dans u
sinon'''
t[i] = v[b] // on choisit l'élément courant de v comme élément suivant de t
b = b + 1 // on avance dans v
fin si
fin pour
fin si
renvoyer t
fin procédure
L'algorithme suivant est détaillé de sorte qu'il soit traduisible en n'importe quel langage impératif en
quelques minutes. La liste à trier est de taille n. Pour la concision et l'efficacité de l'algorithme, on suppose
que la liste à trier comporte au moins 2 éléments et que :
soit elle est simplement chaînée, non circulaire et précédée par un maillon racine p (hors de la liste
mais pointant vers son premier élément) ;
Module : Algorithmique et structure de données - Année universitaire : 2016-2017
Dans tous les cas, la liste est triée après le chaînon p passé en paramètre, c'est-à-dire que le chainon
successeur de p sera le plus petit de la liste. Des descriptions un peu moins concises mais libérées de ces
contraintes structurelles existent.
fonction trier(p, n)
Q := n/2 (division entière)
P := n-Q
si P >= 2
q := trier(p, P)
si Q >= 2 p := trier(q, Q)
sinon
q := p.suivant
fin
q := fusionner(p, P, q, Q)
renvoyer q
fin
fonction fusionner(p, P, q, Q)
répéter indéfiniment
si valeur(p.suivant) > valeur(q.suivant)
déplacer le maillon q.suivant après le maillon p
si Q = 1 quitter la boucle
Q := Q-1
sinon
si P = 1
tant que Q >= 1
q := q.suivant
Q := Q-1
fin
quitter la boucle
fin
P := P-1
fin
p := p.suivant
fin
renvoyer q
fin
Le déplacement d'un maillon q.suivant après un maillon p nécessite un pointeur temporaire t. Si la liste est
simplement chainée, le déplacement se fait par cet échange de liens :
t := q.suivant
q.suivant := t.suivant
t.suivant := p.suivant
p.suivant := t
Si la liste est doublement chainée et circulaire, le déplacement se fait par cet échange de liens :
t := q.suivant
q.suivant := t.suivant
q.suivant.précédent := q
t.précédent := p
t.suivant := p.suivant
p.suivant.précédent := t
p.suivant := t
Module : Algorithmique et structure de données - Année universitaire : 2016-2017
Ainsi décrit, l'algorithme peut être hybridé très facilement avec d'autres tris. Cela se fait en rajoutant une
condition sur la toute première ligne de la fonction fonction trier(p, n). Sur de petites sous-listes, elle a pour
rôle de remplacer toutes les opérations qui suivent par un tri de complexité quadratique mais en pratique
plus rapide. Dans la suite, les conditions P >= 2 et Q >= 2 peuvent alors être retirées.
Fusionner [1;2;5] et [3;4] : le premier élément de la liste fusionnée sera le premier élément d'une des deux
listes d'entrée (soit 1, soit 3) car ce sont des listes triées.