| Votre opinion sur le cours de ce matin : | Votre opinion sur le TD de la semaine dernière : | 
On rappelle qu'une file est une structure de données dans laquelle on peut mettre des éléments et en prendre, de sorte que les éléments pris le sont dans l'ordre dans lequel ils ont été mis (premier entré premier sorti). On rappelle aussi qu'un tas est un arbre dans lequel les clefs croissent le long des branches, c'est-à-dire que la clef d'un nœud est toujours inférieure ou égale à la clef de chacun de ses fils. Ici, une clef est un entier, qui représente une priorité.
L'objectif de ce TD est d'implémenter les tas binomiaux qui sont une structure particulière permettant de programmer efficacement des files de priorité. Celles-ci sont une variante des files dans lesquelles les éléments sont entrés avec une priorité (qui est un entier) et sortent par ordre de priorité : les éléments de plus petite priorité sortent en premier, et dans le cas où la file contient plusieurs éléments de priorité minimale, l'élément entré en premier sort en premier.
Le TD devra être réalisé en OCaml. On pourra utiliser soit utiliser Eclipse, soit un éditeur de texte classique (par exemple Emacs).
      Nous allons commencer par définir un module BinomialTree
      permettant de définir des tas fondés sur des arbres particuliers,
      appelés arbres binomiaux. Dans ces arbres un nœud peut avoir un
      nombre quelconque de fils, et l'ordre des fils importe.
    
