Skip to main content Informatique en prépa - Lycée Malherbe 

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 :

De plus,

  1. 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.
  2. le tas doit être semi-complet, c'est à dire que tous les niveaux sauf le dernier doivent être remplis.
123171936725100
1 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
100 8

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 :

OCaml

Loading editor...

type tas = F | N of int * tas * int * tas;;

A chaque noeud N, on associe

On vous donne également une variable tas_test contenant le tas ci-dessus, et une fonction affiche_tas qui permet d'afficher un tas.

OCaml

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.

OCaml

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.

OCaml

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.

OCaml

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 est celle du fils gauche et celle du fils droit, on doit toujours avoir

OCaml

Loading editor...

On aimerait maintenant définir deux opérations sur les tas :

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.

123171936725100
1 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
100 8
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.

123171936725100
1 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
100 8
1 / 3

Tas initial

OCaml

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.

10023171936725
100 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
1 / 5

Tas initial

OCaml

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.

OCaml

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 !

123171936725100
1 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
100 8
1 / 6

Tas initial

OCaml

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

  1. Exprimez la hauteur du tas en fonction de n
  2. Exprimez la complexité des opérations d'ajout et d'extraction en fonction de la hauteur de l'arbre
  3. 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.

OCaml

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 :

À 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.

OCaml

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 :

OCaml

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.

123171936725100
1 0
2 1
3 2
17 3
19 4
36 5
7 6
25 7
100 8
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é.

OCaml

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.

OCaml

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.

OCaml

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 ?

OCaml

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.

OCaml

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.

OCaml

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 ?

OCaml

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 :