Traiter d’abord le ticket le plus urgent
Un tas est une structure qui garde toujours le plus petit élément tout en haut, prêt à être pris. Il sert à construire une file de priorité, où l’on sert d’abord l’élément le plus urgent.
Le service informatique d’une université reçoit des tickets : une panne du réseau, une imprimante, un mot de passe oublié. Il doit traiter d’abord le plus urgent, pas le plus ancien.
À la fin de cette leçon
Remarque
Je peux garder le plus petit élément à portée de main avec un tas.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Tas | un arbre rempli niveau par niveau où chaque parent est plus petit que ses enfants | |
| File de priorité | une file où l’on sort d’abord l’élément le plus urgent | |
| Priorité | un nombre : plus il est petit, plus c’est urgent | 1 |
| Parent | le nœud juste au-dessus d’un autre | |
| Remonter | échanger un élément avec son parent plus grand | |
| Descendre | échanger un élément avec son plus petit enfant |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Chaque parent est plus petit que ses enfants
Un tas est un arbre binaire rempli niveau par niveau, de gauche à droite, sans trou. Sa règle : chaque parent est plus petit que ses deux enfants.
Le plus petit élément est donc toujours à la racine. Les deux enfants ne sont pas rangés entre eux : le plus petit peut être à gauche ou à droite.
Un tas se range dans une simple liste
Un tas n’a pas de trou, donc une liste suffit. La racine est à l’indice 0. Le parent de l’indice i est à l’indice (i - 1) // 2.
Les enfants de l’indice i sont aux indices 2 × i + 1 et 2 × i + 2. Un calcul d’indice remplace le lien. La commande suivante calcule le parent de l’indice 6, puis les enfants de l’indice 2 : elle affiche 2, 5 et 6.
Parent de 6 et enfants de 2
python3 -c "print((6 - 1) // 2, 2 * 2 + 1, 2 * 2 + 2)"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : indices d’un tas
| Indice | Valeur | Parent | Enfants |
|---|---|---|---|
| 0 | 2 | Aucun | Indices 1 et 2 |
| 1 | 5 | Indice 0 | Indices 3 et 4 |
| 2 | 3 | Indice 0 | Indice 5 seulement |
| 3 | 9 | Indice 1 | Aucun |
| 4 | 6 | Indice 1 | Aucun |
| 5 | 8 | Indice 2 | Aucun |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Ajouter : poser en bas, puis remonter
Pour ajouter une valeur, on la pose à la première place libre, tout en bas. Tant qu’elle est plus petite que son parent, on l’échange avec lui : elle remonte.
Elle s’arrête dès que son parent est plus petit, ou à la racine. Il y a au plus un échange par niveau.
Exemple travaillé : ajouter 1 au tas
| Étape | Action | Liste |
|---|---|---|
| Départ | Le tas de la leçon | 2, 5, 3, 9, 6, 8 |
| 1 | Poser 1 en bas, indice 6 | 2, 5, 3, 9, 6, 8, 1 |
| 2 | 1 est plus petit que son parent 3 : échange | 2, 5, 1, 9, 6, 8, 3 |
| 3 | 1 est plus petit que son parent 2 : échange | 1, 5, 2, 9, 6, 8, 3 |
| 4 | 1 est à la racine : fin | 1, 5, 2, 9, 6, 8, 3 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Retirer le plus petit : faire descendre le dernier
Le plus petit est à la racine. On le retire, puis on met à sa place le dernier élément de la liste. Il est sans doute trop grand.
Tant qu’il est plus grand que son plus petit enfant, on l’échange avec cet enfant : il descend.
Exemple travaillé : retirer le plus petit
| Étape | Action | Liste |
|---|---|---|
| Départ | Le tas obtenu plus haut | 1, 5, 2, 9, 6, 8, 3 |
| 1 | Retirer 1, mettre le dernier (3) à la racine | 3, 5, 2, 9, 6, 8 |
| 2 | Le plus petit enfant de 3 est 2 : échange | 2, 5, 3, 9, 6, 8 |
| 3 | Le seul enfant de 3 est 8, plus grand : fin | 2, 5, 3, 9, 6, 8 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une file de priorité de tickets
import heapq
# La file de priorité d’un service informatique : le plus petit nombre passe d’abord.
tickets = []
heapq.heappush(tickets, [3, "mot de passe"])
heapq.heappush(tickets, [1, "panne du réseau"])
heapq.heappush(tickets, [2, "imprimante"])
while len(tickets) > 0:
ticket = heapq.heappop(tickets)
print(ticket[0], ticket[1])
# Ajoutez un ticket de priorité 1 à la liste, puis relancez.
Un tas coûte une dizaine d’étapes
Chaque échange change de niveau, donc une opération coûte au plus la hauteur du tas. Pour 1 000 éléments, c’est environ 10 étapes. Pour un million, environ 20.
Une liste non triée ajoute vite, mais trouver le plus petit demande de tout regarder. Le tas équilibre les deux.
Liste non triée ou tas : le coût
| Éléments | Structure | Ajouter | Retirer le plus petit |
|---|---|---|---|
| 1 000 | Liste non triée | 1 | 1 000 |
| 1 000 | Tas | 10 | 10 |
| 1 000 000 | Liste non triée | 1 | 1 000 000 |
| 1 000 000 | Tas | 20 | 20 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Le module heapq fait ce travail pour vous
Python propose le module heapq, qui range un tas dans une liste comme nous venons de le faire. La première commande ajoute 5, 3, 8 puis 1, retire le plus petit, et affiche ce qui reste.
La seconde range d’un coup la liste 5, 3, 8, 1 en tas et l’affiche.
Quatre ajouts, un retrait
python3 -c "import heapq; t = []; [heapq.heappush(t, x) for x in [5, 3, 8, 1]]; print(heapq.heappop(t), t)"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Ranger une liste en tas
python3 -c "import heapq; t = [5, 3, 8, 1]; heapq.heapify(t); print(t)"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Erreur fréquente
Attention
Beaucoup pensent qu’un tas est une liste triée. Ce n’est pas vrai. Seul le plus petit est à sa place, à la racine. [2, 5, 3, 9] est un tas, alors que 5 vient avant 3. Garder un tas coûte moins cher que garder une liste triée.
Pour aller plus loin
Poussez dans un tas tous les nombres d’une liste, puis retirez-les un à un : ils sortent triés. C’est le tri par tas.
Estimez le nombre d’étapes pour 1 000 nombres, puis comparez avec l’insertion de chaque nombre dans une liste triée.