'a tree d'arbre dont les nœuds sont
        étiquetés par une priorité qui est un entier, et
        une valeur de type quelconque 'a. Un nœud peut
        avoir un nombre quelconque de fils. La structure de données devra être
        immuable (on ne modifiera pas la priorité ou la valeur d'un nœud).
      node : int -> 'a -> 'a tree qui prend
        en argument une priorité et une valeur et renvoie un arbre à un nœud.
      L'ensemble des arbres binomiaux de rang k est défini inductivement par :
link qui prend deux arbres binomiaux
      de même rang k et crée un nouvel arbre binomial de rang k+1
        selon la méthode précédente.
      size qui calcule le nombre de nœuds d'un arbre. Il
        pourra être utile de simultanément définir une fonction
        auxiliaire size_list qui calcule la somme des tailles d'une
        liste d'arbres en utilisant la structure de code suivante :
let rec size t = ... and size_list = function | [] -> ... | t :: l -> ...Vérifier la propriété précédente sur quelques exemples :
let () =
  let zero = node 0 "a" in
  let one = link zero zero in
  let two = link one one in
  assert (size two = 4)
        
      rank qui calcule le
        rang d'un arbre et la tester :
        
let () =
  let zero = node 0 "a" in
  let one = link zero zero in
  let two = link one one in
  assert (rank two = 2)
        
      'a tree devra être abstrait (on veut pouvoir modifier
        son implémentation par la suite) et déclarer les fonctions ci-dessus
        (node, link et rank).
      link de sorte à lever une exception
        lorsque les deux arbres donnés en argument n'ont pas le même rang (c'est
        le seul cas dans lequel la fonction a du sens).
      rank étant maintenant couramment utilisée, on
        souhaiterait qu'elle soit effectuée le plus efficacement
        possible. Modifier la structure d'arbre 'a tree afin de
        maintenir le rang et modifier l'implémentation de rank pour
        qu'elle soit calculée en temps constant. On s'efforcera de conserver la
        même interface (mli) : la déclaration de 'a
        tree comme type abstrait a permis d'améliorer l'implémentation
        sans changer la signature du module.
      link de sorte que si ses arguments
        sont des tas (i.e. la priorité d'un nœud est toujours plus petite que
        celle de l'un de ses fils), le résultat soit encore un tas, quitte à
        échanger le rôle des deux arguments.
      Déposer les fichier BinomialTree.ml et BinomialTree.mli :
      Afin de s'exercer pour la partie suivante, nous allons implémenter dans un
      autre module Binary quelques manipulations simples d'entiers
      binaires codés comme des listes d'entiers : un entier binaire sera codé
      par la liste croissante des indices des bits à 1 dans son écriture
      binaire. Par exemple, l'entier 19, dont l'écriture binaire
      est 10011, sera codé par la liste [0;1;4]. On
      notera binary = int list le type des entiers binaires codés
      de cette façon. Le module aura l'interface Binary.mli suivante :
      
type binary
val to_int : binary -> int
val of_int : int -> binary
val add_bit : binary -> int -> binary
val add : binary -> binary -> binary
      
      On pourra partir du fichier Binary.ml suivant :
      
type binary = int list
let to_int b =
  (* à compléter *)
  assert false
let of_int n =
  (* à compléter *)
  assert false
let add_bit b k =
  (* à compléter *)
  assert false
let add b1 b2 =
  (* à compléter *)
  assert false
      
    
    to_int : binary -> int
        et of_int : int -> binary et testez-les :
        
let () =
  assert (to_int [0;1;4] = 19);
  assert (of_int 19 = [0;1;4])
        
        On peut calculer 2k via l'expression 1 lsl
        k (lsl signifie logical shift left).
        On peut obtenir le dernier bit d'un entier positif n en
        calculant n mod 2 (qui vaudra 0
        ou 1). On peut « effacer » le dernier bit d'un
        entier n en calculant n/2.
        Indice: vous pourrez écrire une fonction auxiliaire
        of_int_aux, de type int -> int -> binary,
        telle que of_int_aux convertit l'entier
        2kn en une liste.
        Intuitivement, l'entier n est un entier dont on a
        déjà « effacé » plusieurs bits (en le divisant
        par 2, comme indiqué plus haut), et l'entier k
        indique justement combien de bits ont déjà
        été « effacés ».
      add_bit : binary -> int -> binary qui calcule le
        résultat de la somme n+2k en binaire. Testez-la :
        
let () =
  assert (add_bit [0;1;4] 3 = [0;1;3;4]);
  assert (add_bit [0;1;4] 5 = [0;1;4;5]);
  assert (add_bit [0;1;4] 0 = [2;4])
        
      add : binary -> binary -> binary qui
        implémente l'addition générale et la tester sur quelques
        exemples :
        
let () =
  let add_int x y = to_int (add (of_int x) (of_int y)) in
  assert (add_int 19 33 = 52);
  assert (add_int 41 29 = 70);
  assert (add_int 33 85 = 118)
        
      Déposer le fichier Binary.ml :
      Nous allons maintenant implémenter, dans un nouveau
      module BinomialHeap, les tas binomiaux. Ce sont des files
      permettant de stocker efficacement des (multi)ensembles de paires priorité
      / valeur, d'en ajouter de nouvelles, et d'extraire une valeur de priorité
      minimale rapidement. L'idée pour stocker
      n valeurs va être la suivante. Supposons que les indices de bits
      à 1 dans l'écriture binaire de n soient i1,
      i2, ..., ik, c'est-à-dire que n =
      ∑p=1k2ip. Nous allons
      découper l'ensemble des valeurs en sous-ensembles de taille
      2ip, chacun de ces ensembles étant stocké dans
      les nœuds d'un arbre binomial de rang ip. Ainsi, un
      tas binomial sera une liste d'arbres binomiaux de rangs respectifs i1,
      i2, ..., ik avec i1 <
      i2 < ... < ip. Autrement dit, un tas
      binomial est
      une liste d'arbres binomiaux tels que
      
'a heap des tas binomiaux contenant
      des valeurs de type 'a.add_tree : 'a heap -> 'a tree -> 'a
      heap qui ajoute un arbre binomial supposé croissant (un tas) à une
      file. On veillera à maintenir les propriétés précédentes et l'on pourra
      s'inspirer de la fonction add_bit écrite précédemment.insert : 'a heap -> int -> 'a -> 'a
      heap qui ajoute un nouvel élément de priorité et de valeur données
à une file.find_min : 'a heap -> 'a qui renvoie
        l'élément de priorité minimale le plus ancien d'une file en temps
        linéaire par rapport au nombre d'arbres. (On ne demande pas ici
        d'extraire l'élément de la file; seulement de le trouver.)
        Il pourra être utile d'ajouter
        au module BinomialTree des fonctions permettant
        d'obtenir la clé
        et la valeur de la racine d'un arbre.
      
let () =
  let h = empty in
  let h = insert h 5 "a" in
  assert (find_min h = "a");
  let h = insert h 8 "b" in
  assert (find_min h = "a");
  let h = insert h 2 "c" in
  assert (find_min h = "c");
  let h = insert h 2 "d" in
  assert (find_min h = "c");
  let h = insert h 2 "e" in
  assert (find_min h = "c")
        
      Déposer les fichier BinomialHeap.ml et BinomialHeap.mli :
