4.1- TP - Tas
I. Implémentation d'un tas min
Les tas sont un type d'arbre binaire avec quelques propriétés supplémentaires.
On définit un tas min comme :
- Soit une feuille
- Soit un noeud, qui a une étiquette et deux fils (droite et gauche)
De plus,
- si un noeud est étiquetté par
et y l'étiquette d'un de ses fils, on a . Ainsi, les fils sont toujours plus grands que leur pères. - le tas doit être semi-complet, c'est à dire que tous les niveaux sauf le dernier doivent être remplis.
En dessous de l'arbre, les mêmes éléments rangés "dans l'ordre de lecture" (niveau par niveau, de gauche à droite) : cela servira dans la partie II. Survolez une case pour la retrouver dans l'arbre.
On donne le type OCaml suivant pour un tas min :
Loading editor...
type tas = F | N of int * tas * int * tas;;
A chaque noeud N, on associe
- la hauteur du sous arbre enraciné en
N - le fils gauche de
N - l'étiquette de
N - le fils droite de
N
On vous donne également une variable tas_test contenant le tas ci-dessus, et une fonction affiche_tas qui permet d'afficher un tas.
Loading editor...
let tas_test = N (3,
N (2,
N (1,
N (0, F, 25, F),
17,
N (0, F, 100, F)),
2,
N (0, F, 19, F)),
1,
N (1,
N (0, F, 36, F),
3,
N (0, F, 7, F))
);;
let affiche_tas t =
let rec aux t indentl indentr = match t with
|F -> ()
|N(_, t2, x, t1) -> aux t1 (indentl^"\t ") (indentl^"\t|") ;
Printf.printf "%s%d ----%s|\n" indentl x (if x > 10 then "" else "-");
aux t2 (indentr^"\t|") (indentr^"\t ");
print_string indentr;
print_newline ()
in
aux t " " ""
;;
affiche_tas tas_test;;Dessiner un tas
La fonction afficher_arbre_binaire du site dessine un arbre binaire, à partir d'une fonction qui découpe l'arbre (None pour une feuille F, et sinon Some (gauche, étiquette, droit)) :
let decoupe t = match t with
| F -> None
| N (_, g, x, d) -> Some (g, string_of_int x, d);;
afficher_arbre_binaire decoupe tas_test;;Question 0
Ecrivez une fonction hauteur: tas -> int qui renvoie la hauteur d'un tas. Une feuille a une hauteur de -1.
Loading editor...
Question 1
Comment peut-on trouver l'élément minimal dans un tas min ? Écrivez une fonction getmin : tas -> int qui renvoie le minimum d'un tas non vide, et une erreur sinon.
Loading editor...
Question 2
Écrivez une fonction verifie1 : tas -> bool qui vérifie qu'un tas donné en argument vérifie la propriété 1. du tas min.
Loading editor...
Question 3
Écrivez une fonction verifie2 : tas -> bool qui vérifie qu'un tas donné en argument vérifie la propriété 2. du tas min.
Utilisez la hauteur stockée dans l'arbre : si
Loading editor...
On aimerait maintenant définir deux opérations sur les tas :
- l'ajout d'un élément
- l'extraction de l'élément minimal
Dans les deux cas, on va en fait ajouter / retirer les éléments uniquement à la fin du tas, c'est à dire l'élément le plus a droite du dernier niveau.
Question 4
Comment peut-on trouver cet élément le plus à droite du dernier niveau ?
On veut encore une fois utiliser la hauteur !
Question 5
Ecrivez une fonction echange: tas -> int * tas qui retire et renvoie le plus petit élément d'un tas, en mettant le dernier élément (le plus à droite du dernier niveau) à sa place.
Tas initial
Loading editor...
Maintenant, on a un tas dont la racine ne respecte plus forcément la propriété 1. (voir schéma ci-dessus).
Question 6
Écrivez une fonction descend: tas -> tas qui fait descendre le noeud en l'échangeant avec un de ses fils jusqu'à ce que le propriété 1. soit respectée.
Tas initial
Loading editor...
let descend (t: tas): tas = failwith "à compléter"
Question 7
Combinez ces deux fonctions en une fonction qui permet d'extraire le minimum du tas en conservant ses propriétés.
Loading editor...
let extrait_min (t: tas): int * tas = failwith "à compléter"
Question 8
Écrivez une fonction qui permet d'ajouter un élément au dernier emplacement vide, puis de le faire remonter jusqu'à ce qu'il soit à sa place.
Attention au cas où le dernier niveau du tas est plein !
Tas initial
Loading editor...
let ajout (t: tas) (el: int): tas = failwith "à compléter"
Complexité des opérations
On va s'intérésser à la complexité des opérations en fonction du nombre total d'éléments dans le tas.
Question 9
- Exprimez la hauteur du tas en fonction de n
- Exprimez la complexité des opérations d'ajout et d'extraction en fonction de la hauteur de l'arbre
- En déduire la complexité en fonction de n
Tri par tas
Le tri par tas est un algorithme de tri simple (une fois qu'on a implémenté un tas) : on ajoute tous les éléments de notre liste au tas, puis on extrait le minimum jusqu'à ce que celui-ci soit vide.
Question 10
Écrivez une fonction tri_tas: int list -> int list qui trie une liste en utilisant le tri par tas.
Loading editor...
II. Implémentation d'un tas avec un tableau
Plutot que d'utiliser une structure récursive immutable, on propose d'utiliser un tableau pour stocker notre tas. On utilise la représentation suivante :
- la racine est stockée à l'indice 0
- si un élément est stocké à l'indice i, son fils gauche est à la position 2i + 1 et son fils droite 2i + 2
- les éléments absents sont représentés par "-1".
À noter que cela revient à placer les éléments dans l'ordre "de lecture".
On défini le type OCaml suivant, ainsi qu'à nouveau une fonction d'affichage et un tas de test.
Loading editor...
type tas_bleau = int array;; let tas_bleau_test = [|1; 2; 3; 17; 19; 36; 7; 25; 100; -1; -1; -1; -1; -1; -1|];;
La fonction afficher_tas du site dessine un tas stocké dans un tableau, sous forme d'arbre et de tableau. L'argument optionnel ~taille indique le nombre de cases utilisées :
Loading editor...
afficher_tas ~taille:9 tas_bleau_test;;
Vous pouvez aussi manipuler le tas ci-dessous : ajoutez des éléments et extrayez le minimum, en observant ce qui se passe dans le tableau.
Question 11
Réimplémentez les fonctions d'ajout et d'extraction sur les tas_bleau. Vos fonction devront ici modifier le tas original (sans le copier ou le renvoyer). Elles pourront renvoyer des erreurs si la taille du tableau est dépassé.
Loading editor...
Pour, par exemple, l'algorithme de Dijkstra, il manque une opération à implémenter (qui est plus simple à implémenter sur le tableau) : la mise à jour d'un noeud.
L'idée est de, si on augmente un noeud, on va le déplacer vers le bas, et sinon, on va le déplacer vers le haut.
Question 12
Écrivez une fonction maj: tas_bleau -> int -> int -> unit qui prend en argument un tas_bleau, un indice, et la nouvelle valeur du noeud à cet indice, et effectue les opération nécéssaires pour la mise à jour.
Loading editor...
III. File de priorité
Pour l'algorithme de Dijkstra, on a besoin d'une file de priorité : une structure qui stocke des éléments associés à des priorités (des entiers), avec les opérations ajouter et extraire_min (qui renvoie l'élément de plus petite priorité). On l'implémente avec un tas min stocké dans un tableau, comme dans la partie II, mais qui contient des couples (priorité, élément), et on garde en mémoire le nombre de cases utilisées.
Loading editor...
type 'a file_priorite = {
mutable taille : int;
donnees : (int * 'a) array (* couples (priorité, élément) *)
};;Question 13
Écrire une fonction creer (capacite : int) (defaut : 'a) : 'a file_priorite qui crée une file vide pouvant contenir capacite éléments, et une fonction est_vide : 'a file_priorite -> bool. Pourquoi a-t-on besoin d'un élément defaut ?
Loading editor...
Array.make a besoin d'une valeur pour remplir le tableau : les cases non utilisées contiennent (0, defaut), et ne seront jamais lues.
Question 14
Écrire une fonction ajouter (f : 'a file_priorite) (x : 'a) (p : int) : unit qui ajoute l'élément x avec la priorité p, en le plaçant dans la première case libre puis en le faisant remonter. On pourra écrire une fonction auxiliaire echanger (t : 'a array) (i : int) (j : int) : unit.
Loading editor...
Question 15
Écrire une fonction extraire_min (f : 'a file_priorite) : int * 'a qui retire et renvoie le couple de plus petite priorité, en mettant le dernier élément à la racine puis en le faisant descendre.
Loading editor...
Question 16
Tester votre file de priorité : ajouter des tâches avec des priorités, puis les extraire toutes. Dans quel ordre sortent-elles ?
Loading editor...
let f = creer 10 "";;
List.iter (fun (x, p) -> ajouter f x p) [("réviser", 3); ("manger", 1); ("dormir", 2); ("jouer", 5)];;
while not (est_vide f) do
let (p, x) = extraire_min f in
Printf.printf "%s (priorité %d)\n" x p
done;;Entrez le code donné en cours pour accéder à la solution :