(* This OCaml script was exported from a Jupyter notebook using an open-source software (under the MIT License) written by @Naereen from https://github.com/Naereen/Jupyter-Notebook-OCaml This software is still in development, please notify me of a bug at https://github.com/Naereen/Jupyter-Notebook-OCaml/issues/new if you find one *) (* # Table of Contents
*) (* # TP 7 - Programmation pour la préparation à l'agrégation maths option info TP 5 : Algorithmes gloutons et files de priorité. *) (* - En OCaml. *) (* In[1]: *) let print = Printf.printf;; Sys.command "ocaml -version";; (* In[2]: *) print_endline;; (* ---- ## Algorithme de Dijkstra - avec des files non mutables Déjà vu, on le retraite ici. *) (* ### Files de priorité min *) (* In[3]: *) (* file de priorité version non-mutable *) type 'a priopqueue = (int * 'a) list;; (* In[4]: *) (* file vide *) let vide : 'a priopqueue = [ ];; (* In[5]: *) (* [inserer x clef q] insere l'element [x] dans la file [q] avec le clef [x], et renvoie la nouvelle file ainsi créée. Termine avec une exception si la file contient déjà [x] *) let inserer (x:'a) (clef:int) (q:'a priopqueue) : 'a priopqueue = if List.exists (fun (_, v) -> x = v) q then failwith "l'element est déjà dans la file" else (clef,x) :: q ;; (* In[6]: *) (* [est_vide q] teste si la file [q] est vide *) let est_vide (q:'a priopqueue) : bool = (q = [ ]);; (* In[7]: *) (* [trouve_min_aux min_val min_clef q] renvoie un couple de clef minimale dans (min_val,min_clef)::q *) let rec trouve_min_aux (min_val:'a) (min_clef:int) (q:'a priopqueue) : int * 'a = match q with | [ ] -> (min_clef, min_val) | (clef, _) :: q when clef > min_clef -> trouve_min_aux min_val min_clef q | (clef, v) :: q -> trouve_min_aux v clef q ;; (* In[8]: *) (* [trouve_min q] renvoie un élément de clef minimale la file [q]. Lance une exception si la liste est vide *) let trouve_min (q:'a priopqueue) : 'a = match q with | [ ] -> failwith "trouve_min: la file est vide" | (clef, v) :: q -> snd (trouve_min_aux v clef q) ;; (* In[9]: *) let _ = trouve_min (inserer '1' 1 (inserer '2' 2 (inserer '3' 3 vide)));; let _ = trouve_min (inserer '1' 4 (inserer '2' 2 (inserer '3' 3 vide)));; (* In[10]: *) (* [supprime v q] renvoie une file contenant les éléments de [q], sauf [x]. [x] doit apparaitre une et une seule fois dans la file. *) let rec supprime (x:'a) (q:'a priopqueue) : 'a priopqueue = match q with | [ ] -> [ ] | (_, v) :: q when v=x -> q | (clef, v) :: q -> (clef, v) :: (supprime x q) ;; (* In[11]: *) (* [extraire_min q] renvoie un élément de q, de clef minimal, ainsi que la nouvelle file obtenue en supprimant cet élément; termine avec une exception si la file est vide *) let extraire_min (q:'a priopqueue) : 'a * 'a priopqueue = if q = [ ] then failwith "extraire_min: file vide" else let min = trouve_min q in (min, supprime min q) ;; (* In[12]: *) let _ = extraire_min (inserer '1' 1 (inserer '2' 2 (inserer '3' 3 vide)));; let _ = extraire_min (inserer '1' 4 (inserer '2' 2 (inserer '3' 3 vide)));; (* In[13]: *) (* [diminuer_clef q clef x] modifie la clef de l'élément [x] dans la file q en lui associant la nouvelle clef [clef], qui doit être inferieur à la clef actuelle de [x]. Termine avec une exception si la file ne contient pas [x] *) let rec diminuer_clef (x:'a) (clef:int) (q:'a priopqueue) : 'a priopqueue = match q with | [ ] -> failwith "diminuer_clef : l'élément n'est pas présent" | (_, v) :: q when v=x -> (clef, x) :: q | (c, v) :: q -> (c, v) :: diminuer_clef x clef q ;; (* In[14]: *) let f = inserer '1' 1 (inserer '2' 2 (inserer '3' 3 vide));; let _ = diminuer_clef '3' 0 f;; let _ = diminuer_clef '2' 0 f;; (* ### Graphe par tableau de listes d'adjacence *) (* In[15]: *) type sommet = int;; type graph = { taille: int; (* les sommets sont des entiers entre 0 et taille-1 *) adj: (int * sommet) list array; entree: sommet };; (* Ce qui suit est purement optionnel, ce n'était pas demandé, ne vous embêtez pas à chercher à tout comprendre, c'est simplement pour visualiser les graphes et les afficher ensuite. *) (* In[16]: *) let print = Printf.fprintf;; let dot outname (g:graph) (bold:(int*int) list) : unit = let f = open_out (outname ^ ".dot") in print f "digraph G {\n"; for i=0 to g.taille-1 do print f " som%d [label=\"%d\"];\n" i i done; for i=0 to g.taille-1 do List.iter (fun (c,j) -> let option = if List.mem (i,j) bold then ",style=bold" else "" in print f " som%d -> som%d [label=\"%d\"%s];\n" i j c option ) g.adj.(i); done; print f "}\n"; close_out f ;; let dot2svg outname = Sys.command (Printf.sprintf "dot -Tsvg %s.dot > %s.svg" outname outname);;; (* ### Exemple de visualisation de graphe *) (* In[17]: *) let s = 0 and a = 1 and b = 2 and c = 3 and d = 4;; let g1 = { taille = 5; entree = s; adj = [| [(2,a); (4,b); (2,c)]; (* adj(s) *) [(1,d)]; (* adj(A) *) [(4,d)]; (* adj(B) *) [(1,b)]; (* adj(C) *) [ ]; (* adj(D) *) |] };; (* In[18]: *) let _ = dot "TP7__g1" g1 [ ];; dot2svg "TP7__g1";; (* In[19]: *) Sys.command "cat TP7__g1.dot";; (* ![TP7__g1.svg](TP7__g1.svg) *) (* Le second argument permet d'afficher un certain chemin : *) (* In[20]: *) let _ = dot "TP7__g2" g1 [(0,3);(3,2);(2,4)];; (* In[21]: *) Sys.command "cat TP7__g2.dot";; dot2svg "TP7__g2";; (* ![](TP7__g2.svg) *) (* ### Dijkstra *) (* Une fois qu'on dispose de tout ça, écrire l'algorithme de Dijkstra est relativement rapide. - Voir [ce site](https://www.cs.usfca.edu/~galles/visualization/Dijkstra.html) pour de belles visualisations de l'algorithme de Dijkstra. - Et [cette page](https://jilljenn.github.io/tryalgo/tryalgo/tryalgo.html#module-tryalgo.dijkstra) pour une implémentation propre en Python ([lien direct vers le code](https://jilljenn.github.io/tryalgo/_modules/tryalgo/dijkstra.html#dijkstra)). *) (* In[22]: *) let dijkstra g = let q = ref vide in let dist = Array.init g.taille (fun i -> if i=g.entree then 0 else max_int ) in for i=0 to g.taille - 1 do (* initialisation de la file *) q := inserer i dist.(i) !q done; while not (est_vide !q) do let (x, q') = extraire_min !q in q := q'; (* ne pas oublier de mettre à jour la file *) (* on regarde les adjacents de x *) List.iter (fun (c,y) -> if dist.(y) > dist.(x) + c then begin dist.(y) <- dist.(x) + c; q := diminuer_clef y dist.(y) !q end ) g.adj.(x) done; dist ;; (* In[23]: *) let _ = dijkstra g1;; (* ### Contre-exemples Trouver des cas simples faisant échouer l’algorithme si une des hypothèses n'est pas satisfaite : par exemple un graphe non connexe, ou un graphe avec une arête de poids négatif. *) (* #### Un graphe non connexe *) (* In[32]: *) let s = 0 and a = 1 and b = 2 and c = 3 and d = 4 and e = 5 and f = 6;; let g2 = { taille = 7; entree = s; adj = [| [(2,a); (4,b); (2,c)]; (* adj(s) *) [(1,d)]; (* adj(A) *) [(4,d)]; (* adj(B) *) [(1,b)]; (* adj(C) *) [ ]; (* adj(D) *) [(5,f)]; (* adj(E) *) [ ]; (* adj(F) *) |] };; (* In[33]: *) let _ = dijkstra g2;; (* Oups, ça n'a pas l'air correct ! *) (* #### Un graphe avec un poids négatif *) (* In[44]: *) let s = 0 and a = 1 and b = 2 and c = 3 and d = 4;; let g3 = { taille = 5; entree = s; adj = [| [(2,a); (-4,b); (2,c)]; (* adj(s) *) [(1,d)]; (* adj(A) *) [(-4,d)]; (* adj(B) *) [(1,b)]; (* adj(C) *) [(2,b)]; (* adj(D) *) |] };; (* In[45]: *) let _ = dijkstra g3;; (* Oups, ça n'a pas l'air correct non plus ! *) (* ---- ## Algorithme de Dijkstra - avec des files mutables Déjà vu, on le retraite ici. *) (* ### Files de priorité min mutables *) (* In[46]: *) (* file de priorité version non-mutable *) type 'a priopqueue = (int * 'a) list ref;; (* In[47]: *) (* file vide *) let vide () : 'a priopqueue = ref [ ];; (* In[48]: *) (* [inserer x clef q] insere l'element [x] dans la file [q] avec le clef [x]. Termine avec une exception si la file contient déjà [x] *) let inserer (x:'a) (clef:int) (q:'a priopqueue) : unit = if List.exists (fun (_, v) -> x=v) !q then failwith "l'element est déjà dans la file" else q := (clef,x) :: !q ;; (* In[49]: *) (* [est_vide q] teste si la file [q] est vide *) let est_vide (q:'a priopqueue) : bool = (!q = [ ]);; (* In[50]: *) (* [trouve_min_aux min_val min_clef q] renvoie un couple de clef minimale dans (min_val,min_clef)::q *) let rec trouve_min_aux (min_val:'a) (min_clef:int) (q:(int*'a) list) : int * 'a = match q with | [ ] -> (min_clef, min_val) | (clef, _) :: q when clef > min_clef -> trouve_min_aux min_val min_clef q | (clef, v) :: q -> trouve_min_aux v clef q ;; (* In[51]: *) (* [trouve_min q] renvoie un élément de clef minimale la file [q]. Lance une exception si la liste est vide *) let trouve_min (q:(int*'a) list) : 'a = match q with | [ ] -> failwith "trouve_min: la file est vide" | (clef, v) :: q -> snd (trouve_min_aux v clef q) ;; (* In[52]: *) (* [supprime v q] renvoie une file contenant les éléments de [q], sauf [x]. [x] doit apparaitre une et une seule fois dans la file. *) let rec supprime (x:'a) (q:(int*'a) list) : (int*'a) list = match q with | [ ] -> [ ] | (_, v) :: q when v=x -> q | (clef, v) :: q -> (clef,v) :: (supprime x q) ;; (* In[53]: *) (* [extraire_min q] renvoie un élément de q, de clef minimal, et met à jour la file; termine avec une exception si la file est vide *) let extraire_min (q:'a priopqueue) : 'a = if !q = [ ] then failwith "extraire_min: file vide" else let min = trouve_min !q in q := supprime min !q; min ;; (* In[54]: *) (* [diminuer_clef q clef x] modifie la clef de l'élément [x] dans la file q en lui associant la nouvelle clef [clef], qui doit être inferieur à la clef actuelle de [x]. Termine avec une exception si la file ne contient pas [x] *) let diminuer_clef (x:'a) (clef:int) (q:'a priopqueue) : unit = let rec diminuer_aux (l:(int*'a) list) : (int*'a) list = match l with | [ ] -> failwith "diminuer_clef : l'élément n'est pas présent" | (_, v) :: q when v=x -> (clef, x) :: q | (c, v) :: q -> (c, v) :: diminuer_aux q in q := diminuer_aux !q ;; (* ### Dijkstra, 2ème version *) (* C'est aussi assez direct : *) (* In[55]: *) let dijkstra g = let q = vide () in let dist = Array.init g.taille (fun i -> if i=g.entree then 0 else max_int ) in let pere = Array.init g.taille (fun i -> i) in for i=0 to g.taille - 1 do (* initialisation de la file *) inserer i dist.(i) q; done; while not (est_vide q) do let x = extraire_min q in (* on regarde les adjacents de x *) List.iter (fun (c,y) -> if dist.(y) > dist.(x) + c then begin pere.(y) <- x; dist.(y) <- dist.(x) + c; diminuer_clef y dist.(y) q end) g.adj.(x) done; dist, pere ;; (* ### Exemples *) (* In[56]: *) let _ = dijkstra g1;; (* Et les contre-exemples maintenant : *) (* In[57]: *) let _ = dijkstra g2;; (* In[58]: *) let _ = dijkstra g3;; (* ---- ## Arbres couvrants de poids minimal On ne traite que l'algorithme de Prim. L'algorithme de Kruskal n'est pas plus compliqué, il utilise une autre structure de données (Union-Find, déjà traité aussi). *) (* ### Algorithme de Prim *) (* In[37]: *) let prim g = let q = vide () in let poids = Array.init g.taille (fun i -> if i=g.entree then 0 else max_int ) in let pere = Array.init g.taille (fun i -> i) in for i=0 to g.taille-1 do (* initialisation de la file *) inserer i poids.(i) q; done; while not (est_vide q) do let x = extraire_min q in (* on regarde les adjacents de x *) List.iter (fun (c,y) -> if poids.(y) > c then begin pere.(y) <- x; poids.(y) <- c; diminuer_clef y poids.(y) q end) g.adj.(x) done; Array.iteri (fun i p -> if i != p then Printf.printf " (%d, %d)\n" i p ) pere; poids, pere; ;; (* ### Exemple *) (* In[38]: *) let _ = prim g1;; (* --- ## Tas binaire min *) (* In[39]: *) (** {2 Leftist heaps, by Jean-Christophe Filliâtre} *) (**************************************************************************) (* *) (* Copyright (C) Jean-Christophe Filliâtre *) (* *) (* This software is free software; you can redistribute it and/or *) (* modify it under the terms of the GNU Library General Public *) (* License version 2.1, with the special exception on linking *) (* described in file LICENSE. *) (* *) (* This software is distributed in the hope that it will be useful, *) (* but WITHOUT ANY WARRANTY; without even the implied warranty of *) (* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *) (* *) (**************************************************************************) (* Leftist heaps. See for instance Chris Okasaki's "Purely Functional Data Structures" *) module type Ordered = sig type t val le: t -> t -> bool end exception Empty module Make(X : Ordered) : sig type t val empty : t val is_empty : t -> bool val insert : X.t -> t -> t val min : t -> X.t val extract_min : t -> X.t * t val merge : t -> t -> t val length : t -> int end = struct type t = E | T of int * X.t * t * t let rank = function E -> 0 | T (r,_,_,_) -> r let rec length = function E -> 0 | T (_,_,t1,t2) -> 1 + (length t1) + (length t2) let make x a b = let ra = rank a and rb = rank b in if ra >= rb then T (rb + 1, x, a, b) else T (ra + 1, x, b, a) let empty = E let is_empty = function E -> true | T _ -> false let rec merge h1 h2 = match h1,h2 with | E, h | h, E -> h | T (_,x,a1,b1), T (_,y,a2,b2) -> if X.le x y then make x a1 (merge b1 h2) else make y a2 (merge h1 b2) let insert x h = merge (T (1, x, E, E)) h let min = function E -> raise Empty | T (_,x,_,_) -> x let extract_min = function | E -> raise Empty | T (_,x,a,b) -> x, merge a b end;; (* ---- ## Codage de Huffman C'est un grand classique pour les leçons "Programmation dynamique" et "Algorithmique du texte". Le présenter en développement sans l'avoir implémenter est difficilement pardonnable. *) (*