Les questions suivantes sont optionnelles. Elles permettent de voir l'utilisation que l'on peut faire des files de priorité.
Nous allons commencer par implémenter quelques autres fonctions utiles sur les files binomiales.
merge : 'a heap -> 'a heap -> 'a heap
        qui fusionne deux files, en vous inspirant de l'addition d'entiers
        binaires.
      extract_min_tree : 'a heap -> 'a tree * 'a
      heap qui isole un arbre dont la tête est de priorité minimale dans
      une file, et renvoie cet arbre, ainsi que la file privée de cet
      arbre.extract_min : 'a heap -> 'a * 'a
      heap qui extrait d'une file une valeur de priorité minimale de la
      file. Vérifier que cette fonction a une complexité logarithmique en le
        nombre d'éléments de la file.
let () =
  let h = empty in
  let h = insert h 1 "a" in
  let h = insert h 1 "b" in
  assert (fst (extract_min h) = "a");
  let h = insert h 1 "c" in
  assert (fst (extract_min h) = "a")
        
        
      Déposer le fichier BinomialHeap.ml :
      Nous allons finalement utiliser le module des files binomiales pour créer
      un module PriorityQueue implémentant les files de priorité
      (de type 'a heap).
    
create : unit -> 'a heap permettant de
        créer une file de priorité.push : 'a heap -> int -> 'a -> unit
        qui permet d'ajouter un élément, avec une priorité donnée, à une file.pop : 'a heap -> 'a permettant
        d'extraire l'élément de priorité minimale le plus ancien dans la file.
let () =
  let q = create () in
  push q 2 "a";
  push q 1 "b";
  assert (pop q = "b");
  assert (pop q = "a")
        
        et
        
let () =
  let q = create () in
  push q 1 "a";
  push q 2 "b";
  assert (pop q = "a");
  assert (pop q = "b")
        
      is_empty : 'a heap -> bool
        permettant de déterminer si une file est vide.size : 'a heap -> int déterminant le
      nombre d'éléments dans une file. Comment l'implémenter avec une complexité
      logarithmique en le nombre de ces éléments ?Déposer le fichier PriorityQueue.ml :
Le module des files de priorité peut être utilisé pour implémenter l'algorithme de Dijkstra qui permet de trouver le plus court chemin dans un graphe dirigé pondéré (chaque arrête est étiquetée par un entier indiquant sa longueur). On trouvera les fonctions de test mentionnées ci-dessous dans le fichier Graph.ml.
pop : 'a heap -> int
          * 'a pour qu'elle revoie la priorité en plus de la valeur.
      
let graph edges =
  let vertices = List.fold_left (fun v (s,d,t) -> max v (max s t)) 0 edges + 1 in
  let g = Array.init vertices (fun _ -> Array.make vertices 0) in
  List.iter (fun (s,d,t) -> g.(s).(t) <- d) edges;
  g
        
      
let dijkstra g source =
  let pq = create () in
  let vertices = Array.length g in
  let d = Array.make vertices max_int in
  d.(source) <- 0;
  push pq 0 source;
  while not (is_empty pq) do
    let pi, i = pop pq in
    for j = 0 to vertices - 1 do
      if g.(i).(j) > 0 then
        let dj = d.(i) + g.(i).(j) in
        if dj < d.(j) then begin
          d.(j) <- dj;
          push pq dj j
        end
    done
  done;
  d
        
      
let () =
  let g =
    graph
      [
        (1,2,2);
        (1,4,3);
        (2,1,3);
        (2,4,4);
        (2,2,5);
        (3,3,5);
        (4,2,6);
        (5,3,4);
        (5,2,6);
      ]
  in
  let d = dijkstra g 1 in
  for i = 0 to Array.length g - 1 do
    Printf.printf "distance to %d : %d\n" i d.(i)
  done
        
        doit afficher le résultat suivant :
        
distance to 0 : 4611686018427387903
distance to 1 : 0
distance to 2 : 2
distance to 3 : 3
distance to 4 : 6
distance to 5 : 4
distance to 6 : 6
        
      Une solution vous est proposée.