une boˆıte d’outils pour la programmation en camlpauillac.inria.fr/~cheno/taupe/poly.pdf · b...

136
Une boˆ ıte d’outils pour la programmation en Caml Laurent Ch´ eno avec la collaboration de Alain B` eges ´ et´ e 1995 Lyc ´ ee Louis-le-Grand, Paris

Upload: tranbao

Post on 15-Oct-2018

217 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Une boıte d’outilspour la programmation

en Caml

Laurent Chenoavec la collaboration de

Alain Beges

ete 1995

Lycee Louis-le-Grand, Paris

Page 2: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 3: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Table des matieres

I Structures de donnees 13

1 Piles 151.1 Definition abstraite de la structure . . . . . . . . . . . . . . . . . . . . . . . 151.2 Une structure concrete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.3 Une implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2 Files d’attente 192.1 Definition abstraite de la structure . . . . . . . . . . . . . . . . . . . . . . . 192.2 Interface proposee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.3 Une structure concrete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3 Une structure pour les listes circulaires 233.1 Description de la structure . . . . . . . . . . . . . . . . . . . . . . . . . . . 233.2 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4 Files de priorite 274.1 Definition abstraite de la structure . . . . . . . . . . . . . . . . . . . . . . . 274.2 Une structure concrete : les tas . . . . . . . . . . . . . . . . . . . . . . . . . 274.3 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.4 Preuve et evaluation de l’algorithme . . . . . . . . . . . . . . . . . . . . . . 33

5 Partitions d’un ensemble 355.1 Definition abstraite de la structure . . . . . . . . . . . . . . . . . . . . . . . 355.2 Un autre point de vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365.3 Interface proposee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365.4 Une structure concrete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375.5 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395.6 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

II Quelques algorithmes sur les graphes 43

6 Generalites sur les graphes 456.1 Vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456.2 Une representation avec des listes . . . . . . . . . . . . . . . . . . . . . . . 466.3 Une representation matricielle . . . . . . . . . . . . . . . . . . . . . . . . . 476.4 Conversion d’une representation a l’autre . . . . . . . . . . . . . . . . . . . 47

7 Les algorithmes de Floyd et de Warshall 497.1 Le probleme des plus courts chemins . . . . . . . . . . . . . . . . . . . . . 497.2 L’algorithme de Floyd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507.3 Implementation en Caml de N ∪ {+∞} . . . . . . . . . . . . . . . . . . . . . 517.4 Implementation en Caml de l’algorithme de Floyd . . . . . . . . . . . . . . . 52

3

Page 4: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

4 TABLE DES MATIERES

7.5 L’algorithme de Warshall . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

8 L’algorithme de Dijkstra 558.1 Utilite d’un nouvel algorithme . . . . . . . . . . . . . . . . . . . . . . . . . 558.2 Description de l’algorithme de Dijkstra . . . . . . . . . . . . . . . . . . . . 558.3 Implementation de l’algorithme de Dijkstra en Caml . . . . . . . . . . . . . . 578.4 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

9 Arbre couvrant minimal d’un graphe non oriente 619.1 Presentation du probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . 619.2 Algorithme de Prim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

9.2.1 Description informelle . . . . . . . . . . . . . . . . . . . . . . . . . 639.2.2 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . 639.2.3 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

9.3 Algorithme de Kruskal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669.3.1 Description de l’algorithme . . . . . . . . . . . . . . . . . . . . . . 669.3.2 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . 669.3.3 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

III Quelques algorithmes de geometrie combinatoire 69

10 Enveloppe convexe d’un ensemble de points du plan 7110.1 Rappels sur la convexite . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7110.2 Algorithme de Graham-Andrew . . . . . . . . . . . . . . . . . . . . . . . . 72

10.2.1 La marche de Graham . . . . . . . . . . . . . . . . . . . . . . . . . 7210.2.2 L’algorithme de Graham est optimal . . . . . . . . . . . . . . . . . . 7310.2.3 L’amelioration de l’algorithme de Graham par Andrew . . . . . . . . 74

10.3 Strategie diviser pour regner . . . . . . . . . . . . . . . . . . . . . . . . . . 7510.3.1 Algorithme de Shamos . . . . . . . . . . . . . . . . . . . . . . . . . 7510.3.2 Algorithme de Preparata et Hong . . . . . . . . . . . . . . . . . . . 76

10.4 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7810.4.1 Le tri-fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7810.4.2 Quelques fonctions utilitaires . . . . . . . . . . . . . . . . . . . . . 7810.4.3 La recherche des points extremaux d’un polygone convexe . . . . . . 7810.4.4 L’algorithme de Preparata et Hong : les ponts inferieur et superieur . . 8010.4.5 La fonction enveloppeConvexe . . . . . . . . . . . . . . . . . . . 80

11 Problemes de proximite dans le plan 8311.1 Quelques problemes classiques . . . . . . . . . . . . . . . . . . . . . . . . . 83

11.1.1 La paire la plus rapprochee . . . . . . . . . . . . . . . . . . . . . . 8311.1.2 Les plus proches voisins . . . . . . . . . . . . . . . . . . . . . . . . 8311.1.3 Triangulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

11.2 La paire la plus rapprochee . . . . . . . . . . . . . . . . . . . . . . . . . . . 8411.2.1 Premiere analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8411.2.2 Un cas particulier : la dimension 1 . . . . . . . . . . . . . . . . . . . 8411.2.3 Retour au probleme plan . . . . . . . . . . . . . . . . . . . . . . . . 8511.2.4 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . 87

11.3 Diagrammes de Voronoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9111.3.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9111.3.2 Quelques proprietes . . . . . . . . . . . . . . . . . . . . . . . . . . 9111.3.3 Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

11.4 Algorithme de Tsai . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9611.4.1 Quelques preliminaires . . . . . . . . . . . . . . . . . . . . . . . . . 96

Page 5: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

TABLE DES MATIERES 5

11.4.2 Premiere etape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9711.4.3 Deuxieme etape . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9711.4.4 Troisieme etape . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9911.4.5 Remarques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9911.4.6 Les diagrammes de Voronoı revisites . . . . . . . . . . . . . . . . . . 100

11.5 Implementation en Caml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10011.5.1 Quelques utilitaires . . . . . . . . . . . . . . . . . . . . . . . . . . 10011.5.2 Deuxieme etape de l’algorithme . . . . . . . . . . . . . . . . . . . . 10211.5.3 Troisieme etape . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10411.5.4 Determination du diagramme de Voronoı . . . . . . . . . . . . . . . 10511.5.5 Affichage dans une fenetre graphique . . . . . . . . . . . . . . . . . 106

IV Annexes 109

A Un petit manuel de reference de Caml 111A.1 Types de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

A.1.1 Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112A.1.2 Booleens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112A.1.3 Nombres entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112A.1.4 Nombres a virgule ou “flottants” . . . . . . . . . . . . . . . . . . . 112A.1.5 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112A.1.6 Chaınes de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . 113A.1.7 Couples et multiplets . . . . . . . . . . . . . . . . . . . . . . . . . . 113A.1.8 Listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114

A.2 Types construits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115A.2.1 Types produit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115A.2.2 Types somme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115A.2.3 Types parametres . . . . . . . . . . . . . . . . . . . . . . . . . . . 115A.2.4 Types (mutuellement) recursifs . . . . . . . . . . . . . . . . . . . . 115A.2.5 Abreviations de type . . . . . . . . . . . . . . . . . . . . . . . . . 115

A.3 Structures de controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116A.3.1 Sequencement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116A.3.2 Operateurs & et or . . . . . . . . . . . . . . . . . . . . . . . . . . . 116A.3.3 Conditionnelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116A.3.4 Filtrage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116A.3.5 Boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119A.3.6 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

A.4 Effets de bord : types mutables . . . . . . . . . . . . . . . . . . . . . . . . . 120A.4.1 Types mutables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120A.4.2 Autre type mutable : les references . . . . . . . . . . . . . . . . . . . 120A.4.3 Autre type mutable : les vecteurs . . . . . . . . . . . . . . . . . . . . 121

A.5 Effets de bord : entrees / sorties . . . . . . . . . . . . . . . . . . . . . . . . 122A.5.1 Entrees/sorties clavier/ecran . . . . . . . . . . . . . . . . . . . . . . 122A.5.2 Fichiers texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122A.5.3 Fichiers binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

A.6 Effets de bord : la bibliotheque graphique . . . . . . . . . . . . . . . . . . . 123

B Quelques idees pour la programmation fonctionnelle 125B.1 Fonctionnelles en guise de structures de controle . . . . . . . . . . . . . . . 126

B.1.1 Un exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126B.1.2 Retour au probleme general : la sequence . . . . . . . . . . . . . . . 127B.1.3 Boucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127B.1.4 Boucle de comptage . . . . . . . . . . . . . . . . . . . . . . . . . . 128

Page 6: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

6 TABLE DES MATIERES

B.2 Gestion de suites infinies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128B.2.1 L’idee de l’evaluation paresseuse . . . . . . . . . . . . . . . . . . . . 128B.2.2 Quelques suites simples . . . . . . . . . . . . . . . . . . . . . . . . 129B.2.3 La suite des nombres premiers . . . . . . . . . . . . . . . . . . . . . 130

C Glossaire franco-anglais 133

D Glossaire anglo-francais 135

Page 7: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Liste des figures

2.1 La structure de liste doublement chaınee . . . . . . . . . . . . . . . . . . . 20

4.1 Un arbre tournoi parfait, i.e. un tas . . . . . . . . . . . . . . . . . . . . . . 284.2 Insertion d’une nouvelle cle : la cle 0 . . . . . . . . . . . . . . . . . . . . . . 284.3 L’extraction de la cle minimale : version incorrecte . . . . . . . . . . . . . . . 284.4 L’extraction de la cle minimale : version finale . . . . . . . . . . . . . . . . . 29

5.1 Une foret associee a une partition . . . . . . . . . . . . . . . . . . . . . . . 375.2 Une foret moins profonde associee a la meme partition . . . . . . . . . . . . . 375.3 La compression des chemins . . . . . . . . . . . . . . . . . . . . . . . . . . 38

6.1 Un exemple de graphe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

7.1 Un graphe sans plus court chemin . . . . . . . . . . . . . . . . . . . . . . . 507.2 Un graphe exemple pour l’algorithme de Floyd . . . . . . . . . . . . . . . . 53

8.1 Une etape de l’algorithme de Dijkstra . . . . . . . . . . . . . . . . . . . . . 56

9.1 Arbre couvrant minimal apres partition des sommets . . . . . . . . . . . . . 62

10.1 Un exemple d’enveloppe convexe dans le plan . . . . . . . . . . . . . . . . . 7110.2 Classification des angles au sommet d’un polygone . . . . . . . . . . . . . . 7210.3 La marche de Graham . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7310.4 Optimalite de l’algorithme de Graham . . . . . . . . . . . . . . . . . . . . . 7410.5 L’idee de Andrew . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7410.6 Les deux cas : p est interieur ou exterieur a P2 . . . . . . . . . . . . . . . . 7610.7 L’algorithme de Preparata et Hong . . . . . . . . . . . . . . . . . . . . . . . 77

11.1 La relation→ de plus proche voisinage . . . . . . . . . . . . . . . . . . . . 8411.2 Diviser pour regner sur une droite . . . . . . . . . . . . . . . . . . . . . . . 8411.3 Diviser pour regner dans un plan . . . . . . . . . . . . . . . . . . . . . . . 8511.4 La position la plus defavorable, avec 5 points a visiter . . . . . . . . . . . . . 8611.5 Un exemple de diagramme de Voronoı . . . . . . . . . . . . . . . . . . . . . 9211.6 Support de la demonstration du theoreme 8 . . . . . . . . . . . . . . . . . . 9311.7 La triangulation de Delaunay . . . . . . . . . . . . . . . . . . . . . . . . . 9511.8 Illustration du critere du max-min . . . . . . . . . . . . . . . . . . . . . . . 9611.9 Deuxieme etape de l’algorithme de Tsai . . . . . . . . . . . . . . . . . . . . 9811.10La derniere etape de l’algorithme de Tsai . . . . . . . . . . . . . . . . . . . 9911.11Une copie d’ecran de mon Macintosh . . . . . . . . . . . . . . . . . . . . . 108

7

Page 8: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 9: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Liste des programmes

1.1 L’interface Piles.mli . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.2 Une session exemple : utilisation des piles . . . . . . . . . . . . . . . . . . . 171.3 Piles.ml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.1 L’interface Files d attente.mli . . . . . . . . . . . . . . . . . . . . . . 192.2 Files d attente.ml . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213.1 L’interface Cycles.mli pour les listes circulaires . . . . . . . . . . . . . . . 233.2 La premiere partie du fichier Cycles.ml qui implemente les listes circulaires 243.3 La seconde partie du fichier Cycles.ml qui implemente les listes circulaires . 254.1 L’interface Tas.mli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.2 Debut de Tas.ml, une gestion par les tas des files de priorite . . . . . . . . . 314.3 Fin de Tas.ml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325.1 L’interface Partitions.mli (sauf la definition des types) . . . . . . . . . . 365.2 L’interface Partitions.mli . . . . . . . . . . . . . . . . . . . . . . . . . 405.3 Partitions.ml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.1 Definition Caml de la structure de graphe . . . . . . . . . . . . . . . . . . . 476.2 Conversion de la representation en listes vers la representation matricielle . . 486.3 Conversion de la representation matricielle vers la representation en listes . . 487.1 L’ensemble N ∪ {+∞} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517.2 L’algorithme de Floyd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527.3 La reponse de Caml sur notre exemple pour l’algorithme de Floyd . . . . . . 537.4 L’algorithme de Warshall . . . . . . . . . . . . . . . . . . . . . . . . . . . 548.1 Les entiers etendus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578.2 L’algorithme de Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.1 Les entiers etendus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639.2 L’algorithme de Prim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659.3 L’algorithme de Kruskal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6710.1 Le tri-fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7810.2 Recherche de l’enveloppe convexe : utilitaires . . . . . . . . . . . . . . . . . 7910.3 Recherche de l’enveloppe convexe : les points extremaux . . . . . . . . . . . 7910.4 Recherche de l’enveloppe convexe : les ponts inferieur et superieur . . . . . . 8110.5 Recherche de l’enveloppe convexe : l’algorithme proprement dit . . . . . . . 8211.1 Paire la plus proche : quelques utilitaires . . . . . . . . . . . . . . . . . . . 8711.2 Paire la plus proche : coupure d’une liste . . . . . . . . . . . . . . . . . . . 8811.3 Paire la plus proche : le cœur du probleme . . . . . . . . . . . . . . . . . . 8911.4 Paire la plus proche : la fonction principale . . . . . . . . . . . . . . . . . . 9011.5 Quelques utilitaires pour l’algorithme de Tsai . . . . . . . . . . . . . . . . . 10011.6 Algorithme de Tsai : 1ere etape . . . . . . . . . . . . . . . . . . . . . . . . 10111.7 Algorithme de Tsai : 2e etape . . . . . . . . . . . . . . . . . . . . . . . . . 10311.8 Algorithme de Tsai : 3e etape . . . . . . . . . . . . . . . . . . . . . . . . . 10411.9 Construction du diagramme de Voronoı . . . . . . . . . . . . . . . . . . . . 10511.10 Affichage graphique des diagrammes de Voronoı de nuages de points aleatoires 107B.1 Une version fort laide avec references . . . . . . . . . . . . . . . . . . . . . 126

9

Page 10: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10 LISTE DES PROGRAMMES

B.2 Une version fonctionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 127B.3 La version finale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127B.4 Utilisation des suites infinies . . . . . . . . . . . . . . . . . . . . . . . . . 130B.5 Implementation des suites infinies . . . . . . . . . . . . . . . . . . . . . . 131

Page 11: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Introduction

Ce poly comprend trois parties : la premiere, intitulee Structures de donnees, decrit des struc-tures qu’on rencontre dans toutes sortes d’algorithmes classiques. On peut voir les programmesCaml ecrits pour les implementer comme des briques de base de plus gros programmes, commeune librairie de fonctions, pour parler davantage comme un informaticien.dans la suite, nous

prefererons le termebibliotheque alibrairie, qui n’estqu’une pauvretraduction del’anglais library

Ensuite on trouve une partie sur les graphes, ou nous exposons — et implementons enCaml — quelques uns des algorithmes les plus classiques sur les graphes.

Une derniere partie s’interesse a ce qu’on appelle communement la geometrie combinatoire.Il s’agit de detailler quelques algorithmes classiques de geometrie comme par exemple larecherche de l’enveloppe convexe d’un ensemble de points du plan.

Nous avons fait figurer en annexe un petit manuel de reference — ecrit en collaborationavec A. Beges — qui ne pretend pas se substituer aux deux ouvrages de X. Leroy et P. Weis.

On trouvera egalement en annexe quelques indications de programmation en Caml pourmaıtriser le style fonctionnel, ce qui sera d’autant plus utile qu’on aura deja travaille dans desmaıtriser ou

apprivoiser. . . langages comme C ou Pascal.Enfin on fournit un petit glossaire dans les deux sens anglais vers francais et inversement.Pour terminer cette petite introduction, je voudrais remercier Bruno Petazzoni, qui a bien

voulu assurer la corvee de la relecture de tout ce texte, et qui a releve une collection de fautesd’orthographe ou de typographie a faire rougir mes instituteurs.

11

Page 12: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 13: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Partie I

Structures de donnees

13

Page 14: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 15: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 1

Piles

1.1 Definition abstraite de la structure

Une pile est une structure qui autorise les trois operations fondamentales suivantes :pile≡ stack

• l’empilage d’un objet, c’est-a-dire son insertion dans la structure ;

• le depilage du dernier objet qui a ete empile dans la structure ;

• le test qui determine si la pile est vide ou non.

En anglais on dira push pour empiler, et pop pour depiler.On ajoute parfois une procedure qui permet simplement de visiter le sommet de pile,

c’est-a-dire l’objet qui serait depile au moment considere. Il faut noter que les proceduresprecedentes permettent d’obtenir le meme resultat : pour visiter le sommet de pile, on depilele sommet de pile, on le recopie sur son cahier, et on le rempile pour laisser la pile dans l’etatou on l’avait trouvee en entrant.

Les Americains utilisent souvent l’expression LIFO, acronyme de Last In, First Out, pourillustrer que c’est toujours l’objet qui a ete empile en dernier qui est depile en premier, ou,si l’on prefere, que l’on procede comme avec une pile d’assiettes : c’est toujours la dernieresauf peut-etre chez

M. Tex Avery assiette posee (tout en haut de la pile) qu’on retire la premiere.

1.2 Une structure concrete

Il n’est pas difficile d’imaginer une structure simple pour implementer les piles. Il suffit eneffet d’une liste : empiler un objet obj dans la pile pile revient tout betement a remplacer lapile par obj :: pile ; depiler est un simple appel a la fonction hd (head) de la bibliothequestandard, qui renvoie le premier element de la liste.

Nous allons ici decrire une structure plus complexe. Pourquoi?Il est vrai que Caml se charge tout seul de gerer sa memoire dans le cas de la gestion des

listes. On ne peut cependant ignorer que les listes Caml sont des listes chaınees, et donc quechaque cellule de liste utilise la memoire necessaire a l’objet meme qu’elle contient mais aussia un lien (un pointeur, si on veut) vers la cellule suivante de la liste. On peut essayer — quitteun pointeur, ca cree

des liens a compliquer la structure et donc les operations qui la gerent — d’avoir un meilleur controlesur cette gestion de la memoire.

On propose donc d’utiliser une liste chaınee non pas de cellules contenant 1 objet, maisde tableaux d’objets de taille fixe (des blocs). Au moment d’empiler, ou bien on a encore assezde place dans les differents blocs alloues, et c’est tout bon, ou bien on manque de place, et onalloue un nouveau bloc de memoire sous la forme d’un nouveau tableau.

Bien entendu, il faut gerer la liste de blocs et noter la position dans ce bloc du sommet depile. C’est le prix a payer pour maintenir la structure. D’un autre cote si chaque bloc comporte

15

Page 16: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

16 PILES

k objets, on n’aura plus besoin que d’environ n/k pointeurs au lieu des n de la structurenaıve.

1.3 Une implementation en Caml

La pile utilisera une liste des blocs alloues, que nous appelleronsliste_des_blocs_alloues ;deux entiers, l’indice du sommet de pile dans son bloc, note indice_dans_bloc, et le nombrede blocs qu’on a deja alloues depuis la naissance de la pile, nb_blocs_reserves.

Au debut la liste des blocs est vide, le nombre de blocs alloues est nul, et, conventionnel-lement, l’indice dans le bloc courant k− 1, si k est la taille de chaque bloc alloue.

Pour depiler, on declenchera une exception Pile_Vide si le nombre de blocs alloues estnul. Sinon, les variables definies ci-dessus permettent de recuperer directement le sommet depile. Reste a les mettre a jour, en decrementant l’indice dans le bloc, sauf s’il etait nul : alorsil faudrait aussi retrancher le bloc courant de la liste des blocs, et positionner l’indice dans lebloc du sommet de pile a k− 1. La consultation du sommet de pile se fait de facon analogue.

Pour empiler, si l’indice dans le bloc est au plus egal a k−2, on se contente de l’incrementeret de ranger l’objet a empiler dans la case designee du bloc. Sinon on cree un nouveau blocqu’on ajoute a la liste.

Chaque nouvelle pile doit gerer sa propre liste de blocs, et ses propres index : le nom-bre de blocs alloues, la position courante dans le bloc. On ne veut evidemment pas inon-der le monde Caml de variables globales en trop grand nombre, ni d’ailleurs permettre al’utilisateur de modifier les variables de la pile, sous peine de se retrouver dans des si-tuations perilleuses. On va donc encapsuler les differents elements necessaires a la gestion ce texte est imprime

dans la policeApollo. . .

de la pile dans la pile elle-meme. Comme on aura ainsi interdit a l’utilisateur l’acces auxvariables de la pile, la fonction cree_pile qu’on va ecrire renverra non pas un pseudo-objet de type pile, mais les fonctions de gestion de la pile, sous la forme d’un quadruplet(depiler,empiler,visiter,test_a_vide) de fonctions. cree_pile aura deux argu-ments : le premier sera un objet du type de ceux que l’on veut empiler (Caml l’exige pour sontypage), le second la taille des blocs alloues, en nombre d’objets.

On trouvera ci-dessous l’interface correspondante, et, plus loin, un exemple d’utilisationde la structure dans une session type.

Programme 1.1 L’interface Piles.mli

1 exception Pile_Vide ;;

23 value cree_pile : ’a -> int -> (unit -> ’a) * (’a -> unit) * (unit -> ’a) * (unit ->

bool) ;;

45 (* (depiler,empiler,visiter,test_a_vide) *)

Montrons maintenant comment on peut, en Caml, encapsuler constantes et variablesdans une procedure. Ce procede, tres general, sera regulierement utilise tout le longde nos exemples. Il permet en quelque sorte de mimer (et en mieux, dans un certain

sens) la programmation objet qu’on retrouve dans d’autres langages de programmation.Nous ecrirons quelque chose du genre :

let procedure_englobante arg1 arg2 =

let var1 = ref init1 and var2 = ref init2 and ...

and k1 = expr1 and k2 = expr2 and ...

in

let f1 x y = ...

and f2 w = ...

in (f1,f2,...) ;;

Nous assurons ainsi que seules les procedures f1, f2 ont acces aux variables var1, var2, etaux constantes k1, k2. Remarquons la difference entre les references dont la valeur pourra

Page 17: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

1.3. UNE IMPLEMENTATION EN CAML 17

etre modifiee et les constantes qui ne correspondent qu’a des definitions. Notons enfin que lesn’est pas mutable quiveut arguments arg1 et arg2 de la procedure englobante jouent un peu le meme role que k1 et k2

pour les fonctions f1 et f2.

Programme 1.2 Une session exemple : utilisation des piles

1 > Caml Light version 0.6

23 #load_object "Piles.zo";;

4 - : unit = ()

5 ##open "Piles" ;;

6 #let (pop,push,scan,vide) = cree_pile "bidon" 10 ;;

7 vide : unit -> bool = <fun>

8 scan : unit -> string = <fun>

9 push : string -> unit = <fun>

10 pop : unit -> string = <fun>

11 #vide() ;; (* la pile est-elle vide ? *)

12 - : bool = true

13 #do_list push ["a";"b";"c";"d";"e";"f";"g";"h";"i";"j";"k";"l";"z"] ;;

14 - : unit = ()

15 #scan() ;; (* visite du sommet de pile *)

16 - : string = "z"

17 #scan() ;; (* toujours le meme *)

18 - : string = "z"

19 #pop() ;; (* la on le depile ! *)

20 - : string = "z"

21 #scan() ;; (* la preuve : *)

22 - : string = "l"

23 #push "m" ;; push "n" ;;

24 - : unit = ()

25 #- : unit = ()

26 #pop() ;; pop() ;; pop() ;; pop() ;; pop() ;;

27 pop() ;; pop() ;; pop() ;; pop() ;; pop() ;;

28 - : string = "n"

29 #- : string = "m"

30 #- : string = "l"

31 #- : string = "k"

32 #- : string = "j"

33 #- : string = "i"

34 #- : string = "h"

35 #- : string = "g"

36 #- : string = "f"

37 #- : string = "e"

38 #pop() ;; pop() ;; pop() ;; pop() ;;

39 - : string = "d"

40 #- : string = "c"

41 #- : string = "b"

42 #- : string = "a"

43 #pop() ;;

44 Uncaught exception: Pile_Vide

45 #

Dans le cas qui nous interesse, nous encapsulons les trois variables explicitees ci-dessus,liste_des_blocs_alloues, nb_blocs_reserves et indice_dans_bloc. Le depilagecommence par la selection du sommet de pile a qu’on trouvera dans le premier bloc de la listed’allocation, et continue par la mise a jour des variables de la pile. La consultation du sommetde pile est encore plus simple. Enfin l’empilage pourra en cas de besoin invoquer la fonctionmake_vect pour allouer un nouveau bloc de memoire.

On trouvera l’integralite du fichier Piles.ml ci-dessous.Remarque : une fois compilee l’interface Piles.mli, on dispose d’un fichier Piles.zi, et

il n’est plus utile de redeclarer l’exception Pile_Vide. C’est ce qui explique le commentaireen ligne 1.

Page 18: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

18 PILES

Programme 1.3 Piles.ml

1 (* exception Pile_Vide ;; *)

23 (* cree_pile attend deux arguments: le premier n’a d’utilite *)

4 (* que pour fixer le type des elements de la pile *)

5 (* le second est un entier decrivant la taille des blocs *)

6 (* d’allocation memoire successivement crees *)

7 (* si on s’attend a une taille maximale (a peu pres) de N *)

8 (* il paraıt intelligent de la signaler lors de cet appel *)

9 (* la fonction renvoie les fonctions standard d’acces a *)

10 (* une pile : a savoir le depilage : pop : unit -> ’a *)

11 (* la consultation : scan : unit -> ’a *)

12 (* l’empilage : push : ’a -> unit *)

13 (* le test a vide : est_vide : unit -> bool *)

1415 let cree_pile x k =

16 if k <= 0 then failwith "cree_pile objet_type taille_du_bloc attend une

taille_du_bloc > 0"

17 else

18 let liste_des_blocs_alloues = ref ([ ])

19 and nb_blocs_reserves = ref 0

20 and indice_dans_bloc = ref (k-1)

21 in

22 let pop () =

23 if !nb_blocs_reserves = 0 then raise Pile_Vide

24 else

25 begin

26 let a = (hd !liste_des_blocs_alloues).(!indice_dans_bloc)

27 in

28 if !indice_dans_bloc = 0 then

29 begin

30 indice_dans_bloc := k-1 ;

31 nb_blocs_reserves := !nb_blocs_reserves - 1 ;

32 liste_des_blocs_alloues := tl !liste_des_blocs_alloues

33 end

34 else indice_dans_bloc := !indice_dans_bloc - 1 ;

35 a

36 end

37 and scan () =

38 if !nb_blocs_reserves = 0 then raise Pile_Vide

39 else (hd !liste_des_blocs_alloues).(!indice_dans_bloc)

40 and push a =

41 if !indice_dans_bloc < k-1 then

42 begin

43 indice_dans_bloc := !indice_dans_bloc + 1 ;

44 (hd !liste_des_blocs_alloues).(!indice_dans_bloc) <- a

45 end

46 else

47 begin

48 let nouveau_bloc = make_vect k x

49 in

50 nb_blocs_reserves := 1 + !nb_blocs_reserves ;

51 liste_des_blocs_alloues := nouveau_bloc :: !liste_des_blocs_alloues ;

52 indice_dans_bloc := 0 ;

53 nouveau_bloc.(0) <- a

54 end

55 and est_vide () = !nb_blocs_reserves = 0

56 in (pop,push,scan,est_vide) ;;

Page 19: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 2

Files d’attente

2.1 Definition abstraite de la structure

Une file d’attente, on dit parfois aussi une queue (y compris en anglais, d’ailleurs), est unestructure de donnees qui permet les operations suivantes :

• l’insertion d’un nouvel objet dans la structure ;

• la suppression de l’objet insere le premier dans la structure ;

• le test qui determine si la file est vide ou non.

On ajoutera eventuellement une fonction qui renvoie la liste des elements de la file.On aura compris pourquoi cette structure s’appelle une file d’attente : comme dans la queue

devant un guichet, le premier arrive est celui qui s’en va le premier. On traite les personnesdans l’ordre ou elles se sont presentees. Il s’agit donc d’une structure de type FIFO : First In,First Out, quand la structure de pile etait du type LIFO.

2.2 Interface proposee

D’une facon analogue a ce qui a ete fait pour les piles, nous ecrirons une fonction que nousappellerons cree_file_d_attente et qui prend en argument un objet du type de ceuxqu’on veut utiliser dans la file et qui renvoie un quadruplet de fonctions d’acces a la struc-Caml veut des types !ture : (ajoute, extrait, est vide,en liste) qui respectivement insere un objet, ex-trait l’objet le plus ancien, teste si la file est vide, et rend la liste des objets presents.

Voici l’interface correspondante :

Programme 2.1 L’interface Files d attente.mli

1 exception File_Vide ;;

2 value cree_file_d_attente : ’a -> (’a -> unit) * (unit -> ’a) * (unit -> bool) *

(unit -> ’a list) ;;

2.3 Une structure concrete

Une premiere idee pour implementer les files d’attente consiste a utiliser une liste : on insererapar exemple les nouveaux objets en tete de liste, et on trouvera le plus ancien en queue deliste. Evidemment, ce n’est pas si simple, car pour trouver la queue de liste il faudra parcourirtoute la liste, et supprimer cet element semble peu evident. Inversons donc la question : on

19

Page 20: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

20 FILES D’ATTENTE

1 2 3 4

Figure 2.1: La structure de liste doublement chaınee

insere en queue de liste et on supprime en tete. Cette fois c’est la suppression qui est evidente.En revanche inserer un objet en queue de liste demande a nouveau un parcours complet de laliste.

Bref, la structure de liste que nous offre Caml semble mal adaptee a notre probleme.Nous allons construire une structure plus appropriee : les listes doublement chaınees.Il s’agit de listes formees de cellules comprenant un objet de la liste, et des liens sur les

cellules precedente et suivante, quand elles existent, ou sur Nil sinon. Un petit schema (voir Nil est a la terreci-dessus) explique la structure d’une liste doublement chaınee contenant les entiers 1, 2, 3et 4.

Voici la declaration du type correspondant en Caml :

type ’a liste_doublement_chaınee = Nil | Cellule of ’a cellule_de_liste_doublement_chaınee

and ’a cellule_de_liste_doublement_chaınee =

{ mutable valeur : ’a ;

mutable lien_arriere : ’a liste_doublement_chaınee ;

mutable lien_avant : ’a liste_doublement_chaınee } ;;

La file d’attente sera representee par une liste doublement chaınee, avec un acces rapidegrace a deux pointeurs supplementaires, l’un sur la premiere cellule, l’autre sur la derniere dela liste. Ainsi notre type s’ecrira-t-il :

type ’a file_d_attente = { mutable tete : ’a liste_doublement_chaınee ;

mutable queue : ’a liste_doublement_chaınee } ;;

On procedera a l’insertion d’un nouvel objet en tete de la liste doublement chaınee, la l’habitude est deparler de l’insertion,ou de la suppressiond’une cle dans unestructure

suppression se realisant donc sur la fin de la liste, a laquelle on accede directement grace aupointeur queue. La seule difficulte est dans la gestion des differents pointeurs, mais elle sesurmonte sans peine grace a quelques schemas.

On trouvera le programme final page suivante.Notons que l’insertion comme la suppression se realisent en temps constant : le cout est

O(1).

Page 21: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

2.3. UNE STRUCTURE CONCRETE 21

Programme 2.2 Files d attente.ml

1 type ’a liste_doublement_chaınee = Nil | Cellule of ’a cellule_de_liste_doublement_chaınee

2 and ’a cellule_de_liste_doublement_chaınee =

3 { mutable valeur : ’a ;

4 mutable lien_arriere : ’a liste_doublement_chaınee ;

5 mutable lien_avant : ’a liste_doublement_chaınee } ;;

67 type ’a file_d_attente = { mutable tete : ’a liste_doublement_chaınee ;

8 mutable queue : ’a liste_doublement_chaınee } ;;

910 let cree_file_d_attente (bidon:’a) =

11 let (file:’a file_d_attente) = { tete = Nil ; queue = Nil }

12 in

13 let ajoute a =

14 let c = Cellule { valeur = a ; lien_arriere = Nil ; lien_avant = file.tete }

15 in

16 match file.tete with

17 Nil -> file.tete <- c ; file.queue <- c

18 | Cellule ct -> ct.lien_arriere <- c ; file.tete <- c

19 and extrait () =

20 match file.queue with

21 Nil -> raise File_Vide

22 | Cellule cq -> match cq.lien_arriere with

23 Nil -> file.tete <- Nil ;

24 file.queue <- Nil ;

25 cq.valeur

26 | (Cellule cp) as p -> file.queue <- p ;

27 cp.lien_avant <- Nil ;

28 cq.valeur

29 and est_vide () = file.tete = Nil

30 and en_liste () =

31 let rec listeur = function

32 Nil -> []

33 | Cellule c -> c.valeur:: (listeur c.lien_avant)

34 in

35 listeur file.tete

36 in

37 (ajoute,extrait,est_vide,en_liste) ;;

Page 22: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 23: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 3

Une structure pour les listescirculaires

3.1 Description de la structure

Dans ce court chapitre, nous ecrivons en Caml de quoi implementer des listes circulairesdoublement chaınees. Il s’agit d’une structure ordonnee, dans laquelle nous pouvons avancercomme reculer, et qui boucle sur elle-meme : si la structure comporte les n objets x0, x1,. . . , xn−1, il est defini sur toute la structure une fonction predecesseur et une fonctionsuccesseur , telles quetu as remarque? on

travaille modulo n∀i > 0, predecesseur (xi) = xi−1;

predecesseur (x0) = xn−1;

∀i < n− 1 successeur (xi) = xi+1;

successeur (xn−1) = x0.

Comme on le verra aisement, nous nous inspirerons largement de ce qui a ete vu dans lechapitre precedent : nous utiliserons ici encore une liste doublement chaınee.

3.2 Implementation en Caml

Ayant largement explicite precedemment l’utilisation des listes doublement chaınees, nousnous contenterons ici de fournir les listages des fichiers sources.remacher trop ne

donne qu’une infamebouillie Programme 3.1 L’interface Cycles.mli pour les listes circulaires

1 type ’a cycle_bien_chaıne ;;

23 exception Cycle_Vide ;;

45 value insere_devant : ’a -> ’a cycle_bien_chaıne -> ’a cycle_bien_chaıne

6 and insere_derriere : ’a -> ’a cycle_bien_chaıne -> ’a cycle_bien_chaıne

7 and valeur_cycle : ’a cycle_bien_chaıne -> ’a

8 and avance_cycle : ’a cycle_bien_chaıne -> ’a cycle_bien_chaıne

9 and recule_cycle : ’a cycle_bien_chaıne -> ’a cycle_bien_chaıne

10 and supprime_valeur_cycle : ’a cycle_bien_chaıne -> ’a cycle_bien_chaıne

11 and liste_en_cycle : ’a list -> ’a cycle_bien_chaıne

12 and cycle_en_liste : ’a cycle_bien_chaıne -> ’a list

13 and taille_cycle : ’a cycle_bien_chaıne -> int ;;

23

Page 24: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

24 UNE STRUCTURE POUR LES LISTES CIRCULAIRES

Nous devrons definir deux fonctions d’insertion (devant et derriere l’element courant),une fonction (valeur cycle) qui renvoie l’element courant, deux fonctions de deplacementdans le cycle (en avant ou en arriere), une fonction de suppression de l’element courant, etbien sur les conversions de listes circulaires en listes simples (a partir de l’element courant) etinversement. C’est ce qui est fait ici.

Programme 3.2 La premiere partie du fichier Cycles.ml qui implemente les listes circulaires

1 type ’a cycle_bien_chaıne = Nil | Cellule of ’a cellule_de_cycle

2 and ’a cellule_de_cycle =

3 { valeur : ’a ;

4 mutable lien_avant : ’a cycle_bien_chaıne ;

5 mutable lien_arriere : ’a cycle_bien_chaıne } ;;

67 let avance_cycle = function Nil -> Nil | Cellule c -> c.lien_avant ;;

8 let recule_cycle = function Nil -> Nil | Cellule c -> c.lien_arriere ;;

9 let valeur_cycle = function Nil -> raise Cycle_Vide | Cellule c -> c.valeur ;;

1011 let lien_avant_sur but = function

12 Nil -> raise Cycle_Vide

13 | Cellule c -> c.lien_avant <- but ;;

141516 let lien_arriere_sur but = function

17 Nil -> raise Cycle_Vide

18 | Cellule c -> c.lien_arriere <- but ;;

1920 let taille_cycle = function

21 Nil -> 0

22 | cycle ->

23 let rec compte n c = if c = cycle then n else compte (n+1) (avance_cycle c)

24 in

25 compte 1 (avance_cycle cycle) ;;

2627 let est_singleton = function

28 Nil -> false

29 | c -> c = (avance_cycle c) ;;

3031 let supprime_valeur_cycle = function

32 Nil -> raise Cycle_Vide

33 | c -> if (est_singleton c) then Nil

34 else

35 begin

36 lien_avant_sur (avance_cycle c) (recule_cycle c) ;

37 lien_arriere_sur (recule_cycle c) (avance_cycle c) ;

38 avance_cycle c

39 end ;;

Page 25: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

3.2. IMPLEMENTATION EN CAML 25

Programme 3.3 La seconde partie du fichier Cycles.ml qui implemente les listes circulaires

40 let insere_devant x cycle =

41 let c = Cellule { valeur = x ;

42 lien_avant = (avance_cycle cycle) ;

43 lien_arriere = cycle }

44 in

45 if cycle = Nil then

46 begin

47 lien_avant_sur c c ;

48 lien_arriere_sur c c ;

49 c

50 end

51 else

52 begin

53 lien_avant_sur c cycle ;

54 lien_arriere_sur c (avance_cycle c) ;

55 c

56 end ;;

5758 let insere_derriere x cycle =

59 let c = Cellule { valeur = x ;

60 lien_avant = cycle ;

61 lien_arriere = (recule_cycle cycle) }

62 in

63 if cycle = Nil then

64 begin

65 lien_avant_sur c c ;

66 lien_arriere_sur c c ;

67 c

68 end

69 else

70 begin

71 lien_avant_sur c (recule_cycle c) ;

72 lien_arriere_sur c cycle ;

73 c

74 end ;;

7576 let cycle_en_liste = function

77 Nil -> []

78 | c -> let rec scan cycle accu =

79 if cycle = c then (valeur_cycle c) :: accu

80 else scan (recule_cycle cycle) ((valeur_cycle cycle) :: accu)

81 in scan (recule_cycle c) [] ;;

8283 let rec liste_en_cycle = function

84 [] -> Nil

85 | a :: q -> insere_devant a (recule_cycle (liste_en_cycle q)) ;;

Page 26: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 27: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 4

Files de priorite

4.1 Definition abstraite de la structure

Une file de priorite est une structure qui organise les elements d’un ensemble muni d’unerelation d’ordre, que nous noterons ici ¹ (nous noterons la relation stricte ≺). Elle offre deuxoperations fondamentales d’acces :

• l’insertion d’une nouvelle cle ;

• l’extraction de la cle qui realise le minimum pour la relation ¹.

On ajoutera souvent deux fonctions : l’une de creation d’une file de priorite a partir d’uneliste d’objets (c’est a priori une simple suite d’insertions), et l’autre qui procede a l’operationcontraire, et rend la liste des objets contenus dans la structure.

Il existe plusieurs methodes pour implementer les files de priorite. On peut bien entenducommencer par essayer une simple liste. Si n est le nombre d’objets de la structure, l’insertionest alors en O(1) et l’extraction du minimum est en O(n).

On peut imaginer des structures plus efficaces, qui realisent l’insertion et l’extraction enO(logn). C’est le cas de la structure en arbre bicolore, en tas, ou encore en file binomiale.

4.2 Une structure concrete : les tas

Des structures efficaces qui implementent les files de priorite, la structure en tas est sans douteon dit aussi arbretournoi parfait la plus simple a concevoir, la plus facile a mettre en œuvre, et, partant, la plus utilisee.

L’idee est simple : on part du fait qu’un arbre binaire de profondeur k peut loger, si onla profondeur est lenombre d’aretesparcourues pour allerde la racine a lafeuille la plus basse

remplit tous les niveaux (sauf peut-etre le dernier), entre 2k et 2k+1 − 1 objets. Inversement,un arbre bien rempli de n objets a une profondeur egale a k = blog2 nc.

On va donc tenter d’organiser les donnees en un arbre toujours bien rempli (on dit parfait),avec, pour retrouver facilement la cle minimale, une condition supplementaire qui definit lesarbres tournois et qui se traduit ainsi :

tout nœud de l’arbre a une valeur inferieure a celles de ses nœuds-fils

Toute la difficulte consiste a maintenir cette structure en autorisant les deux operationssouhaitees et en verifiant toujours la condition imposee.

Lors d’une insertion, on ajoutera la nouvelle cle tout en bas de l’arbre et on la fera remonter,on parle depercolation en l’echangeant avec son pere, si celui-ci est superieur, jusqu’a atteindre (peut-etre) la racine de

l’arbre (si on avait insere une cle plus petite que toutes les autres). On trouvera, en figure 4.2,ce qui arrive quand on ajoute la cle 0 a l’arbre donne en exemple figure 4.1, ce qui correspondjustement au cas le pire, ou l’on remonte jusqu’a la racine.

27

Page 28: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

28 FILES DE PRIORITE

11

83

4

5 10

9

6

1

2

Figure 4.1: Un arbre tournoi parfait, i.e. un tas

11

83

4

5 10

9

6

1

2

0 11

8

34

5 10

9

6

1

2

0

11

8

34

5 10

9

6

1

2

0

11

8

34

5 10

9

61

2

0

Figure 4.2: Insertion d’une nouvelle cle : la cle 0

Le minimum de la structure est toujours a la racine. On le trouve donc sans difficulte. Unefois l’arbre etete, il faut faire remonter de ses deux fils le plus petit, creant ainsi un nouveautrou. On recommence alors, faisant a nouveau remonter le plus petit des deux fils eventuels,etc.

Le probleme est qu’on n’est pas assure que l’arbre final soit encore parfait, c’est-a-dire“bien” rempli. Dans l’exemple de la figure 4.3 ou tout semble bien se passer, on s’apercoit denotre erreur dans le cas ou les cles 2 et 6 de l’arbre initial auraient ete interverties : l’arbrefinal n’aurait plus ete parfait.

11

83

4

5 10

9

6

1

2

11

83

4

5 10

9

6

2

2

11

83

4

5 10

9

6

2

3

11

845 10

9

6

2

3

Figure 4.3: L’extraction de la cle minimale : version incorrecte

Page 29: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

4.3. IMPLEMENTATION EN CAML 29

Une solution consiste a tout d’abord faire remonter a la racine l’element le plus en bas adroite de l’arbre, c’est-a-dire en quelque sorte a creer le trou la ou il ne pose pas probleme :tout en bas. Ensuite seulement, on procede a la percolation, faisant redescendre a sa place lanouvelle racine. C’est ce qui est fait dans la figure suivante, la figure 4.4.

11

835 10

9

6

4

4

2

11

835 10

9

6

2

1

4

11

845 10

9

6

2

3

Figure 4.4: L’extraction de la cle minimale : version finale

4.3 Implementation en Caml

Un autre interet de la structure de tas est qu’elle se prete particulierement bien a une imple-mentation informatique simple. Nul besoin de creer un nouveau type arbre particulier, unsimple tableau suffit a representer un arbre parfait. En effet, si on numerote les nœuds d’unarbre parfait dans l’ordre dit militaire :le plus age dans le

grade le plus eleve1. on numerote la racine de l’arbre par l’indice 0 ;

2. on numerote les nœuds de profondeur 1, de la gauche vers la droite ;

3. on numerote les nœuds de profondeur 2, de la gauche vers la droite ;

4. etc.

on verifie que si i numerote un nœud, son pere est numerote (i−1)/2 (division entiere, j’utilisela notation de Caml), et ses deux fils 2i+ 1 et 2i+ 2.

On represente donc un arbre parfait de taillen par un vecteur de meme taille, et on utiliserala remarque precedente pour simuler le parcours de l’arbre.

Il est temps de donner l’interface Tas.mli (voir page suivante).Un tas est un quintuplet (t,donne_taille,incr_taille,<,taille_max). t est le

vecteur des donnees, donne taille est une fonction sans argument qui renvoie la taillecourante du tas, incr taille ajoute son argument a la taille actuelle, < designe la relationd’ordre≺, et taille max est la taille-memoire qu’on a reservee pour le tableau, et que ne doitpas depasser la taile actuelle, sauf a declencher l’exception Tas Plein.

Nous avons ici utilise une autre methode classique de programmation Caml : nousdefinissons dans le programme Tas.mlquelques fonctions utilitaires comme par exem-ple echange dans vect, que nous ne declarons pas dans Tas.mli. Apres avoir charge

le module en memoire grace aux habituels load_object "Tas" ;; et #open "Tas" ;;, notremonde Caml ne contiendra que les definitions trouvees dans Tas.zi, c’est-a-dire ce qui a etegenere durant la compilation de Tas.mli. En revanche les fonctions utiles, declarees dansl’interface, restent disponibles pour l’utilisateur, comme par exemple extrait minimum.

Page 30: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

30 FILES DE PRIORITE

Programme 4.1 L’interface Tas.mli

1 type ’a structure_de_tas ;;

23 exception Tas_Plein ;;

4 exception Tas_Vide ;;

56 value percolation : ’a structure_de_tas -> int -> unit

7 and insere_dans_tas : ’a structure_de_tas -> ’a -> unit

8 and extrait_minimum : ’a structure_de_tas -> ’a

9 and tas_en_liste : ’a structure_de_tas -> ’a list

10 and tas_en_vecteur : ’a structure_de_tas -> ’a vect

11 and liste_en_tas : (’a -> ’a -> bool) -> ’a list -> int -> ’a structure_de_tas ;;

Nous aurons besoin, pour implementer l’algorithme de Dijkstra sur les graphes, d’un accesa la procedure remonte dans tas. Nous avons choisi de declarer publique une fonctionpercolation qui est simplement ici un synonyme de cette procedure. D’autres structures dedonnees nous auraient conduits a une autre implementation.

Voici quelques remarques sur la programmation des differentes fonctions de Tas.ml :

echange dans vect realise simplement l’echange de deux elements d’un vecteur quelconque ;

descend dans tas fait descendre a sa place dans le tas un element donne, comme il est requispour l’extraction. Il faut simplement faire attention a ne jamais aller au-dela de la tailledu tas, n, qu’on recupere en ligne 25 ;

remonte dans tas fait au contraire remonter un element, comme il est requis pour l’insertion.Il n’y a pas de difficulte particuliere, et la programmation est plus simple que dans le casprecedent ou on doit choisir la direction de la descente ;

insere dans tas doit seulement faire attention a la gestion de la taille du tas, et declencheren cas de besoin l’exception Tas Plein ;

extrait minimum realise l’extraction comme on l’a expliquee plus haut, et declenche l’ex-ception Tas Vide si on l’appelle sur un tas initialement vide ;

tas en liste rend la liste des elements du tas. Elle utilise une routine recursive locale toutesimple epuise pour balayer le tableau ;

tas en vecteur rend un vecteur des elements du tas ;

liste en tas cree l’arbre initial. On prendra garde, en particulier a cause de l’habituelprobleme du typage de Caml, a fournir une liste non vide d’elements a ranger dans letas. Si on tient vraiment a un tas initialement vide, on utilisera une liste comportant unelement unique pour fournir le type du tas, et on appellera immediatement la procedured’extraction. Bien sur, il faut aussi fournir la relation d’ordre et la taille maximale a ne on pourrait gerer une

allocation de lamemoire comme onl’a fait pour les pilespour eviter ceprobleme

pas depasser du tas. La procedure locale copie liste realise simplement la copie deselements dans le tableau : copie_liste i l copie dans les elements ti, ti+1, . . . , leselements de la liste l. Reste a transformer l’arbre que represente ce tableau en arbretournoi : nous n’avons pour l’instant pas pris en consideration la relation d’ordre entreles elements. C’est le but de la ligne 101, mais cela merite une preuve, qui est donneeci-apres.

Page 31: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

4.3. IMPLEMENTATION EN CAML 31

Programme 4.2 Debut de Tas.ml, une gestion par les tas des files de priorite

1 (*=========================================================================*)

2 (* *)

3 (* gestion de files de priorite *)

4 (* utilisation d’arbres tournois parfaits *)

5 (* ou tas ou heaps *)

6 (* *)

7 (*=========================================================================*)

89 (* definition du type *)

10 type ’a structure_de_tas =

11 Tas of (’a vect * (unit -> int) * (int -> unit) * (’a -> ’a -> bool) * int) ;;

1213 exception Tas_Plein ;;

14 exception Tas_Vide ;;

1516 (*** PARTIE PRIVEE : ces fonctions ne sont pas declarees dans Tas.mli, *****)

17 (*** et restent donc inaccessibles a l’utilisateur *****)

1819 let echange_dans_vect t i j =

20 let temp = t.(i) in t.(i) <- t.(j) ; t.(j) <- temp ;;

2122 (* descend_dans_tas t i fait descendre a sa place dans le tas t *)

23 (* l’element d’indice i *)

24 let rec descend_dans_tas (Tas(tas,sa_taille,_,inf,_) as t) i =

25 let n = sa_taille ()

26 in (* calcul du fils gauche *)

27 let fils = 2*i + 1

28 in (* calcul du plus petit des deux fils *)

29 let fils = if (fils < n-1) & (inf tas.(fils+1) tas.(fils)) then fils+1 else fils

30 in (* descente de ce cote si le fils en question est plus petit *)

31 if fils < n & inf tas.(fils) tas.(i) then

32 begin

33 echange_dans_vect tas i fils ;

34 descend_dans_tas t fils

35 end ;;

3637 let rec remonte_dans_tas (Tas(tas,_,_,inf,_) as t) i =

38 if i > 0 & inf tas.(i) tas.((i-1)/2) then

39 begin

40 echange_dans_vect tas i ((i-1)/2) ;

41 remonte_dans_tas t ((i-1)/2)

42 end ;;

4344 (********* FIN DE LA PARTIE PRIVEE **********************************************)

Page 32: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

32 FILES DE PRIORITE

Programme 4.3 Fin de Tas.ml

45 (********* PARTIE PUBLIQUE : ces fonctions sont declarees dans Tas.mli, *****)

46 (********* et sont disponibles pour l’utilisateur *****)

4748 let percolation = remonte_dans_tas ;;

4950 let insere_dans_tas (Tas(tas,sa_taille,incr_taille,inf,nMAX) as t) x =

51 let n = sa_taille ()

52 in

53 if n = nMAX then raise Tas_Plein ;

54 tas.(n) <- x ;

55 incr_taille 1 ;

56 remonte_dans_tas t n ;;

5758 let extrait_minimum (Tas(tas,sa_taille,incr_taille,_,_) as t) =

59 let n = sa_taille ()

60 in

61 if n = 0 then raise Tas_Vide ;

62 let mini = tas.(0)

63 in

64 begin

65 echange_dans_vect tas 0 (n-1) ;

66 incr_taille (-1) ;

67 descend_dans_tas t 0 ;

68 mini

69 end ;;

7071 let tas_en_liste (Tas(tas,sa_taille,_,_,_) as t) =

72 let n = sa_taille ()

73 in

74 let rec epuise nb l = if nb = 0 then l else epuise (nb-1) (tas.(nb-1) :: l)

75 in epuise n [] ;;

7677 let tas_en_vecteur t = vect_of_list (tas_en_liste t) ;;

7879 (* on fournit la relation d’ordre <, une liste NON VIDE d’objets, *)

80 (* et un majorant de la sa_taille du tas *)

81 (* on rend une structure de tas *)

8283 let liste_en_tas inf (a::q as l) nMAX =

84 let n = list_length l

85 in

86 let sa_taille = ref n

87 in

88 let tas = make_vect nMAX a

89 in

90 let rec copie_liste i = function

91 [] -> ()

92 | a::q -> tas.(i) <- a ; copie_liste (i+1) q

93 in

94 copie_liste 0 l ;

95 let t = Tas ( tas,

96 (function () -> !sa_taille),

97 (function delta -> sa_taille := !sa_taille + delta),

98 inf,

99 nMAX )

100 in

101 for i = (n/2 - 1) downto 0 do descend_dans_tas t i done ;

102 t ;;

Page 33: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

4.4. PREUVE ET EVALUATION DE L’ALGORITHME 33

4.4 Preuve et evaluation de l’algorithme

Donnons maintenant une preuve de l’algorithme de creation du tas (la ligne 101), et expliquonsen particulier les bornes de variation de l’indice.

✧ Rappelons tout d’abord precisement la ligne en question :

for i = (n/2 - 1) downto 0 do descend_dans_tas t i done ;

Bien entendu, nous supposerons que descend dans tas fait ce qu’on attend de lui.Encore faut-il pour cela s’entendre sur sa definition precise.descend dans tas t i demande que i soit le numero d’un nœud tel que ses deuxsous-arbres (eventuellement vides) gauche et droit soient deja des tas ; alors il fait des-cendre le nœud a sa place, et le remplace par le minimum de telle sorte qu’apres sonexecution tout l’arbre de racine le nœud i soit lui-meme un tas.un tas est un arbre

parfait, c’est-a-direun arbre bien rempli

Ceci etant entendu, nous allons definir un invariant de boucle qui permettra de finirnotre preuve. Cet invariant est le suivant : tout sous-arbre dont la racine est un nœudd’indice strictement superieur a i est un tas.Verifions l’invariant a l’entree de la boucle.Il s’agit de prouver que n/2 est l’indice de la premiere (de gauche a droite) feuille detoujours une division

entiere a la Caml l’arbre. En effet les arbres reduits a un nœud sont necessairement deja des tas. Or on adeja dit qu’un arbre de taille n a une profondeur egale a k = blog2 nc. Or, puisque nosdivisions sont des divisions entieres,

n/2 = 2blog2 nc−1 = 2k−1,

et ainsi le plus gros arbre complet (son dernier niveau de profondeur est completementrempli) strictement inclus dans notre arbre est de taille exactement egale an/2. Doncn/2est effectivement l’indice de la premiere feuille de notre tas, puisque notre numerationcommence a 0.Le plus dur est fait.En effet, d’apres ce que nous avons dit de descend dans tas, l’invariant de boucle estbel et bien conserve durant la boucle.conserve ou

preserve? Et ceci finit la preuve, car en sortie de boucle on aura justement assure que 0 est l’indicede la racine d’un tas, or c’est par construction la racine de notre arbre entier.✦

Interessons nous maintenant aux couts des differentes fonctions ecrites.Soit n la taille du tas. Sa profondeur est alors k = blog2 nc, comme nous l’avons deja

dit a maintes reprises. Ainsi le cout d’une insertion est O(k) = O(logn) car il y aura auplus k + 1 montees dans l’arbre a effectuer. De meme le cout de l’extraction du minimumest O(k) = O(logn), ce n’est d’ailleurs pas l’extraction proprement dit qui coute, mais lapercolation necessaire pour reorganiser le tas, et qui se traduit par au plus k descentes dansl’arbre.

Reste le probleme de la creation du tas a partir d’une liste de n objets, c’est-a-dire toujourset encore le cas de la ligne 101.

descend_dans_tas t i, quand 0 ≤ i ≤ n, coute au plus

ki =

⌊log2

n

i+ 1

⌋,

car c’est la profondeur du sous-arbre du tas de racine i. Demontrons le :

✧ Fixons n ≥ 1 et notons k = blog2 nc la profondeur du tas. Posons, pour i ≥ 0,

ϕ(i) =

⌊log2

n

i+ 1

⌋.

Le dernier niveau du tas contient les feuilles a profondeur k, qui sont numerotees 2k−1,2k, . . . , n−1. Or si 2k−1 ≤ i ≤ n−1, alors 2k ≤ i ≤ n et − log2 n ≤ − log2 i ≤ −k,d’ou, d’apres la definition de k, ϕ(i) = 0.

Page 34: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

34 FILES DE PRIORITE

La propriete est donc verifiee pour le dernier niveau, c’est-a-dire quand i ≥ 2k − 1.Supposons la propriete verifiee pour tous les nœuds de profondeur au moins egale a uncertain j, ou 1 ≤ j ≤ k. Soit i le numero d’un nœud a profondeur j − 1, verifions leresultat pour i, ce qui enclenchera la recurrence.L’eventuel fils gauche de i porte le numero 2i+ 1, comme on l’a deja dit.De deux choses l’une : ou bien 2i+ 1 ≥ n, et c’est dire que i est une feuille ; ou bien laprofondeur de l’arbre de racine i vaut 1 + ϕ(2i + 1), car la propriete a ete demontreepour le fils gauche, qui est plus profond, et aussi car on est sur que le sous-arbre gaucheest au moins aussi profond que le sous-arbre droit, puisque l’arbre est suppose parfait.Dans le premier cas, on trouve

log2n

i+ 1≤ log2

n

(n+ 1)/2= 1+ log2 n− log2(n+ 1) < 1,

donc ϕ(i) = 0 et c’est gagne.Dans le second cas, on a :

1+ϕ(2i+ 1) = 1+

⌊log2

n

2i+ 1+ 1

⌋= 1+

⌊log2

n

i+ 1− 1

⌋=

⌊log2

n

i+ 1

⌋= ϕ(i),

et ca marche encore.✦

Le cout de la creation du tas est donc

c(n) =

n/2−1∑i=0

ki =

n/2∑i=1

⌊log2

n

i

⌋=

+∞∑i=1

⌊log2

n

i

⌋,

puisque les termes qui correspondent a i > n/2 sont tous nuls. Une etude superficielledonnerait a penser que c(n) = O(n logn). Il n’en est rien, de fait : c(n) = O(n), ce quenous montrons maintenant.

✧ On va regrouper les termes de la somme en paquets lies aux puissances de 2 :

c(n) =

+∞∑d=0

2d+1−1∑

i=2d

⌊log2

n

i

⌋=

+∞∑d=0

2d+1−1∑

i=2d

⌊log2 n− d− log2 xi

⌋,

ou xi est un reel tel que 1 ≤ xi < 2, donc 0 ≤ log2 xi < 1. Notons qu’en realite,la plage de variation de l’indice d est la suivante : 0 ≤ d ≤ k − 1, ou on a toujoursk = blog2 nc.On dispose alors de l’encadrement :

k−1∑d=0

2d(k− d− 1) ≤ c(n) ≤k−1∑d=0

2d(k− d).

Ces sommes s’evaluent aisement, et on obtient l’encadrement final

2k − k− 1 ≤ c(n) ≤ 2k+1 − k− 2,

ce qui acheve notre demonstration car 2k = O(n) et k ∼ log2 n.✦

Page 35: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 5

Partitions d’un ensemble

5.1 Definition abstraite de la structure

Le probleme qui nous interesse ici a ete baptise par les Anglo-Saxons union-find, car union ≡fusionner et find ≡ trouver. Il interviendra par exemple dans certains algorithmes sur lesgraphes.

On considere un ensemble fini A de n objets a0, a1, . . . , an−1. On remarquera qu’on nedemande aucune relation d’ordre particuliere sur ces objets.

On souhaite qu’a l’initialisation de la structure, on dispose d’une partition deA constitueedes singletons {ai}. C’est-a-dire qu’au debut du processus on considere la partitionon peut tres bien

parler de l’histoire dela structure

P =

{{a0}, {a1}, . . . , {an−1}

}.

Dans la suite du processus, on demande a disposer d’au moins deux operations fondamen-tales :

• une fonction trouve_paquet qui retourne, pour un objet ai de A, un entier caracte-une fonctionretourne, uneprocedure procede?

ristique de celle des parties de la partition qui le contient.

• une procedure fusionne_paquets qui prenne deux entiers identifiant des elements dela partition comme arguments et qui procede a l’agglutination des deux parties specifiees.

Ainsi, apres quelques etapes, on pourra se trouver, dans le cas ou n = 8, avec la partitionsuivante :

P =

{{a0, a2}, {a1}, {a4}, {a3, a6, a7}, {a5}

}.

On n’impose pas la valeur que doit rendre la fonction trouve_paquet quand on lui fourniten argument l’objet a3, en revanche on veut que seuls a3, a6 et a7 rendent le meme resultat.

Enfin, pour poursuivre cet exemple, apres l’appel de fusionne_paquets avec commearguments les resultats de trouve_paquet sur a0 et a5, la partition courante devient :

P =

{{a0, a2, a5}, {a1}, {a4}, {a3, a6, a7}

}.

On pourra, pour la commodite de l’utilisateur, definir une fonction regroupe_en_paquet

qui opere la meme fusion mais qui attend en argument deux objets a mettre ensemble plutotque les numeros des paquets correspondants.

35

Page 36: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

36 PARTITIONS D’UN ENSEMBLE

5.2 Un autre point de vue

On peut aussi parler en termes de relation et de classes d’equivalence.Rappelons que siR1 etR2 sont deux relations d’equivalence sur un meme ensemble E, laeuxieme est dite

s grossiere que lamiere, ouore. . .moins fine

relationR1 est dite plus fine que la relationR2 si on a

∀(x, y) ∈ E2, xR1 y =⇒ xR2 y.

Ainsi, par exemple, sur N, peut-on dire que la relation “a meme parite que” est moins fine quela relation “a meme reste modulo 6 que”.

Si R1 est plus fine que R2, on observera que les partitions de E qui correspondent auxensembles quotients E/R1 et E/R2 verifient

∀x ∈ E, x ⊂ x,

ou je note x la classe d’un objet x moduloR1 et x la classe du meme objet moduloR2.On part de la relation d’equivalence la plus fine sur l’ensemble de nos objets, a savoir celle

pour laquelle les classes sont reduites aux seuls singletons. trouve paquet p a fournit unrepresentant entier na de la classe de a. Si na et nb sont des representants entiers des classesde a et b, fusionne paquets p na nb transforme la relation d’equivalence de depart enune relation un peu moins fine, la plus fine de toutes celles qui sont a la fois moins fines que perdu? ah mais non !

faudrait voir a suivreun minimum !

la premiere et qui verifient aR b.

5.3 Interface proposee

On donne l’interface Partitions.mli, sans la definition du type partition qui decoulerade l’implementation choisie.

Programme 5.1 L’interface Partitions.mli (sauf la definition des types)

1 value liste_en_partition : ’a list -> (’a -> int) -> ’a partition

2 and fusionne_paquets : ’a partition -> int -> int -> unit

3 and trouve_paquet : ’a partition -> ’a -> int

4 and nbre_de_paquets : ’a partition -> int

5 and meme_paquet : ’a partition -> ’a -> ’a -> bool

6 and regroupe_2_en_paquet : ’a partition -> ’a -> ’a -> unit

7 and regroupe_n_en_paquet : ’a partition -> ’a list -> unit ;;

En ligne 1, est declaree liste_en_partition qui prend en arguments une liste d’objetset une fonction de numerotation des objets a laquelle on demande d’etre une bijection de Asur {0, . . . , n− 1}, et qui renvoie la partition initiale, composee des n singletons.

La fonction de numerotation des objets qui vient d’etre evoquee n’a rien a voir avec lesentiers qui sont associes de facon caracteristique a chacun des elements de la partition(les paquets, si l’on prefere), c’est-a-dire les representants entiers des classes. Il s’agit

simplement de disposer de facon canonique (autrement dit independante du type des objets)d’un moyen de selectionner tel ou tel objet.

(fusionne_paquets p i j) fusionne les elements numeros i et j de la partition p.(trouve_paquet p a) renvoie le numero du paquet (l’entier qui le caracterise) de la

partition p qui contient l’objet a.(nbre_de_paquets p) et (meme_paquet p a b) precisent le nombre d’elements de la

partition p ou si les objets a et b sont dans le meme paquet.Enfin, (regroupe_2_en_paquet p a b), comme annonce plus haut, procede a la fusion

des elements de la partition qui contiennent les deux objets a et b.De meme (regroupe_n_en_paquet p [a ; ... ; z]) fusionne les elements de la par-

tition qui contiennent chacun des elements de la liste donnee en dernier argument.

Page 37: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

5.4. UNE STRUCTURE CONCRETE 37

5.4 Une structure concrete

On pourrait bien sur representer la partition sous forme d’un tableau t de n entiers, tel que ticontienne le numero de sa classe. trouve paquet est alors immediat !

En revanche fusionne paquets necessite un balayage complet du tableau, et tourne doncen O(n).

Nous allons utiliser plutot une representation arborescente : on associe a la partition uneforet d’arbres, chaque classe etant representee par un arbre. Au sein d’une classe, les nœudsde l’arbre possedent un pointeur sur leur pere, sauf la racine, bien sur. De fait, la structurecomplete peut etre implementee par un tableau, ou ti vaut i si ti est la racine d’un arbre depour simplifier la

presentation, lesobjets de la partitionsont representes pardes entiers

la partition et le numero j de son pere dans le cas contraire.

8

9

2

4

6

1

5 7

3

0

Figure 5.1: Une foret associee a une partition

Par exemple, a la foret representee dans la figure ci-dessus, sont associes la partition{{0, 3, 5, 7}, {1}, {2, 4, 6}, {8, 9}} et le tableau suivant :

i 0 1 2 3 4 5 6 7 8 9

ti 0 1 6 0 4 3 4 3 8 8

Dans la suite, pour eviter toute confusion, on designera plutot ce tableau par le nom pere.en general, il estconseille d’utiliserdes identificateurstres parlants et nonambigus

On aura bien entendu remarque qu’il n’y a pas du tout unicite de la representation. Notonsd’autre part qu’il n’est pas necessaire que les arbres de la foret soient binaires. La figure suivanteconvient tout aussi bien que la precedente.

8

9

2

4

6

1

5 73

0

Figure 5.2: Une foret moins profonde associee a la meme partition

Il est bien clair que l’efficacite de la structure augmente quand la profondeur des arbresdiminue. En effet, si la fusion de classes se fait maintenant en O(1) quelles que soient lesprofondeurs des arbres, il faut, pour trouve paquet, remonter d’un nœud d’un arbre jusqu’asa racine.

La premiere idee qui permet d’equilibrer un peu les arbres est de raffiner la fusion. Onraffiner pourequilibrer va conserver dans un tableau poids des entiers representant la profondeur de l’arbre qui se

trouve sous chaque nœud. Au depart, il n’y a dans ce tableau que des 0 ; par la suite, lorsd’une fusion de deux arbres, on s’arrangera pour limiter le poids global en creant un arbredont la racine est celle de l’arbre le plus lourd a laquelle on accroche, comme arbre-fils, l’arbre

Page 38: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

38 PARTITIONS D’UN ENSEMBLE

le plus leger. Bien sur, on prendra garde a recalculer correctement le poids de la racine (ce quine doit etre fait que si les deux arbres etaient de meme poids).

Voici l’extrait correspondant de notre fichier final Partitions.ml :

if poids.(i) = poids.(j) then begin poids.(i) <- poids.(i) + 1 ; pere.(j) <- i end

else if poids.(i) > poids.(j) then pere.(j) <- i else pere.(i) <- j ;;

Une autre facon d’arranger les choses, qui n’est pas contradictoire avec la precedente, est c’est le procede dit“path compression”ou “compression deschemins”

de remarquer que lors de la recherche de la classe d’un objet, quand on remonte vers la racined’un arbre, on peut en profiter pour rediriger directement tous les nœuds visites vers la racinede l’arbre : ce sera autant de gagne pour la prochaine fois.

Voici sur un exemple ce que cela donne : on cherche la classe de 20 ; la figure suivantemontre l’arbre avant et apres l’appel de trouve paquet p 20.

20

8

6

4

11 1

21

10

12

16

9

8

6

4

11 1

10

12

20

21 16

9

avant après

Figure 5.3: La compression des chemins

On ecrira en Caml la recherche de la racine avec compression des chemins sous la forme rappelons que l’onpeut, en Caml, ecrire(. . . ; . . . )

au lieu debegin . . . ; . . . end,mais on tachera dene pas en abuser

let rec monte_au_pere i = if pere.(i) = i then i else monte_au_pere pere.(i)

in

let racine = monte_au_pere x

in

let rec dirige_au_pere i =

if i = racine then ()

else ( dirige_au_pere pere.(i) ; pere.(i) <- racine )

in dirige_au_pere x ;

racine ;;

dirige au pere est chargee de raccrocher tous les sommets au-dessus de x a la racine del’arbre, comme nous l’avons indique.

Le lecteur attentif aura remarque que les informations contenues dans le tableaupoids ne representent plus la profondeur effective des differents sous-arbres de laforet, des qu’on a utilise la compression des chemins. C’est vrai. Il demeure que

le critere — tres simple — qui consiste a utiliser malgre tout ce tableau poids reste valable.Il serait finalement plus couteux de maintenir les vraies profondeurs que de se contenter del’information partielle dont on dispose avec ce tableau simplifie.

Dans la suite, nous utiliserons ces deux ameliorations de l’algorithme.

Page 39: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

5.5. EVALUATION 39

5.5 Evaluation

L’evaluation precise de la complexite de l’algorithme resultant de l’etude precedente peut etrefaite en detail, mais elle est vraiment tres difficile. Nous nous contenterons ici de donner leon en doit l’etude

exhaustive a Tarjan,puis Mehlhorn, qui asimplifie lapresentation de lapreuve de Tarjan

resultat de cette etude.On definit une fonction A : N× N −→ N par les relations de recurrence suivantes :

∀i ≥ 1, A(i, 0) = 1;

∀j ≥ 0, A(0, j) = 2j;

∀(i, j), A(i+ 1, j+ 1) = A(i, A(i+ 1, j)).

Cette fonction est une variante de la celebre fonction d’Ackermann. Elle a pour particularitede croıtre vraiment tres rapidement.

On peut encore ecrire facilement A(i, j) pour 0 ≤ i ≤ 2 et 0 ≤ j ≤ 6 :

j 0 1 2 3 4 5 6

0 0 2 4 6 8 10 12

1 1 2 4 8 16 32 64

2 1 2 4 16 65536 265536 2265536

Mais ensuite on a par exemple

A(3, 4) = 2

2

. ..

2,

ou il y a 65535 exposants 2 a la suite !Definissons maintenant un genre de fonction inverse de la fonction A. Soit α la fonctionRappel : dxe designe

le plafond de x,c’est-a-dire le pluspetit entier superieura x

definie par :

sim ≥ n, alors α(m,n) = min{p ≥ 1 / A(p, 4dm/ne) > log2 n}.

Evidemment, comme A croissait tres vite, α est plutot du style tortue qui lambine. Il estd’ailleurs facile d’etablir que α(m,n) ≤ α(n,n) si m ≥ n, puis que α(n,n) ≤ 1 tant quen < 216, et que α(n,n) ≤ 2 tant que n < 265536.

On retiendra que pour des valeurs raisonnables,α(m,n) ≤ 2. Bon, je vois des sceptiques :au diable la raison !cherchez voir les entiers n tels que α(n,n) ≥ 3 !

Tarjan demontre alors le

Theoreme 1 (Tarjan) Le cout de n− 1 operations de fusion (appels de fusionne paquets) etdem operations de recherche (appels de trouve paquet) est O(mα(m,n)).

En general nous procederons a (n − 1) operations du genre chercher les classes de deux objetspuis les fusionner, et nous pouvons donc dire qu’en general la complexite de notre algorithmeestO(n). (En realite c’estO(nα(n,n)), mais α(n,n) a peu de chances de depasser quelquesunites. . . )

5.6 Implementation en Caml

Venons-en enfin a notre implementation en Caml, qu’on trouvera dans les deux listings suivantslisting ≡ listage(l’interface d’abord, l’implementation elle-meme ensuite).

La description de l’algorithme et de ses details majeurs d’implementation a deja ete faite.Interessons-nous simplement aux lignes 5–11 qui creent la partition. On fournit une listed’objets et une fonction de numerotation de ces objets. liste en partition renvoie unobjet de type partition, c’est-a-dire un quadruplet compose de la fonction de numerotation,du tableau des poids, de la taille et enfin du tableau pere, toutes choses que nous avons decritesprecedemment.

Page 40: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

40 PARTITIONS D’UN ENSEMBLE

Programme 5.2 L’interface Partitions.mli

1 type ’a partition ;;

23 value liste_en_partition : ’a list -> (’a -> int) -> ’a partition

4 and fusionne_paquets : ’a partition -> int -> int -> unit

5 and trouve_paquet : ’a partition -> ’a -> int

6 and nbre_de_paquets : ’a partition -> int

7 and meme_paquet : ’a partition -> ’a -> ’a -> bool

8 and regroupe_2_en_paquet : ’a partition -> ’a -> ’a -> unit

9 and regroupe_n_en_paquet : ’a partition -> ’a list -> unit ;;

Programme 5.3 Partitions.ml

1 type ’a partition = Partition of (’a -> int) * (int vect) * int * (int vect) ;;

23 (* on fournit la liste d’objets, et une fonction qui permet de les numeroter *)

4 (* de 0 a n-1 ; on retourne la partition constituee des singletons *)

5 let liste_en_partition l numerote =

6 let taille = list_length l

7 in

8 let poids = make_vect taille 0 and pere = make_vect taille 0

9 in

10 for i = 0 to taille-1 do pere.(i) <- i done ;

11 Partition(numerote,poids,taille,pere) ;;

1213 (* fusionne les deux parties numerotees i et j en une seule *)

14 let fusionne_paquets (Partition(_,poids,_,pere) as p) i j =

15 if i <> j then

16 if poids.(i) = poids.(j) then begin poids.(i) <- poids.(i) + 1 ; pere.(j) <- i

end

17 else if poids.(i) > poids.(j) then pere.(j) <- i else pere.(i) <- j ;;

1819 (* renvoie le numero de la partie qui contient l’objet a *)

20 let trouve_paquet (Partition(numerote,poids,_,pere) as p) a =

21 let x = numerote a

22 in

23 let rec monte_au_pere i = if pere.(i) = i then i else monte_au_pere pere.(i)

24 in

25 let racine = monte_au_pere x

26 in

27 let rec dirige_au_pere i =

28 if i = racine then ()

29 else ( dirige_au_pere pere.(i) ; pere.(i) <- racine )

30 in dirige_au_pere x ; racine ;;

3132 (* renvoie le nombre de parties qui constituent la partition *)

33 let nbre_de_paquets = function Partition(_,_,n,pere)

34 -> let nb = ref 0

35 in

36 for i = 0 to n-1 do if i = pere.(i) then nb := 1 + !nb done ; !nb ;;

3738 (* dit si deux objets sont dans la meme partie *)

39 let meme_paquet p a b = (trouve_paquet p a) = (trouve_paquet p b) ;;

4041 (* regroupe les paquets qui contiennent deux elements donnes en un seul paquet *)

42 let regroupe_2_en_paquet p a b =

43 fusionne_paquets p (trouve_paquet p a) (trouve_paquet p b) ;;

4445 (* regroupe les paquets qui contiennent n elements donnes en un seul paquet *)

46 let rec regroupe_n_en_paquet p = function

47 [] -> ()

48 | a :: [] -> ()

49 | a :: b :: q -> regroupe_2_en_paquet p a b ; regroupe_n_en_paquet p (a :: q) ;;

Page 41: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

5.6. IMPLEMENTATION EN CAML 41

On aura note la pratique adoptee ici : au lieu de rendre unn-uplet de fonctions d’acceset de modification a la structure, nous avons choisi de rendre un objet contenant lesdifferentes variables caracteristiques de la structure, et de passer en argument a

chacune des fonctions d’acces/modification la structure ainsi construite.Il s’agit de deux points de vue assez differents. Le choix est affaire de gout (on aurait pu utiliserl’autre strategie dans ce cas sans difficulte particuliere, si l’on avait voulu). Remarquons quequel que soit le point de vue adopte, nous avons fait en sorte de limiter l’utilisation des variablesglobales : c’est la veritablement le critere essentiel.

Page 42: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 43: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Partie II

Quelques algorithmes sur lesgraphes

43

Page 44: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 45: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 6

Generalites sur les graphes

6.1 Vocabulaire

Un graphe G = (S,A, X,ϕ) est la donnee d’un ensemble fini non vide S dont les elementssont appeles sommets du graphe, d’une partieA de S×S, dont les elements sont appeles aretesdu graphe et enfin d’une fonction ϕ : A→ X, ou X est un ensemble quelconque, fini ou non,appelee fonction de valuation du graphe. Si α = (a, b) ∈ A est une arete du graphe, a et ble plus souvent

X = R sont deux sommets du graphe, et si x = ϕ(α), a s’appelle le sommet de depart de l’arete α, ble sommet d’arrivee, et x est l’etiquette, ou tout simplement la valeur de l’arete α.

Dans la suite, nous noterons a x→ b pour exprimer a la fois que (a, b) ∈ A et queϕ(a, b) = x.

Notons que la plupart du temps on ne decrit pas un graphe de la facon precedente, maisavec un dessin, ou les sommets sont representes par leurs noms dans un cercle, les aretes pardes fleches, etiquetees par leurs valeurs.

Par exemple la figure suivante :

a

c d

fb

e

8 5

3

47

4

2

Figure 6.1: Un exemple de graphe

represente un grapheG = (S,A, X,ϕ)

avec

S = {a, b, c, d, e, f},

A = {(a, a), (a, b), (a, c), (b, a), (d, c), (d, f), (f, f)},

X = R,

45

Page 46: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

46 GENERALITES SUR LES GRAPHES

et ou ϕ est representee par le tableau suivant :

u→ v a b c d e f

a 8 5 4b 3c

d 7 4e

f 2

Ainsi defini, un graphe est souvent dit oriente et value.Si G = (S,A, X,ϕ) est un graphe oriente value, on dira que G ′ = (S,A), obtenu a partir

de G en oubliant les etiquettes des aretes, est le graphe non value sous-jacent. Inversement,quand on nous fournira un graphe non value G ′ = (S,A), nous utiliserons sa representation cette convention sera

utilisee dans toute lasuite

par le graphe value G = (S,A, { vrai, faux}, ϕ), ou ϕ(α) est definie constante egale a vraipour toute arete α ∈ A.

Le graphe G = (S,A, X,ϕ) est dit symetrique si l’on a :

∀(a, b) ∈ A, (b, a) ∈ A et ϕ(b, a) = ϕ(a, b).

Notons que la structure de graphe oriente symetrique est isomorphe a celle de graphe nonoriente et c’est pourquoi nous representerons justement les graphes non orientes par des imagine la definition

rigoureuse d’ungraphe non oriente

graphes symetriques.Enfin, il est souvent d’usage de prolonger la definition de ϕ a S× S tout entier. Pour cela

nous poserons pour tout couple (a, b) qui n’est pas une arete ϕ(a, b) = nil du type, ounil du type vaudra en general +∞ si X = R et faux si X = { vrai, faux}. en toute rigueur, il

faudrait prolonger Ra R ∪ {+∞ }

En effet dans le cas ou X = R, l’etiquette represente souvent le cout, le poids, la longueurde l’arete, et donc l’absence d’arete correspond bien a un poids infini. Mais tel n’est pastoujours le cas : dans les problemes dits problemes de flots, l’etiquette represente au contrairela capacite du tuyau que represente l’arete, et il conviendra alors de definir nil du type = 0.

6.2 Une representation avec des listes

La representation informatique des graphes orientes values que nous privilegierons est fondeesur l’utilisation de listes.

On representera le graphe par une liste de couples de la forme ( nœud, liste d’adjacence) une liste de couplesd’un sommet et d’uneliste de couplesde . . .

ou nœud decrit l’ensemble des sommets du graphe (meme s’ils sont isoles, comme dans lecas du sommet e de notre exemple ci-dessus), et ou la liste d’adjacence associee est une liste(eventuellement vide, donc) de couples de la forme ( voisin, poids) telle que ( nœud, voisin)

decrive A et, bien sur, poids = ϕ( nœud, voisin).Dans notre exemple ci-dessus, on obtient la representation suivante du graphe : en une

pseudo-syntaxe a laCamlgraphe = [

( a , [ (a,8) ; (b,5) ; (c,4) ] ) ;

( b , [ (a,3) ] ) ;

( c , [ ] ) ;

( d , [ (c,7) ; (f,4) ] ) ;

( e , [ ] ) ;

( f , [ (f,2) ] )

]

Notons en particulier qu’on impose d’ecrire des couples du genre ( e , [ ] ) sans quoion ne pourrait reconstituer l’ensemble des sommets du graphe.

Remarquons au passage que le nombre n de sommets du graphe est toujours la taille de laliste qui represente le graphe.

Page 47: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

6.3. UNE REPRESENTATION MATRICIELLE 47

On peut reprocher a cette representation de ne pas montrer clairement quels sont les graphessymetriques. En revanche, elle se revele fort pratique a l’usage, et surtout economique, aucontraire de la representation matricielle que nous evoquerons plus loin.

Donnons maintenant la reelle definition Caml de cette representation.

Programme 6.1 Definition Caml de la structure de graphe

1 type sommet = Sommet of string

2 and ’a fleche == sommet * ’a

3 and ’a graphe == (sommet * (’a fleche) list) list ;;

On aura remarque que les sommets seront representes par des expressions du genreSommet("a"), c’est volontaire, meme si certains trouveront cela trop lourd : il s’agit de ne paspouvoir melanger les sommets avec les etiquettes ou autres. . .

6.3 Une representation matricielle

Une autre solution pour representer un graphe consiste a utiliser une liste des sommets(numerotes comme toujours en Caml de 0 a n − 1) et une matrice carree M d’ordre n (lesindices (i, j) varient dans {0, . . . , n− 1}× {0, . . . , n− 1}). On trouvera en ligne i et colonnej l’element mi,j = Φ(ϕ( sommeti, sommetj)), ou Φ est une transformation (qui sera sou-vent l’identite) de X en un ensemble eventuellement plus pratique. On notera que l’usage denil du type qu’on a evoque plus haut est ici incontournable : tous les elements de la matricedoivent bien valoir quelque chose !

Par exemple, et avec tout simplement Φ = Id, on obtient la matrice suivante :8 5 4 +∞ +∞ +∞3 +∞ +∞ +∞ +∞ +∞

+∞ +∞ +∞ +∞ +∞ +∞+∞ +∞ 7 +∞ +∞ 4

+∞ +∞ +∞ +∞ +∞ +∞+∞ +∞ +∞ +∞ +∞ 2

.

On aura remarque sur cet exemple le gaspillage auquel peut mener cette representation.

6.4 Conversion d’une representation a l’autre

On va donner maintenant les procedures Caml qui permettent de passer d’une representationa l’autre.

Commencons par la fonctionmatrice d incidence et sommetsqui prend en argumentsle graphe defini par listes, la valeur de nil du type et la fonction Φ. Elle renvoie un coupleconstitue de la matrice cherchee et de la liste des noms des sommets.

La fonction inverse, representation en liste, prend en arguments la matrice, la valeurde nil du type, et la liste des noms des sommets. Elle renvoie la liste qui represente le memegraphe.

Page 48: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

48 GENERALITES SUR LES GRAPHES

Programme 6.2 Conversion de la representation en listes vers la representation matricielle

1 (*====== matrice_d_incidence_et_sommets ==========================================*)

23 (* renvoie un couple (m,s) *)

4 (* ou m est la matrice de terme generique m(i,j) egal *)

5 (* a l’image par la fonction transf *)

6 (* de la valeur de l’arc (i->j) s’il existe, *)

7 (* ou a nil_du_type sinon *)

8 (* et s est la liste des noms des sommets du graphe *)

910 let matrice_d_incidence_et_sommets g nil_du_type transf =

11 let n = list_length g

12 in

13 let m = make_matrix n n nil_du_type

14 and s = let rec cree_liste_sommets = function

15 [] -> []

16 | (Sommet(s),_) :: q -> s :: cree_liste_sommets q

17 in cree_liste_sommets g

18 in

19 let rec ajoute_fleche i = function

20 [] -> ()

21 | (Sommet(but),val) :: qf

22 -> m.(i).(index but s) <- transf val ;

23 ajoute_fleche i qf

24 and cree_matrice = function

25 [] -> ()

26 | (Sommet(a),f) :: q

27 -> ajoute_fleche (index a s) f ;

28 cree_matrice q

29 in

30 cree_matrice g ;

31 (m,s) ;;

Programme 6.3 Conversion de la representation matricielle vers la representation en listes

1 (*====== operation inverse : donner la liste a partir de la matrice ==============*)

23 let representation_en_liste m nil_du_type sommets =

4 let n = list_length sommets

5 and sv = vect_of_list sommets

6 in

7 let rec cree_liste_de i j petit_accu =

8 if j = n then petit_accu

9 else cree_liste_de i (j+1)

10 ( if m.(i).(j) = nil_du_type

11 then petit_accu

12 else ((Sommet(sv.(j)),m.(i).(j)) :: petit_accu) )

13 and cree_liste i accu =

14 if i = n then accu

15 else cree_liste (i+1) ((Sommet(sv.(i)),(cree_liste_de i 0 [])) :: accu)

16 in

17 cree_liste 0 [] ;;

Page 49: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 7

Les algorithmes de Floyd et deWarshall

7.1 Le probleme des plus courts chemins

Considerons le reseau ferre de la SNCF. On peut le representer par un graphe dont les som-mets sont les gares de France, les aretes les lignes ferroviaires, et les etiquettes les distanceskilometriques. Notons que, par definition meme, le graphe est symetrique.

Si il est clair que pour aller de Paris a Rouen on utilisera la ligne directe, il est en revanchemoins evident de trouver l’itineraire le plus court entre Metzeral (en Alsace) et Luzy (dans leMorvan). On cherche plus generalement un moyen de calculer l’itineraire le plus court d’unetrouver la distance et

les garesintermediaires

gare quelconque a une autre. Je sais : les mechants diront que j’oublie le probleme essentiel,a savoir les correspondances. Mais a chaque jour suffit sa peine.

Explicitons de facon plus mathematique le probleme, en utilisant (bien sur !) les notationsintroduites dans le chapitre precedent.

Considerons donc un graphe G = (S,A, X,ϕ), ou X est muni d’une relation d’ordre total¹ et d’une loi de composition interne + qui en font un groupe ordonne. Dans la suite, nousutiliserons le langage correspondant au cas courant X = R, et nous appellerons par exemplel’element neutre 0, nous dirons que x est positif si 0 ¹ x, etc.

On appelle chemin d’un sommeta ∈ S a un sommet b ∈ S toute suite finie (a0, a1, . . . , ap)nos chemins sontdonc orientes, y fairebien attention pourun graphe nonsymetrique

de sommets deS telle quea0 = a,ap = b et, pour tout i tel que1 ≤ i ≤ p, le couple (ai−1, ai)

soit element de A, c’est-a-dire soit une arete du graphe.On dira que le grapheG est connexe si pour tout couple (a, b) de sommets distincts il existe

un chemin de a a b.Si (a0 = a, a1, . . . , ap = b) est un chemin de a a b, sa longueur (ou son poids, tout

depend de l’interpretation qu’on choisit), est definie par

`(a0, . . . , ap) =

p∑i=1

ϕ(ai−1, ai).

Nous allons demontrer le resultat suivant :

Theoreme 2 Si G est un graphe connexe, et si pour toute arete α on a 0 ¹ ϕ(α), alors, pourtout couple fixe (a, b) de sommets distincts de G, l’ensemble des longueurs des differents cheminsde a a b admet un plus petit element pour l’ordre defini par ¹.

Un chemin qui realise cette longueur minimale s’appellera un plus court chemin de a a b.

✧ Fixons a et b. Notons C l’ensemble des chemins de a a b, et L l’ensemble de leurslongueurs.Comme G est un graphe connexe, C et donc aussi L sont non vides.

49

Page 50: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

50 LES ALGORITHMES DE FLOYD ET DE WARSHALL

Soit `0 = `(c0) la longueur d’un chemin particulier c0 de a a b.Nous allons montrer que l’ensemble L0 des longueurs des chemins de a a b qui sontplus petites que `0 (L0 = {` ∈ L / ` ¹ `0}) est fini. Le resultera en decoulera aussitot. mais pas forcement

l’ensemble descheminscorrespondants

Soit A∗ l’ensemble des aretes de longueur nulle du graphe.Posons m = inf{`(α), α ∈ A \ A∗}, m est la plus petite longueur non nulle des aretesdu graphe.Si donc on considere un chemin c = (a0 = a, a1, . . . , ap = b) dea ab dont la longueurest dans L0, on peut lui associer la suite c de ses aretes qui ne sont pas dans A∗. Alors`(c) =

∑α∈cϕ(α) º m× |c|, ou |c| designe le nombre d’aretes dans c.

Posons k0 = b`0/mc.Convenons d’appeler poids total d’un ensemble d’aretes la somme de leurs longueurs.L0 est alors inclus dans l’ensemble des poids totaux des suites d’au plus k0 elements deA \A∗. Comme ce dernier ensemble est fini, L0 aussi, et ma preuve itou.✦

Notons que le resultat n’est pas assure si on admet des aretes de longueur negatives. Parexemple dans le cas du graphe de la figure suivante, il n’y a pas de plus court chemin de a ab. Il suffit pour s’en convaincre d’examiner les poids des chemins ab, acab, acacab, . . .

a bc

5

5

-3

-3

Figure 7.1: Un graphe sans plus court chemin

Notre probleme est maintenant clairement defini. On considere un graphe G connexe etd’aretes de longueurs toutes positives ou nulles. On veut pour tout couple de sommets distinctstrouver la longueur d’un plus court chemin qui les relie et exhiber un tel plus court chemin.

Dans la suite les hypotheses requises (graphe connexe, longueur d’aretes non negatives)sont supposees verifiees.

7.2 L’algorithme de Floyd

L’algorithme de Floyd repond tres exactement au probleme que nous nous sommes fixe. Floyd publie sonalgorithme en 1962Nous supposerons en outre que X ⊂ R.

Pour alleger les notations nous conviendrons de numeroter les n sommets du graphe, quiseront confondus, dans cette section, avec leurs numeros. Ainsi a-t-on S = {0, 1, . . . , n− 1}.

Soit M la matrice des longueurs des aretes : mi,j est egal a ϕ(i, j) si (i, j) ∈ A, et a +∞sinon (on etend comme d’habitude les definitions de + et ≤ a R = R ∪ {+∞}).

L’algorithme repose sur la remarque suivante, qui est de demonstration immediate : si(a0, . . . , ai, . . . , ap) est un plus court chemin de a0 a ap, alors necessairement (a0, . . . , ai)

est un plus court chemin de a0 a ai et (ai, . . . , ap) est un plus court chemin de ai a ap.En outre comme un chemin qui contient un cycle a une longueur au moins egale au chemin

obtenu en supprimant ce cycle :

`(a0, . . . , ai, . . . , ai, . . . , ap) º `(a0, . . . , ai, . . . , ap),

on peut se limiter aux plus courts chemins passant par des sommets deux a deux distincts.

Page 51: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

7.3. IMPLEMENTATION EN CAML DE N ∪ {+∞} 51

Floyd propose de calculer la suite de matricesM(−1),M(0),M(1),M(2), . . . , definie parM(−1) = M, et, pour k ≥ 0,

M(k)

i,j = min{M(k−1)i,j ,M

(k−1)i,k +M

(k−1)k,j }.

Une recurrence montre sans difficulte aucune que, quandk ≥ 0,M(k)

i,j est egal a la longueurdu plus court chemin de i a j, quand on ne s’autorise — en fait de sommets intermediaires —que les sommets 0, 1, . . . , k.

Il est alors clair d’apres les remarques precedentes qu’apres n calculs de matrices,M(n−1)

est finalement la matrice des longueurs des plus courts chemins.Bien entendu, le calcul de chaque matrice se faisant enO(n2), l’algorithme de Floyd tourne

en O(n3).Il reste une question a laquelle il semble que nous n’avons pas repondu : quels sont les

plus courts chemins? En fait, il suffit de remarquer que quand, dans le calcul de M(k) nousremplaconsM(k−1)

i,j par la sommeM(k−1)i,k +M

(k−1)k,j , c’est que nous savons que le plus court

chemin (en tout cas jusqu’a nouvel ordre) passe par k. On retiendra donc dans une matricesupplementaire, et au fur et a mesure du calcul des matrices M(k), le sommet par lequelil faut passer pour aller de i a j. Sachant qu’il faut passer par k, il suffira de considerer —recursivement — les elements d’indices i et k d’une part et k et j d’autre part, pour reconstituerl’integralite du plus court chemin de i a j.

7.3 Implementation en Caml de N ∪ {+∞}

Nous devons tout d’abord definir en Caml les operations qui font intervenir +∞. Nous avonsreecris lesinstructionscorrespondant au casreel

choisi de considerer des aretes entieres, et nous ecrirons simplement :

Programme 7.1 L’ensemble N ∪ {+∞}

1 (* on introduit un type correspondant a N U { +infini } *)

2 type entier = Infini | N of int ;;

34 let inferieur x y = match x,y with

5 Infini,Infini -> true

6 | Infini,N(_) -> false

7 | N(_),Infini -> true

8 | N(p),N(q) -> p <= q ;;

910 let strict_inferieur x y = match x,y with

11 Infini,Infini -> false

12 | Infini,N(_) -> false

13 | N(_),Infini -> true

14 | N(p),N(q) -> p < q ;;

1516 let plus x y = match x,y with

17 Infini,Infini -> Infini

18 | Infini,N(_) -> Infini

19 | N(_),Infini -> Infini

20 | N(p),N(q) -> N(p + q) ;;

Page 52: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

52 LES ALGORITHMES DE FLOYD ET DE WARSHALL

7.4 Implementation en Caml de l’algorithme de Floyd

Nous ecrivons en Caml le programme ci-dessous.

Programme 7.2 L’algorithme de Floyd

1 (* pour ameliorer la lisibilite *)

2 type intermediaire = Direct | Nœud of string ;;

34 (* floyd g renvoie une paire de deux fonctions distance,chemin *)

5 (* distance Sommet(a) Sommet(b) renvoie la plus courte distance de a a b *)

6 (* chemin Sommet(a) Sommet (b) decrit le chemin a utiliser *)

78 let floyd g =

9 let n = list_length g

10 and m,sl = matrice_d_incidence_et_sommets g Infini (function x -> N(x))

11 in

12 let sv = vect_of_list sl

13 (***** a priori les chemins sont tous directs *)

14 and passage = make_matrix n n Direct

15 in

16 (***** le cœur de l’algorithme de Floyd : du O(n^3) a coup sur *)

17 for k = 0 to n-1 do

18 for i = 0 to n-1 do

19 for j = 0 to n-1 do

20 let raccourci = plus m.(i).(k) m.(k).(j)

21 in

22 if strict_inferieur raccourci m.(i).(j) then

23 begin

24 m.(i).(j) <- raccourci ;

25 passage.(i).(j) <- Nœud (sv.(k))

26 end

27 done

28 done

29 done ;

30 let rec distance (Sommet a) (Sommet b) = m.(index a sl).(index b sl)

31 (***** une petite recursion pour trouver le chemin de a a b *)

32 and chemin (Sommet a) (Sommet b) =

33 match passage.(index a sl).(index b sl) with

34 Direct -> [(Sommet a) ; (Sommet b)]

35 | Nœud c -> (chemin (Sommet a)(Sommet c))

36 @

37 (tl (chemin (Sommet c)(Sommet b)))

38 in

39 (distance,chemin) ;;

La ligne 2 permet de gerer la matrice passage qui servira a la reconstitution des plus il a fallu inventer unnouveauconstructeur, Nœud,car Sommet n’est pasdisponible pour unnouveau type

courts chemins. Elle sera initialisee avec Direct, ce qui indique qu’on n’a pas encore trouvede sommet intermediaire pour realiser un raccourci.

On aura note aussi que plutot qu’ecrire n matrices differentes successives, on met sim-plement a jour les elements d’une matrice unique. La matrice initiale est remplie en ligne 10par des elements de N ∪ {+∞} grace a notre fonction matrice d incidence et sommets

(vue au chapitre precedent) a laquelle on passe le graphe, la valeur Infini, et la fonction quitransforme un entier standard (int) en un entier.

En lignes 31–37 on trouvera la petite procedure recursive qui reconstitue les plus courtschemins, a l’aide de la matrice passage qui a ete mise a jour en ligne 25.

Enfin, comme on en a vu des exemples analogues dans la premiere partie de ce poly,la procedure floyd renvoie deux fonctions, distance et chemin, qui, appliquees a deuxsommets renvoient respectivement la longueur du plus court chemin qui les relie et un deces plus courts chemins. Ainsi le calcul (en O(n3)) realise dans l’algorithme de Floyd n’est-ileffectue qu’une seule fois, apres quoi on peut s’en servir a volonte.

Page 53: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

7.5. L’ALGORITHME DE WARSHALL 53

Montrons sur un exemple comment utiliser notre programme.On considere le graphe suivant : Quel est un plus court chemin de a a c?

a d

cb

15

30 5

5 155 50

15

Figure 7.2: Un graphe exemple pour l’algorithme de Floyd

Caml nous repond :

Programme 7.3 La reponse de Caml sur notre exemple pour l’algorithme de Floyd

1 #let g = [ (Sommet "a") , [ (Sommet "b"),5 ] ;

2 (Sommet "b") , [ (Sommet "a"),50 ; (Sommet "c"),15 ; (Sommet "d"),5 ] ;

3 (Sommet "c") , [ (Sommet "a"),30 ; (Sommet "d"),15 ] ;

4 (Sommet "d") , [ (Sommet "a"),15 ; (Sommet "c"),5 ] ] ;;

5 g : (sommet * (sommet * int) list) list = [Sommet "a", [Sommet "b", 5]; Sommet "b",

[Sommet "a", 50; Sommet "c", 15; Sommet "d", 5]; Sommet "c", [Sommet "a", 30; Sommet

"d", 15]; Sommet "d", [Sommet "a", 15; Sommet "c", 5]]

67 #let (distance,chemin) = floyd g ;;

8 chemin : sommet -> sommet -> sommet list = <fun>

9 distance : sommet -> sommet -> entier = <fun>

1011 #distance (Sommet "a") (Sommet "c") ;;

12 - : entier = N 15

1314 #chemin (Sommet "a") (Sommet "c") ;;

15 - : sommet list = [Sommet "a"; Sommet "b"; Sommet "d"; Sommet "c"]

1617 #

7.5 L’algorithme de Warshall

L’utilisation que nous avons faite des entiers etendus (N ∪ {+∞}) revient a dire que, quitte aconsiderer des longueurs infinies d’aretes, notre graphe est toujours connexe.

Imaginons ce que donnerait l’algorithme de Floyd sur un graphe dont toute arete serait delongueur infinie (elle ne figure pas dans le graphe) ou de longueur nulle. La longueur d’unplus court chemin entre deux sommets sera alors ou nulle (les deux sommets sont relies) ouinfinie (les deux sommets sont dans deux composantes connexes disjointes). Le plus courtchemin trouve par l’algorithme serait alors simplement un exemple de chemin reliant les deuxsommets, quand il existe.

Finalement nous resolvons ainsi la question qu’on appelle communement probleme de lafermeture transitive du graphe. Bien sur, il ne sert a rien d’etiqueter par des entiers les aretes.pourquoi cette

appellation? On utilisera un etiquetage par des booleens : true pour indiquer que l’arete existe, et falsejouera le role jusqu’alors devolu a l’infini. En outre, ceci permet de simplifier la recurrence

Page 54: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

54 LES ALGORITHMES DE FLOYD ET DE WARSHALL

qui definit les matrices successives : on aura simplement ici, avec les notations precedentes,

M(k)

i,j = M(k−1)i,j ou

(M

(k−1)i,k etM(k−1)

k,j

).

L’algorithme ainsi obtenu s’appelle l’algorithme de Warshall. il date de 1962On deduit son implementation en Caml de celle qu’on a ecrite pour l’algorithme de Floyd.

Programme 7.4 L’algorithme de Warshall

1 (*==============================================================================*)

2 (* *)

3 (* recherche de la fermeture transitive du graphe *)

4 (* algorithme de Warshall *)

5 (* *)

6 (*==============================================================================*)

78 (* warshall g renvoie une paire de deux fonctions relies,chemin *)

9 (* relies est une fonction booleenne qui dit s’il existe un chemin de a vers b *)

10 (* chemin Sommet(a) Sommet (b) decrit le chemin a utiliser *)

1112 let warshall g =

13 let n = list_length g

14 and m,sl = matrice_d_incidence_et_sommets g false (function x -> true)

15 in

16 let sv = vect_of_list sl

17 and passage = make_matrix n n Direct

18 in

19 (***** le cœur de l’algorithme de Warshall : du O(n^3) a coup sur *)

20 for k = 0 to n-1 do

21 for i = 0 to n-1 do

22 for j = 0 to n-1 do

23 if m.(i).(k) & m.(k).(j) then

24 begin

25 m.(i).(j) <- true ;

26 passage.(i).(j) <- Nœud (sv.(k))

27 end

28 done

29 done

30 done ;

31 let rec relies (Sommet a) (Sommet b) = m.(index a sl).(index b sl)

32 (***** une petite recursion pour trouver le chemin de a a b *)

33 and chemin (Sommet a) (Sommet b) =

34 match passage.(index a sl).(index b sl) with

35 Direct -> [(Sommet a) ; (Sommet b)]

36 | Nœud c -> (chemin (Sommet a)(Sommet c))

37 @

38 (tl (chemin (Sommet c)(Sommet b)))

39 in

40 (relies,chemin) ;;

On aura note en ligne 14 la fonction (function x -> true) qui remplace toute etiquetted’une arete existante en l’etiquette true, et l’utilisation de false pour nil du type.

Page 55: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 8

L’algorithme de Dijkstra

8.1 Utilite d’un nouvel algorithme

L’algorithme de Floyd, comme nous l’avons vu dans le chapitre precedent, permet de trouverune solution au probleme des plus courts chemins dans un graphe den sommets enO(n3). Lemoins qu’on puisse dire c’est que O(n3) c’est trop : on evite habituellement comme la pesteles algorithmes quadratiques (c’est-a-dire en O(n2)), alors O(n3) !!

Nous allons decrire ici un algorithme qui tourne enO(a logn), ou a est le nombre d’aretesles algorithmes sontcomme lesrealisateurs, ilstournent. . .

du graphe (suppose connexe). De fait, l’algorithme ne produit pas tout a fait le meme resultat :on se fixe un sommet, appele source, et on cherche les plus courtes distances de cette source achacun des autres sommets du graphe, ainsi qu’un plus court chemin pour chacun.

Cet algorithme est du a Dijkstra, il a ete publie en 1959. Il fait partie de la grande familledes algorithmes baptises par les Anglo-Saxons greedy algorithms, ce qu’on pourrait traduirepar algorithmes gloutons.mettons-nous en

appetit

8.2 Description de l’algorithme de Dijkstra

L’algorithme de Dijkstra s’applique a un graphe dont toutes les aretes sont etiquetees par destu verifieras quesinon l’algorithme deDijkstra peut sombrerdans une boucleinfinie

valeurs positives (ici encore nous prenons X = R = R ∪ {+∞}), sans quoi le probleme pose,nous l’avons vu, n’a pas de solution.

On se fixe donc un sommet source, et on va maintenir tout au long du deroulement denotre algorithme deux ensembles de sommets, que nous appellerons C et ∆.

le notre? celui deEdsger Wybe D. !

C est l’ensemble des sommets qui restent a visiter : au depart C = S \ {source}.∆ est l’ensemble des sommets pour lesquels on connaıt deja leur plus petite distance a la

source : au depart ∆ = {source}.On aura toujours S = C ∪ ∆, de telle sorte qu’on s’arretera quand C = ∅.Pour chaque sommet s dans ∆, on conservera dans un tableau distances sa plus courte

distance a la source, et dans un tableau passage le sommet p qui le precede dans le plus courtchemin choisi de la source a s : ce plus court chemin sera de la forme (source, . . . , p, s) — cen’est pas la convention que nous avions adoptee pour Floyd. Il sera facile de recuperer unplus court chemin avec un (unique, cette fois) appel recursif, en remontant de predecesseuren predecesseur jusqu’a la source.

Comment allons-nous donc faire “grossir” ∆ tout en respectant les conditions imposees?On initialise le tableau distances par les etiquettes des aretes de la source a chacun des

sommets de C = S \ {source}, et de meme passage par source, pour tout le monde. Onpeut interpreter cette initialisation de la facon suivante : jusqu’a ce qu’on trouve peut-etremieux, les informations dont nous disposons pour le moment font que les plus courts cheminsobserves sont simplement les aretes qui relient la source a chacun des sommets.

A chaque etape de l’algorithme, ∆ va s’enrichir d’un nouveau sommet. Si donc le grapheglouton, mais pasgoinfre

55

Page 56: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

56 L’ALGORITHME DE DIJKSTRA

est connexe, ce que nous supposerons dans la suite, nous en aurons termine en n − 1 etapes(on verra tout a l’heure que la (n− 1)-eme et derniere etape peut etre ignoree, car on n’y ferastrictement rien).

Rappelons qu’a tout moment∆ contient les sommets pour lesquels distances et passagecontiennent toute l’information relative a un plus court chemin qui les relie a la source. Ceciest vrai au debut de l’algorithme, nous en aurons donc termine si nous arrivons a expliciterune etape qui conserve cette condition et qui augmente l’ensemble ∆.

Nous choisissons un sommet s de C (ceux qui ne font pas encore partie de∆) qui minimise attention ! c’est ici lecœur de l’algorithme !distances[s], nous le supprimons de C et l’ajoutons a ∆. Puis pour chaque sommet t

qui reste dans C, nous mettons a jour distances[t] et passage[t] de la facon suivante :si distances[s] + ϕ(s, t) < distances[t], alors nous remplacons distances[t] pardistances[s] +ϕ(s, t) et passage[t] par s.

Il n’est pas evident qu’ainsi defini notre algorithme fonctionne. C’est pourtant le cas,mais notre hypothese de recurrence est insuffisante. Voici la propriete qui nous permettrade conclure, si nous arrivons a prouver qu’elle est conservee durant tout le deroulement del’algorithme :

Les tableaux distances et passage permettent d’obtenir pour tous les pointsu ∈ C un plus court chemin qui mene de la source a u en ne passant que par despoints de ∆.

Les tableaux distances et passage permettent d’obtenir pour tous les pointsu ∈ ∆ un plus court chemin qui mene de la source a u.

L’initialisation des deux tableaux a ete justement faite afin de verifier cette hypothese audebut de l’algorithme, quand ∆ = {source}.

A la fin de l’algorithme, ∆ = S, et nous avons donc bien le resultat attendu.Reste a prouver qu’a chaque etape nous conservons la propriete en question.

source

s

uv

p

c

t

∆C

0

c1

Figure 8.1: Une etape de l’algorithme de Dijkstra

Nous supposons donc que notre condition est verifiee a un moment donne de l’algorithme.Nous choisissons un sommet s de C qui minimise le tableau distances. Nous allonsverifier notre propriete pour s d’une part (on travaille sur la deuxieme propositionde l’hypothese de recurrence), puis pour tout autre sommet t ∈ C (pour la premiereproposition de l’hypothese de recurrence).Soit p = passage[s]. D’apres l’hypothese, il existe un chemin c0 = ( source, . . . , p, s)qui ne passe que par des sommets de ∆ (sauf s) et qui realise le minimum de distance.Montrons qu’il s’agit la d’un chemin minimal de la source a s.

Page 57: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

8.3. IMPLEMENTATION DE L’ALGORITHME DE DIJKSTRA EN CAML 57

Sinon, on pourrait trouver un chemin plus court, qui passe par un sommet t ∈ C (t 6= s).Un tel chemin debutant a la source et terminant a t entre dansC pour la premiere fois parun certain sommet u (il retournera eventuellement dans ∆, peu importe). Alors, commetoutes les aretes sont de poids positifs, notre chemin qui va de la source a s via u puist aura une longueur superieure ou egale a la longueur du chemin de la source a u, cedernier chemin ne passant que par des sommets de ∆. Notre choix de s assure que lalongueur de c0 est plus petite : passer par t (donc u) ne peut que rallonger le chemin.C’est gagne !Occupons nous maintenant d’un sommet t de C, distinct de s.Il s’agit de mettre a jour distances[t] et passage[t] de telle sorte que notre conditionreste verifiee. De deux choses l’une : ou bien pour trouver un plus court chemin de lasource a t en ne passant que par des points du nouveau ∆, c’est-a-dire l’ancien auquelon a ajoute s, on doit passer par s, ou bien il n’y a rien a modifier, et c’est fini.Considerons donc un tel chemin qui passerait par s. Je dis qu’il ne devrait pas retournerensuite dans ∆, sans quoi il en ressortirait par un certain v. Mais v ayant ete incorporedans∆ avant s, le chemin c0 utilise pour aller de la source a s est plus long qu’un certainchemin c1 qui va de la source a v. Ainsi il aurait ete plus court de ne pas passer par s(voir la figure).Nous sommes donc certain qu’un plus court chemin qui passe par s est de la forme(source, . . . , s, t), et l’algorithme de Dijkstra opere alors exactement la mise a jouron ajoute le poids de

l’arete (s, t) necessaire.✦

On voit pourquoi la derniere etape est inutile : il ne reste plus qu’un sommet s dans C, unplus court chemin de la source a s qui ne passe que par des sommets de ∆ = S \ {s} est biensur un plus court chemin.

8.3 Implementation de l’algorithme de Dijkstra en Caml

Rappelons tout d’abord notre implementation de N ∪ {+∞}.

Programme 8.1 Les entiers etendus

1 (* on introduit un type correspondant a N U { +infini } *)

2 type entier = Infini | N of int ;;

34 let inferieur x y = match x,y with

5 Infini,Infini -> true

6 | Infini,N(_) -> false

7 | N(_),Infini -> true

8 | N(p),N(q) -> p <= q ;;

910 let strict_inferieur x y = match x,y with

11 Infini,Infini -> false

12 | Infini,N(_) -> false

13 | N(_),Infini -> true

14 | N(p),N(q) -> p < q ;;

1516 let plus x y = match x,y with

17 Infini,Infini -> Infini

18 | Infini,N(_) -> Infini

19 | N(_),Infini -> Infini

20 | N(p),N(q) -> N(p + q) ;;

On trouvera page suivante le programme Caml qui implemente l’algorithme de Dijkstra.

Page 58: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

58 L’ALGORITHME DE DIJKSTRA

Programme 8.2 L’algorithme de Dijkstra

1 (*===============================================================================*)

2 (* *)

3 (* recherche du chemin le plus court d’un sommet fixe aux autres *)

4 (* algorithme de Dijkstra *)

5 (* *)

6 (*===============================================================================*)

78 load_object "Tas" ;;

9 #open "Tas" ;;

1011 (* dijkstra g Sommet(s) renvoie une paire de deux fonctions distance,chemin *)

12 (* distance Sommet(but) donne la plus courte distance de Sommet(s) a Sommet(b) *)

13 (* chemin Sommet(but) decrit un chemin qui realise cette plus courte distance *)

14 (* bien sur, l’algorithme de Dijkstra nous interdit les distances negatives *)

1516 let dijkstra g (Sommet(nom_source) as source) =

17 let n = list_length g

18 in

19 let distances = make_vect n Infini

20 and passage = make_vect n source

21 and m,sl = matrice_d_incidence_et_sommets g Infini (function x -> N(x))

22 in

23 let indice_source = index nom_source sl

24 and sv = vect_of_list sl

25 (* on fixe le type du tas, et sa taille maximale *)

26 and tas = liste_en_tas ( fun (Sommet a) (Sommet b)

27 -> strict_inferieur distances.(index a sl)

28 distances.(index b sl) )

29 [source] n

30 in

31 extrait_minimum tas ; (* on vide le tas *)

32 (* initialisation *)

33 for i = 0 to n-1 do distances.(i) <- m.(indice_source).(i) done ;

34 for i = 0 to n-1 do if i <> indice_source

35 then insere_dans_tas tas (Sommet(sv.(i))) done ;

36 for bcl = 1 to n-2 do

37 let (Sommet s) = extrait_minimum tas

38 in

39 let is = index s sl

40 and ensemble_C = tas_en_vecteur tas

41 in

42 for i = 0 to (vect_length ensemble_C) - 1

43 do

44 let it = index (match ensemble_C.(i) with Sommet(t) -> t) sl

45 in

46 let somme = plus distances.(is) m.(is).(it)

47 in

48 if strict_inferieur somme distances.(it) then

49 begin

50 distances.(it) <- somme ;

51 passage.(it) <- Sommet(s) ;

52 percolation tas i

53 end

54 done

55 done ;

56 let distance = function Sommet(s) -> distances.(index s sl)

57 and chemin sommet =

58 let rec cree_chemin (Sommet s) l =

59 let is = index s sl

60 in

61 if is = indice_source then source :: l

62 else cree_chemin (passage.(is)) ((Sommet s) :: l)

63 in cree_chemin sommet []

64 in distance,chemin ;;

Page 59: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

8.4. EVALUATION 59

En ligne 21 on calcule comme on en a l’habitude la matrice qui decrit ϕ. Les lignes 25–29permettent d’initialiser la structure de tas qui va implementer l’ensemble C.

Pourquoi un tas : parce que les operations que nous avons a effectuer sur C sont justementl’extraction du sommet qui realise la plus petite distance, ce pour quoi est concue une file depriorite.

Notons que nous sommes obliges de fournir une liste du type d’objets que doit contenirle tas. C’est la raison pour laquelle, en ligne 29, on fournit la liste [source]. Comme cetteinitialisation est fictive, nous revidons le tas en ligne 31. La relation d’ordre fournie est cellesur les distances : lignes 26–28.

L’initialisation proprement dite de l’algorithme se fait dans les lignes 33–35.Les lignes 36–55 forment le cœur de l’algorithme, le resultat de dijkstra etant comme on

en a maintenant l’habitude le couple (distance,chemin) qui est cree dans les lignes 56–63,avec une petite recursion pour chemin, dont nous avons deja parle.

Il n’y a rien de plus dans la boucle principale que ce que nous avons decrit ci-dessus.Simplement, remarquons l’appel en ligne 52 a la procedure percolation, afin de mettre

a sa place dans le tas le sommet pour lequel on vient de modifier l’element correspondant dedistances. Signalons que l’element en question ne peut que remonter dans l’arbre, et quereporte toi au

chapitre sur les filesde priorite

cette percolation n’a donc pas d’influence sur les objets que l’on examinera ensuite dans laboucle.

Une petite remarque pour finir : la gymnastique de la ligne 44 est necessaire puisque noussommes convenus de ne pas melanger les sommets et les noms des sommets du graphe.

8.4 Evaluation

L’initialisation, d’apres les resultats qu’on a vus pour la gestion des files de priorite, tourne enO(n).

La boucle principale du programme est executee n− 2 fois. Elle demande l’extraction duminimum qui est en O(log(n− bcl)), donc O(n). On en est donc a O(n logn).

Restent les percolations. Elles coutent chacune un O du logarithme de la taille du tas aumoment ou elle se produit. Combien y en a-t-il? pas davantage que d’aretes dans le graphe,car on les effectue quand (revoir le schema ci-dessus) le poids de l’arete (s, t) est suffisamentpetit : a chaque percolation on peut associer l’arete (s, t) qui lui correspond, et qui ne seraplus jamais la cause d’aucune autre percolation. Il y a donc O(a) percolations, qui coutenton a deja dit que a

designe le nombred’aretes du graphe

chacune O(logn).Comme le graphe est connexe, n est lui-meme O(a), et finalement le cout de l’algorithme

est bien comme annonce en O(a logn).

Page 60: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 61: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 9

Arbre couvrant minimal d’ungraphe non oriente

9.1 Presentation du probleme

Imaginons-nous une petit region qui ne dispose que d’une centrale electrique. Il s’agitd’acheminer le courant dans toutes les villes. Bien sur, tout ce qu’il suffit de faire c’estque chaque ville soit finalement reliee a la centrale, pas forcement directement. Enfin, onsouhaite pour des raisons evidentes d’economie que la longueur totale des lignes soit la pluscours du jour du

cuivre : $ 3100 latonne

petite possible. Le probleme peut etre resolu a l’aide de la theorie des graphes : c’est celui del’arbre couvrant minimal, minimal spanning tree, en anglais.

Dans ce chapitre, nous considerons un graphe non oriente et connexe.On dit qu’un grapheG ′ est un sous-graphe du grapheG si S ′ ⊂ S,A ′ ⊂ A etϕ ′ = ϕ|A ′ :

tout sommet deG ′ est un sommet deG et toute arete deG ′ est une arete deG, de meme poids.Le sous-graphe G ′ est dit couvrant si il est encore connexe et si S ′ = S : ses aretes relient

tous les sommets du graphe. Bien sur G est lui-meme couvrant donc l’ensemble des sous-graphes couvrants est non vide (et fini car S et A le sont).

Le poids total d’un graphe non oriente est la somme des etiquettes de toutes ses aretes. Leprobleme qu’on se pose est donc precisement de rechercher un sous-graphe couvrant de poidstotal minimal.

Demontrons tout d’abord une propriete qui justifiera le titre de ce chapitre :un cycle est uncheminc = (a 0 , . . . , ap ) oua 0 = ap

Theoreme 3 Si toutes les aretes du graphe sont de poids positif ou nul, il admet un sous-graphecouvrant minimal qui est aussi un arbre, c’est-a-dire un graphe sans cycle.

✧ Soit en effet G ′ un sous-graphe couvrant minimal. On a dit que l’ensemble de sessous-graphes couvrant est non vide et fini, donc on est assure de l’existence d’un telsous-graphe G ′ couvrant minimal.Si par malheur il contenait un cycle c = (a0, . . . , ap) ou a0 = ap, je dis que chacunedes aretes (a0, a1), . . . , (ap−1, ap) est de longueur nulle et que le sous-graphe obtenuen supprimant une quelconque de ces aretes est encore couvrant minimal. Ainsi, eniterant cette simplification tant que possible, on finit par tomber sur un sous-graphecouvrant minimal sans cycle, c’est-a-dire l’arbre couvrant minimal desire.En effet si je considere le graphe G ′′ obtenu en supprimant l’arete α = (ak, ak+1), ou0 ≤ k < p, le poids total de G ′′ est egal a celui de G ′ moins le poids de l’arete α.Mais G ′′ est encore couvrant : l’ensemble des sommets n’a pas bouge, et c reliait entreeux les sommets a0, . . . , ap, ce que font encore les chemins restants : (a0, . . . , ak) et(ak+1, . . . , ap) car on peut les mettre bout a bout (le graphe est non oriente) et ecrireque le chemin (ak+1, . . . , ap = a0, . . . , ak) est un chemin dans G ′′.

61

Page 62: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

62 ARBRE COUVRANT MINIMAL D’UN GRAPHE NON ORIENTE

Finalement G ′′ est encore couvrant, alors que G ′ etait couvrant minimal, donc G ′′ et Gont le meme poids et ϕ(α) = 0, comme on l’avait annonce.✦

Dans toute la suite nous supposons que G est un graphe non oriente, connexe, d’aretes delongueurs toutes positives ou nulles.

Montrons maintenant un resultat sur l’arbre couvrant minimal, qui sera au cœur desalgorithmes que nous allons etudier.

Theoreme 4 SoitG un graphe non oriente connexe d’aretes de longueurs toutes positives ou nulles.Soit S = S1∪S2 une partition de ses sommets. Soit 1A2 l’ensemble des aretes (u1, u2) du graphetelles que u1 ∈ S1 et u2 ∈ S2. Soit enfin (u, v) une arete de 1A2 de poids minimal. Alors ilexiste un arbre couvrant minimal de G qui contient cette arete (u, v).

✧ Remarquons tout d’abord qu’on est assure que 1A2 6= ∅ carG est un graphe connexe.Soit donc (u, v) de poids minimal dans 1A2, et soit G ′ un arbre couvrant minimal de Gqui ne contienne pas (u, v). Nous allons montrer comment construire a partir de G ′ unarbre couvrant minimal qui contienne notre arete (u, v) preferee.Notons tout d’abord que G ′ contient une et une seule arete (u ′, v ′) ∈ 1A2 : une, caril est couvrant, une seule, car c’est un arbre. Si en effet il y avait une autre arete(u ′′, v ′′) ∈ 1A2, u ′ et u ′′ sont connectes par G ′, ainsi que v ′ et v ′′, et on obtiendraitun cycle, ce qui est exclu.

S1 S2

u

u’

v

v’

Figure 9.1: Arbre couvrant minimal apres partition des sommets

Comme G ′ est connexe et que (u ′, v ′) est la seule arete qui fasse le pont entre S1 et S2,on est assure qu’il existe dans G ′ un chemin qui relie u et u ′ et un chemin qui relie vet v ′. Construisons alors G ′′ a partir de G ′ en supprimant l’arete (u ′, v ′) et en ajoutantl’arete (u, v).Comme (u, v) est de poids inferieur a celui de (u ′, v ′) (c’est la definition de (u, v)), lepoids total de G ′′ est inferieur au poids total de G ′. Il ne reste plus qu’a montrer queG ′′ est encore couvrant pour que G ′′ soit l’arbre cherche.Or c’est bien le cas car les aretes de G ′, donc de G ′′, permettent de relier tout sommetde S1 a u sans quitter S1, et tout sommet de S2 a v sans quitter S2. En outre il n’y a pasde cycle dans les chemins inclus dans S1 ni dans les chemins inclus dans S2, et enfin(u, v) est la seule arete qui fasse le pont entre S1 et S2 : ca marche.✦

Page 63: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

9.2. ALGORITHME DE PRIM 63

9.2 Algorithme de Prim

9.2.1 Description informelle

L’algorithme de Prim est une application directe du theoreme 4 ; il fait partie de la grandePrim a publie sonalgorithme en 1957 famille des algorithmes gloutons.

On initialise un ensemble A ′ d’aretes en posant A ′ = ∅, et un ensemble S ′ de sommets enposant S ′ = {u0}, ou u0 est un sommet quelconque du graphe G.

Ensuite, et tant que S ′ 6= S, on itere la manipulation suivante : choisir une arete (u, v)

telle que u ∈ S ′, v /∈ S ′ et qui soit de poids minimal, l’ajouter a A ′, et ajouter alors v a S ′.Quand on arrive a la fin du processus, S ′ = S, et A ′ est l’ensemble des aretes d’un arbre

couvrant minimal.L’algorithme termine car a chaque etape S ′ augmente d’un, et, pour le reste, il s’agit d’une

application directe du theoreme 4, rien de plus.

9.2.2 Implementation en Caml

Pour ecrire l’algorithme en Caml, nous utiliserons a nouveau notre definition des entiers etendusN ∪ {+∞}, que nous rappelons ci-dessous.

Programme 9.1 Les entiers etendus

1 (* on introduit un type correspondant a N U { +infini } *)

2 type entier = Infini | N of int ;;

34 let inferieur x y = match x,y with

5 Infini,Infini -> true

6 | Infini,N(_) -> false

7 | N(_),Infini -> true

8 | N(p),N(q) -> p <= q ;;

910 let strict_inferieur x y = match x,y with

11 Infini,Infini -> false

12 | Infini,N(_) -> false

13 | N(_),Infini -> true

14 | N(p),N(q) -> p < q ;;

1516 let plus x y = match x,y with

17 Infini,Infini -> Infini

18 | Infini,N(_) -> Infini

19 | N(_),Infini -> Infini

20 | N(p),N(q) -> N(p + q) ;;

On commencera par calculer, comme on l’a explique plus haut, la matrice m d’incidencedu graphe. Pour suivre l’algorithme de Prim, nous allons tout au long de son deroulementconserver dans differents tableaux l’information necessaire.

deja choisi est un tableau de booleens qui dira si oui ou non un sommet est deja dansl’ensemble S ′. Au depart seul deja choisi[0] est vrai, car nous choisissons ainsi u0 ;

plus proche est un tableau qui contient pour chaque sommet v qui n’est pas encore choisile numero du sommet u de S ′ tel que (u, v) soit de poids minimal. Au depart il contientdonc uniquement des 0, qui est le numero du sommet u0 ;

a distance de enfin conserve la longueur de l’arete (u, v) precedente. Au depart il contientdonc simplement les m.(0).(i).

Page 64: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

64 ARBRE COUVRANT MINIMAL D’UN GRAPHE NON ORIENTE

Observons d’autre part que l’algorithme de Prim demande l’execution de (n− 1) etapes :a chaque etape on ajoute un nouveau sommet a S ′ qui au depart n’en contenait qu’un et qui al’arrivee en a n ; ou encore : a chaque etape on ajoute une nouvelle arete a A ′ qui au departest vide et qui a l’arrivee en comprend (n− 1), puisqu’on a un arbre connexe a n sommets. demontre ca ! – par

recurrence,peut-etre?

Pour creer la boucle nous allons ecrire une procedure recursive boucle principale adeux arguments, la liste des aretes deA ′, et sa taille, qui se terminera quand cette taille vaudrajustement (n− 1).

A chaque etape, la recherche de l’arete a ajouter a A ′ est une tres classique recherchede minimum dans un tableau ; plus difficile est la mise a jour des tableaux plus proche eta distance de. Quand on ajoute l’arete (u, v) a A ′ et donc le sommet v a S ′, pour unsommet w quelconque qui reste dans S ′, on n’aura a modifier les elements correspondantsdans ces tableaux que si φ(v,w) ameliore la “distance” dew a S ′, c’est-a-dire si et seulementsi φ(v,w) < a distance de[iw].

On verra page suivante le listing correspondant en Caml.Quelques commentaires pour conclure :

lignes 2–12 : l’initialisation des differents tableaux ;

lignes 14–32 : la procedure cree graphe depuis liste prend une liste d’aretes et renvoiele graphe correspondant. C’est sans doute la plus compliquee de tout ce programme,mais elle n’a rien a voir avec notre sujet : l’algorithme de Prim. On pourra donc la sauteren premiere lecture ;

lignes 35–40 : la recherche du minimum. On fournit en arguments le nombre d’elements dejabalayes, le minimum courant et son indice, et donc au depart 1 (car le premier sommetest toujours dans S ′), Infini et 0 ;

lignes 42–53 : la mise a jour des tableaux plus proche et a distance de, qui ne concerneque les points pas encore choisis. Prend deux arguments : le nombre d’elements dejamis a jour et l’indice du sommet nouvellement ajoute a S ′ ;

lignes 55–64 : la boucle principale prend deux arguments, la liste courante des aretes de A ′

et le nombre d’elements dans cette liste.

9.2.3 Evaluation

On a deja dit que la boucle principale est en (n− 1) etapes.Chaque etape demande la recherche d’un minimum, enO(n), et la mise a jour des differents

tableaux, egalement en O(n).Finalement, on peut en conclure que l’algorithme de Prim tourne en O(n2).

Page 65: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

9.2. ALGORITHME DE PRIM 65

Programme 9.2 L’algorithme de Prim

1 let prim g =

2 let n = list_length g

3 in (* phase d’initialisation *)

4 let plus_proche = make_vect n 0

5 and deja_choisi = make_vect n false

6 and a_distance_de = make_vect n (N 0)

7 and m,sl = matrice_d_incidence_et_sommets g Infini (function x -> N(x))

8 in

9 let sv = vect_of_list sl

10 in

11 for i = 1 to n-1 do a_distance_de.(i) <- m.(0).(i) done ;

12 deja_choisi.(0) <- true ;

13 (* pour construire le graphe resultat *)

14 let cree_graphe_depuis_liste l =

15 let rec symetrique = function (u,v) -> (v,u)

16 and gonfle_graphe graphe = function

17 [] -> graphe

18 | arc :: q

19 -> gonfle_graphe

20 (ajoute_arete arc

21 (ajoute_arete (symetrique arc) graphe)) q

22 and ajoute_arete (i1,i2) g =

23 let s1 = Sommet(sv.(i1)) and s2 = Sommet(sv.(i2))

24 and v = match m.(i1).(i2) with N(nv) -> nv | Infini -> 999999

25 in

26 match g with

27 [] -> [ (s1,[(s2,v)]) ]

28 | (((Sommet nom),l) as a) :: q

29 -> if i1 = (index nom sl) then (s1,(s2,v)::l)::q

30 else a::(ajoute_arete (i1,i2) q)

31 in

32 gonfle_graphe [] l

33 in

34 (* on revient a l’algorithme de Prim proprement dit *)

35 let rec arete_la_moins_chere i min imin =

36 if i = n then imin

37 else if (not deja_choisi.(i))

38 & (strict_inferieur a_distance_de.(i) min)

39 then arete_la_moins_chere (i+1) (a_distance_de.(i)) i

40 else arete_la_moins_chere (i+1) min imin

41 and

42 mise_a_jour i k =

43 if i < n then

44 begin

45 if (not deja_choisi.(i))

46 & (strict_inferieur m.(k).(i) a_distance_de.(i))

47 then

48 begin

49 a_distance_de.(i) <- m.(k).(i) ;

50 plus_proche.(i) <- k

51 end ;

52 mise_a_jour (i+1) k

53 end

54 in

55 let rec boucle_principale liste k =

56 if k < n - 1 then

57 begin

58 let imin = arete_la_moins_chere 1 Infini 0

59 in

60 deja_choisi.(imin) <- true ;

61 mise_a_jour 1 imin ;

62 boucle_principale ((imin,plus_proche.(imin)) :: liste) (k+1)

63 end

64 else liste

65 in

66 cree_graphe_depuis_liste (boucle_principale [] 0) ;;

Page 66: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

66 ARBRE COUVRANT MINIMAL D’UN GRAPHE NON ORIENTE

9.3 Algorithme de Kruskal

9.3.1 Description de l’algorithme

Kruskal publie son algorithme en 1956. Il s’agit encore d’un algorithme glouton, qui se prouve broupsaisement par le theoreme 4. Cependant il part d’un point de vue radicalement different decelui de Prim.

Au lieu en effet de privilegier un sommet et de faire gonfler l’arbre couvrant en partant de j’espere que cettedescription, elle,n’est pas tropgonflante. . .

ce sommet fixe, Kruskal propose de partitionner en singletons l’ensemble des sommets, et, enutilisant les aretes de l’arbre en ordre croissant de longueur, de reunir petit a petit des classesde sommets jusqu’a n’avoir plus qu’une seule classe : l’arbre couvrant minimal est alors atteint.

Plus precisement, on trie les aretes de A, en formant une file de priorite (un tas) T . Oncree une partition en singletons P de l’ensemble S des sommets. On initialise enfin une listed’aretes L a vide.

Ensuite, tant queP n’est pas reduit a une seule classe, on effectue les operations suivantes :on extrait l’arete (u, v) la moins longue du tas T ; si u et v sont dans la meme classe de P, tantpis, on boucle ; sinon on reunit les deux classes de u et v en une seule, on ajoute l’arete (u, v)

a la liste finale L d’aretes, et on boucle.A la fin de l’algorithmeL contient une liste d’aretes qui forment un arbre couvrant minimal.

9.3.2 Implementation en Caml

On trouvera le listing correspondant page ci-contre.Quelques commentaires :

lignes 1–5 : chargement des bibliotheques qui nous seront utiles ;

lignes 11–18 : cree liste des aretes cree la liste des aretes en tant que (u, v,ϕ(u, v))

dont on fera le tas T en ligne 45 avec la relation d’ordre adequate ;

lignes 20–36 : on retrouve ici la meme procedure que dans l’algorithme de Prim, pour recons-tituer le graphe final a partir de la liste d’aretes correspondante ;

lignes 38–40 : on cree ici la liste des sommets, par une petite procedure recursive, afin de sepreparer a la creation, ligne 46, de la partition P initiale ;

lignes 49–62 : la boucle principale, qui prend en arguments la liste couranteL et sa taille. Onquitte la boucle quand cette taille est egale a (n − 1), puisque c’est le nombre d’aretesd’un arbre couvrant. La ligne 56 teste si les deux extremites de l’arete extraite du tasT en ligne 52 sont ou non dans la meme classe de P . Si oui, on fusionne, et on boucleavec une liste L agrandie (ligne 58) ; sinon on boucle avec la liste initiale, ligne 60.

9.3.3 Evaluation

La creation du tas se fait en O(a), ou a est le nombre d’aretes. Celle de la partition en O(n). il est temps de rendredes comptesOn a deja dit qu’il y avait (n− 1) etapes dans la boucle principale. Chaque etape demande

une recherche-fusion dans la partition, et une extraction du tas. L’extraction se realise enO(loga), a toutes bonnes fins de percolation.

Pour ce qui est de la partition, on a deja evalue l’ensemble de toutes les operations ef-fectuees : on part en effet d’une partition en n singletons et on arrive a une partition en uneseule classe. Nous savons que tout cela couteO(a), pour des valeurs raisonnables de n (revoirla discussion correspondante dans le chapitre sur les partitions).

Or n − 1 ≤ q ≤ n2, donc en particulier O(loga) est O(logn). Au total, l’algorithme deKruskal tourne donc en O(a+ n logn).

Page 67: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

9.3. ALGORITHME DE KRUSKAL 67

Programme 9.3 L’algorithme de Kruskal

1 load_object "Partitions" ;;

2 #open "Partitions" ;;

34 load_object "Tas" ;;

5 #open "Tas" ;;

67 (* kruskal g renvoie un nouveau graphe (en fait un arbre) qui constitue *)

8 (* un arbre de recouvrement minimal du graphe non oriente donne *)

910 let kruskal g =

11 let rec cree_liste_des_aretes accu_final g =

12 let rec devide accu source = function

13 [] -> accu

14 | (s,v) :: q -> devide ((source,s,v)::accu) source q

15 in

16 match g with

17 [] -> accu_final

18 | (s,l) :: q -> devide (cree_liste_des_aretes accu_final q) s l

19 in

20 let cree_graphe_depuis_liste l =

21 let rec symetrique = function (u,v,w) -> (v,u,w)

22 and

23 gonfle_graphe graphe = function

24 [] -> graphe

25 | arc :: q -> gonfle_graphe

26 (ajoute_arete arc

27 (ajoute_arete (symetrique arc) graphe)) q

28 and

29 ajoute_arete =

30 function ((((Sommet nom1) as s1),s2,v) as arc) -> function

31 [] -> [ (s1,[(s2,v)]) ]

32 | (((Sommet n) as a),l) :: q ->

33 if eq_string n nom1 then (a,(s2,v)::l)::q

34 else (a,l)::(ajoute_arete arc q)

35 in

36 gonfle_graphe [] l

37 in

38 let rec cree_liste_des_sommets accu = function

39 [] -> accu

40 | (s,l) :: q -> cree_liste_des_sommets (s::accu) q

41 in

42 let la = cree_liste_des_aretes [] g

43 and ls = cree_liste_des_sommets [] g

44 in

45 let tas = liste_en_tas ( fun (_,_,p) (_,_,q) -> p < q ) la (list_length la)

46 and les_nœuds = liste_en_partition ls (function s -> (index s ls) )

47 and ns = list_length ls

48 in

49 let rec boucle_principale liste n =

50 if n < ns - 1 then

51 begin

52 let (s1,s2,v) = extrait_minimum tas

53 in

54 let n1,n2 = (trouve_paquet les_nœuds s1),(trouve_paquet les_nœuds s2)

55 in

56 if n1 <> n2 then

57 ( fusionne_paquets les_nœuds n1 n2 ;

58 boucle_principale ((s1,s2,v) :: liste) (n+1)

59 )

60 else boucle_principale liste n

61 end

62 else liste

63 in

64 cree_graphe_depuis_liste (boucle_principale [] 0) ;;

Page 68: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 69: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Partie III

Quelques algorithmes degeometrie combinatoire

69

Page 70: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 71: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 10

Enveloppe convexe d’un ensemblede points du plan

10.1 Rappels sur la convexite

Rappelons qu’une partieX du plan est dite convexe si elle est vide ou si pour tout couple (a, b)

de points deX le segment [ab] est tout entier inclus dansX. On demontre alors tres simplementque toute intersection de convexes est convexe, et on est conduit tout naturellement a definirl’enveloppe convexe d’une partie quelconque non videX du plan comme l’intersection de tousles Anglo-Saxons

disent convex hull les convexes qui contiennentX, puisque bien sur le plan lui-meme est convexe. Nous noteronsdans la suite Conv(X) l’enveloppe convexe de X, et en cas de besoin ∂Conv(X) sa frontiere.

Nous nous interesserons ici plus particulierement du cas ou X est un ensemble fini den points du plan. On notera qu’alors ∂Conv(X) est un polygone (convexe, evidemment), etrechercher l’enveloppe convexe de X reviendra a donner la liste ordonnee (en general dans lesens trigonometrique) des sommets successifs de ce polygone frontiere.

On va voir dans la suite — par deux approches totalement differentes — des algorithmesqui permettent de reconstituer l’enveloppe convexe d’un ensemble de n points du plan enO(n logn).

Figure 10.1: Un exemple d’enveloppe convexe dans le plan

71

Page 72: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

72 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

10.2 Algorithme de Graham-Andrew

10.2.1 La marche de Graham

La premiere approche repose sur la classification des angles que forment deux cotes successifsd’un polygone : on distinguera en effet les angles saillants et les angles rentrants (voir la figuresuivante).

P

P

Q

Q

R

R

Angle saillant Angle rentrant

Figure 10.2: Classification des angles au sommet d’un polygone

La definition de cette classification suppose qu’on utilise une orientation appropriee. Con-siderons par exemple un polygone convexe, et parcourons la suite de ces sommets dans l’ordrecroissant de leurs angles polaires (dans le sens trigonometrique, donc) : tout point interieur aupolygone reste alors toujours a gauche de notre parcours, ou, ce qui revient au meme, tout angleau sommet du polygone est un angle saillant. Sur la figure precedente, a droite, le fait quePQR soit un angle rentrant suffit a prouver que Q ne fait pas partie de l’enveloppe convexede l’ensemble des points {. . . , P,Q, R, . . .}. C’est la remarque qui est au cœur de l’algorithmede Graham.

En outre, il est tres facile de tester si un angle est saillant ou rentrant : PQR est saillant si et

seulement si det(−→PQ,−→QR) est positif, et on s’en sort avec une comparaison, deux multiplications une bagatelle !

et une soustraction.Precisons maintenant l’algorithme de Graham proprement dit, c’est-a-dire ce qu’on appelle

traditionnellement la marche de Graham. On commence par ranger les n points par ordre tu parles d’unetradition, ca date de1972 !

croissant de leurs angles polaires (on fixe une fois pour toutes l’origine en un point interieura Conv(X); il suffit pour cela de prendre le milieu de deux points de X, par exemple), ou deleurs distances a l’origine, s’ils ont meme angle polaire. On va les numeroter suivant l’ordreainsi defini, mais en commencant par un point p0 dont on peut etre sur qu’il fait partie de∂Conv(X). Il suffit pour cela de prendre un point extremal, par exemple un de ceux qui ontla plus grande abscisse. On a finalement numerote les points p0, p1, . . . , pn−1. On convientaussi d’utiliser une numerotation modulo n, ce qui revient par exemple a ecrire pn = p0, oup−1 = pn−1.

L’algorithme de Graham est alors le suivant : on va avancer dans la liste des points, jusqu’aretrouver p0, en supprimant au fur et a mesure les points qui ne font pas partie de ∂Conv(X).

Page 73: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10.2. ALGORITHME DE GRAHAM-ANDREW 73

A la fin du processus, la liste des points restants contient la suite ordonnee des sommets dupolygone-frontiere de l’enveloppe convexe cherchee.

A chaque etape on considere trois points consecutifs pk, pk+1, pk+2. De deux choseson numerote toujoursmodulo n l’une :

pkpk+1pk+2 est un angle saillant et on avance : k← k+ 1 ;

pkpk+1pk+2 est un angle rentrant et alors le sommet pk+1 ne peut etre sur le polygonecherche. On le supprime donc de la liste, mais on est oblige de revenir en arriere, et onpose donc k← k− 1.

p0

p1p

2

p3

p4p

5

p6

p7

p8

p9

p10

Figure 10.3: La marche de Graham

Dans l’exemple de la figure ci-dessus, voici la liste des triplets successivement consideres :p0p1p2, p1p2p3, p2p3p4 (on supprime p3), p1p2p4 (on supprime p2), p0p1p4, p1p4p5,p4p5p6, p5p6p7 (on supprime p6), p4p5p7, p5p7p8 (on supprime p7), p4p5p8, p5p8p9,p8p9p10 (on supprime p9), etc.

On est sur que l’algorithme termine car p0 doit rester dans la liste finale, et car a chaqueetape, soit on est dans le premier cas, et on avance clairement, soit on est dans le second cas,mais c’est qu’on supprime un point, et ca ne peut arriver plus de n fois, evidemment ! Doncnon seulement l’algorithme termine, mais en plus il est lineaire.id est en O(n)

Bizarre, non? on avait annonce un algorithme enO(n logn). . . Simplement, il faut penserau cout du tri de la phase de preparation de l’algorithme, pour retrouver leO(n logn) prevu.

10.2.2 L’algorithme de Graham est optimal

On sait qu’un tri en O(n logn) est optimal. Pour prouver que l’algorithme de Graham eston devrait savoir. . .optimal, il suffit donc de montrer que si on sait resoudre le probleme de la recherche del’enveloppe convexe en un temps T(n), on peut en deduire un algorithme de tri qui tournedans le meme temps : cela prouvera que T(n) est au moins O(n logn).

Supposons donc resolu le probleme de l’enveloppe convexe, et deduisons en un algorithmede tri.

Soit n reels x0, x1, . . . , xn−1, auxquels on fait correspondre n points mi sur l’axe desx. Soit alors, pour chaque i, pi le point d’abscisse xi (comme mi) de la parabole d’equationy = x2 + 1. Rechercher l’enveloppe convexe des points pi c’est alors fournir dans l’ordretrigonometrique les sommets consecutifs du polygone-frontiere, et c’est donc numeroter les pien ordre croissant de leurs abscisses. Bref, c’est trier nos n reels. CQFD.

Page 74: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

74 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

x

p

mi

i

Figure 10.4: Optimalite de l’algorithme de Graham

Cette methode de transformation d’un probleme en un autre est une methode privi-legiee pour majorer ou minorer le cout optimal d’un algorithme, et peut etre utiliseepour prouver l’optimalite de nombreux algorithmes classiques. On prendra toutefois

garde a ne pas oublier de verifier que la transformation elle-meme ne requiert pas un tempsd’un ordre egal ou, pire, superieur aux temps qu’on est en train de comparer. Dans le caspresent, la transformation se fait en un temps lineaire, et notre demonstration est bien correcte.

10.2.3 L’amelioration de l’algorithme de Graham par Andrew

On peut toutefois reprocher a l’algorithme de Graham les calculs qu’imposent le tri initial despoints selon leurs angles polaires. Sensible a ce probleme, Andrew propose en 1979 une petitemodification de l’algorithme de Graham.

g

d

X

X

Figure 10.5: L’idee de Andrew

Il considere les points g et d extremaux en abscisses : g est le point le plus a gauche, et d leplus a droite. Bien sur, il est certain que g et d font partie de ∂Conv(X). Andrew trace alors la les points qui

seraient sur (gd)

peuvent etre ignoresdroitegd et partage ainsiX en deux partiesX+ etX− auxquelles il va appliquer separement une

Page 75: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10.3. STRATEGIE DIVISER POUR REGNER 75

marche de Graham un peu modifiee. Le segment [gd] est un des cotes du polygone ∂Conv(X+),les autres cotes forment ce que Andrew baptise la frontiere superieure de l’enveloppe convexede X, qui est limitee par g et d. Il definit de meme la frontiere inferieure.

Andrew annonce alors qu’il suffit d’appliquer a chacune des parties X+ et X− la marchede Graham habituelle, sauf qu’on trie les points tout simplement par abscisses !

En realite, Andrew utilise exactement la meme methode que Graham, a un passage prespar la geometrie projective, ou il envoie a l’infini le pole qu’utilisait Graham. C’est le memealgorithme, on s’est simplement arrange — par un changement de repere (projectif) — pouravoir des calculs plus simples.

10.3 Strategie diviser pour regner

Nous allons maintenant decrire un algorithme de resolution du meme probleme de l’enveloppeconvexe d’un ensemble X de n points, mais qui utilise cette fois la strategie diviser pour regner.On verra qu’il est comparable en efficacite au precedent, puisqu’egalement en O(n logn).

Bien entendu, fidele a la strategie, nous decoupons l’ensemble initial en deux parties detailles a peu pres egales, X1 et X2. On applique alors l’algorithme recursivement sur chacunede ces moities, obtenant ainsi les deux polygones convexes ∂Conv(X1) et ∂Conv(X1). Toutle probleme reside donc dans la recherche de l’enveloppe convexe de la reunion de deuxpolygones convexes, et c’est ce a quoi nous allons nous interesser maintenant.

10.3.1 Algorithme de Shamos

Shamos imagine la methode suivante en 1978.Soit donc P1 et P2 deux polygones convexes, ayant respectivement n1 et n2 sommets, on

souhaite construire l’enveloppe convexe de leur reunion.Shamos propose de considerer un point p interieur a P1. Pour cela, il suffit de considerer

le centre de gravite de trois des sommets de P1.Alors, de deux choses l’une :

p est interieur a P2 : voir la premiere partie de la figure 10.6, page suivante. On dispose deslistes ordonnees des sommets des deux polygones P1 et P2. Observons que comme p estinterieur aux deux polygones, ce sont aussi des listes de sommets ordonnees par leursangles polaires (pour le pole p). On peut les fusionner en un temps O(n1 + n2) pourformer la liste des sommets de P1 ∪ P2 ordonnee par ordre croissant des angles polaires.Il suffit alors d’utiliser l’algorithme de Graham pour recuperer Conv(P1 ∪ P2).

p est exterieur a P2 : voir la seconde partie de la figure 10.6, page suivante. Cette fois, onpeut (en un temps de l’ordre deO(n2)) determiner les deux sommetsu et v pour lesquelsl’angle polaire ps est extremal pour s ∈ P2. On a ainsi coupe le contour de P2 en deuxparties, et l’on abandonne celle qui correspond aux sommets qui ne feront pas partie del’enveloppe convexe cherchee. On procede alors comme precedemment, en fusionnant laliste ordonnee des sommets de P1 et celle de ceux des sommets de P2 qu’on a conserves,puis en appliquant l’algorithme de Graham.

Le lecteur attentif aura pu se demander comment determiner rapidement si un pointp donne est interieur ou non au polygone convexe P2 . La reponse est simple : onconsidere la droite horizontale passant par p. Si elle contient un des cotes du poly-

gone, p est exterieur au polygone et on a termine. Sinon, on cherche ses points d’intersectionavec les differents cotes du polygone. On en trouvera deux. Si p est entre eux c’est que p estinterieur au polygone, et exterieur sinon. Tout cela se fait en O(n 2 ).

Au total, puisque n1 + n2 ≤ n, l’etape de fusion tourne en O(n) et notre algorithme enO(n logn).

Page 76: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

76 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

p

P

P

1

2

21

3

456

8

9

11

1314

12

10

7

p

P

P

1

2

u

v

1

34

56

7

8

9

11

10

2

12

Figure 10.6: Les deux cas : p est interieur ou exterieur a P2

10.3.2 Algorithme de Preparata et Hong

Cet algorithme, un peu plus simple, date de 1977.Il s’agit toujours de determiner Conv(P1∪P2), mais, cette fois, on suppose qu’on a partage

l’ensemble initial en deux parties de meme taille par une droite verticale. De cette facon, onest sur que les interieurs de P1 et de P2 sont disjoints.

Notre intention est de determiner les deux aretes de ∂Conv(P1 ∪ P2) qui ne font pas dejapartie des polygones eux-memes, a savoir ce qu’on appelle les ponts inferieur et superieur. Sereferant a la figure 10.7, nous allons expliquer comment trouver le pont inferieur, note ici [5f].

On part du point le plus a droite du polygone de gauche, a savoir 1, et du point le plus agauche du polygone de droite, a. Depuis 1, on trace les aretes [1a], [1b], [1c], mais pas [1d] on tourne dans le

sens retrogradequand on s’appuie agauche

qui nous ferait remonter.On s’appuie maintenant sur c, et on trace [1c], [2c], [3c], [4c], mais pas [5c] qui nous ferait

on tourne dans lesens direct quand ons’appuie a droite

rebrousser chemin.Et on itere, s’appuyant sur 4, tracant [4c], [4d], [4e] et [4f]. On s’appuie enfin sur f, et

trace [4f], [5f], ou l’on s’arrete car, que l’on s’appuie a gauche ou a droite, on ne peut plus

Page 77: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10.3. STRATEGIE DIVISER POUR REGNER 77

1

2

3

4

5

a

b

c

d

ef

pont inférieur

pont supérieur

Figure 10.7: L’algorithme de Preparata et Hong

Page 78: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

78 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

tourner. On a trouve le pont inferieur.Mutatis mutandis, on trouve de meme le pont superieur.On aura note que cette fusion se fait bien enO(n1 +n2), ce qui garantit que l’algorithme

entier tourne en O(n logn).

10.4 Implementation en Caml

10.4.1 Le tri-fusion

Nous rappelons brievement l’implementation du tri-fusion, qui a l’immense avantage sur le tridit rapide d’etre beaucoup plus simple a ecrire, et qui en outre profite au maximum d’un ordrepartiel, alors que c’est la configuration la pire pour le tri rapide. De toutes facons, il s’agit d’unalgorithme en O(n logn).

Programme 10.1 Le tri-fusion

1 let rec decoupe_en_deux = function

2 [] -> [],[]

3 | [a] -> [a],[]

4 | a :: b :: q -> let l1,l2 = decoupe_en_deux q

5 in (a :: l1),(b :: l2)

6 and fusionne inf l1 l2 = match l1,l2 with

7 [],_ -> l2

8 | _,[] -> l1

9 | a :: q1 , b :: q2 -> if (inf a b) then a :: (fusionne inf q1 l2)

10 else b :: (fusionne inf l1 q2)

11 and tri_fusion inf = function

12 [] -> []

13 | [a] -> [a]

14 | l -> let l1,l2 = decoupe_en_deux l

15 in fusionne inf (tri_fusion inf l1) (tri_fusion inf l2) ;;

La fonction tri fusion prend en argument la relation d’ordre≺ et la liste a trier. Dans lasuite, nous supposerons ce tri compile et accessible par les habituels #open et load object.

10.4.2 Quelques fonctions utilitaires

Nous commencons par les quelques fonctions qui nous serons utiles dans la suite (voir leprogramme 10.2).

On representera un point par un couple d’entiers. On definit simplement les fonctions quidonnent abscisse et ordonnee d’un point, ainsi que le determinant de deux vecteurs du plan,ce qui permet d’ecrire les fonctions saillant, rentrant, et, en prime, alignes.

10.4.3 La recherche des points extremaux d’un polygone convexe

Dans toute la suite, nous representons un polygone convexe par le cycle de ses sommets,pris dans l’ordre trigonometrique. Pour parler d’un point particulier du polygone, on fournirale plus souvent le cycle complet, mais apres le nombre de rotations necessaire pour que lesommet qui nous interesse se presente le premier dans le cycle. Bref, plutot que de donner unpoint, on donne un pointeur sur ce point dans le cycle.

Dans le programme 10.3, on s’interesse a la recherche des points extremaux d et g des deuxpolygones P1 et P2, ainsi qu’on l’a vu dans l’algorithme decrit ci-avant. d est le point le plusa droite du polygone de gauche (P1), et g le point le plus a gauche du polygone de droite (P2).

Les lignes 19–31 permettent de trouver le point d. Les fonctions xici, xav, xap renvoientles abscisses du sommet vise du polygone, de son successeur et de son predecesseur dans

Page 79: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10.4. IMPLEMENTATION EN CAML 79

Programme 10.2 Recherche de l’enveloppe convexe : utilitaires

1 load_object "Cycles" ;;

2 #open "Cycles" ;;

34 load_object "tri_fusion" ;;

5 #open "tri_fusion" ;;

67 type point == int * int ;;

89 let abscisse = function (px,_) -> px ;;

10 let ordonnee = function (_,py) -> py ;;

1112 let vecteur (px,py) (qx,qy) = (qx-px,qy-py) ;;

1314 let determinant (ax,ay) (bx,by) = ax * by - ay * bx ;;

1516 let saillant p q r = 0 < (determinant (vecteur p q) (vecteur q r)) ;;

17 let rentrant p q r = 0 > (determinant (vecteur p q) (vecteur q r)) ;;

18 let alignes p q r = 0 = (determinant (vecteur p q) (vecteur q r)) ;;

Programme 10.3 Recherche de l’enveloppe convexe : les points extremaux

19 let point_droit p =

20 let xici p = abscisse (valeur_cycle p)

21 and xav p = abscisse (valeur_cycle (avance_cycle p))

22 and xap p = abscisse (valeur_cycle (recule_cycle p))

23 in

24 let rec roule_avant p =

25 if (xici p) < (xav p) then roule_avant (avance_cycle p)

26 else p

27 and roule_derriere p =

28 if (xici p) < (xap p) then roule_derriere (recule_cycle p)

29 else p

30 in

31 if (xici p) < (xav p) then roule_avant p else roule_derriere p ;;

3233 let point_gauche p =

34 let xici p = abscisse (valeur_cycle p)

35 and xav p = abscisse (valeur_cycle (avance_cycle p))

36 and xap p = abscisse (valeur_cycle (recule_cycle p))

37 in

38 let rec roule_avant p =

39 if (xici p) > (xav p) then roule_avant (avance_cycle p)

40 else p

41 and roule_derriere p =

42 if (xici p) > (xap p) then roule_derriere (recule_cycle p)

43 else p

44 in

45 if (xici p) > (xav p) then roule_avant p else roule_derriere p ;;

Page 80: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

80 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

l’ordre trigonometrique. On ira en avant ou en arriere selon que l’on est avant ou apres le pointcherche, choix qui est opere en ligne 31.

Si par exemple on est avant le point d, on appelle roule avant, definie dans les lignes 24–26, qui avance jusqu’a ce que l’abscisse du point se remette a decroıtre : c’est que d est le pointd’abscisse maximale.

On obtient de facon analogue le point g.

10.4.4 L’algorithme de Preparata et Hong : les ponts inferieur et superieur

Nous decrirons ici simplement la fonction pont inferieur des lignes 46–72 du pro-gramme 10.4. On se donne les deux polygones par les cycles correspondants, en visant di-rectement les points d et g vus precedemment.

Il va s’agir d’iterer la succession des deux rotations en appui sur P1 et en appui sur P2,jusqu’a ce qu’on ait trouve le pont inferieur. A ce moment la, aucune de ces deux rotations nesera plus possible, ce qui signalera la fin du processus.

C’est l’argument niveau qui permet ce controle. On part avec une valeur nulle (ligne 72), une logique a troisetatset on ajoute 1 a chaque fois qu’une rotation est bloquee (lignes 61 et 67). En revanche, des

qu’une rotation est reussie, on remet niveau a 0, puisque tout est a recommencer : c’est ce quiarrive aux lignes 60 et 66. Enfin, on arrete l’iteration quand niveau vaut 2 (test en ligne 69).

Les rotations en appui a gauche ou a droite se font sans difficultes, il suffit de ne pas setromper dans le test avec saille ou rentre, qui sont des versions modifiees de saillant etrentrant pour tenir compte du cas des points alignes.

Notons qu’ici aussi pont inferieur renvoie non pas un couple de points, mais le coupledes deux cycles qui representent les polygones, en visant les points interessants.

10.4.5 La fonction enveloppeConvexe

On termine par la fonction enveloppeConvexe proprement dite (voir le programme 10.5),qui prend en argument une liste de points et rend un cycle representant le polygone convexefrontiere de l’enveloppe convexe souhaitee.

On utilise une fonction recursive enveloppeConvexeRecursif a laquelle on fournit laliste des points tries en abscisse (ligne 138). Ainsi est-on sur de ne pas realiser ce tri plus d’unefois.

Interessons-nous donc a cette fonction recursive (lignes 102 a 135).Les lignes 103–108 evacuent les cas les plus elementaires d’une liste de moins de 4 points,

pour lesquels on fournit directement la reponse, en faisant attention a respecter le senstrigonometrique pour ranger les points d’un triangle.

Les lignes 120–125 definissent la petite et tres simple fonction coupe en deux qui prendune liste et renvoie le couple de listes (prefixe,suffixe) forme de ses deux moities.

On peut ainsi utiliser la strategie diviser pour regner en coupant en deux la liste initialepuis appelant recursivement le calcul de l’enveloppe convexe sur chacune des deux moities(lignes 109–113).

Il est alors temps d’utiliser ce qui precede en calculant les deux points extremaux (ligne 114),puis le pont inferieur (ligne 116), et enfin le pont superieur (ligne 117).

Reste a fusionner le tout. C’est la tache de fusionne polygones, qui est definie enlignes 126–135.

Le pont inferieur etant [pq], on accumule a rebours les differents points du pourtour.D’abord les points de P1 entre p et p ′, puis le pont superieur [p ′q ′], et enfin les points de P2entre q ′ et q.

La fonction tour1, par exemple, prend en arguments la liste d’accumulation, le cycle encours, et la balise qui indique le dernier sommet a accumuler (en l’occurrence p ′). Elle s’ecrit(lignes 127–129) a l’aide d’un simple appel recursif.

Pour terminer, il n’y a plus qu’a convertir la liste accumulee en un cycle. Le tour est joue.

Page 81: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

10.4. IMPLEMENTATION EN CAML 81

Programme 10.4 Recherche de l’enveloppe convexe : les ponts inferieur et superieur

46 let pont_inferieur d g =

47 let saille p q r =

48 let d = determinant (vecteur p q) (vecteur q r)

49 in

50 (d > 0) or ( (d = 0) & ((ordonnee p) > (ordonnee r)) )

51 and rentre p q r =

52 let d = determinant (vecteur p q) (vecteur q r)

53 in

54 (d < 0) or ( (d = 0) & ((ordonnee p) > (ordonnee r)) )

55 in

56 let rec tourne_en_appui_gauche (p1,p2,niveau) =

57 if saille (valeur_cycle p2)

58 (valeur_cycle p1)

59 (valeur_cycle (avance_cycle p2))

60 then tourne_en_appui_gauche (p1,(avance_cycle p2),0)

61 else (p1,p2,niveau + 1)

62 and tourne_en_appui_droit (p1,p2,niveau) =

63 if rentre (valeur_cycle p1)

64 (valeur_cycle p2)

65 (valeur_cycle (recule_cycle p1))

66 then tourne_en_appui_droit ((recule_cycle p1),p2,0)

67 else (p1,p2,niveau + 1)

68 and itere (p1,p2,niveau) =

69 if niveau < 2 then

70 itere (tourne_en_appui_droit (tourne_en_appui_gauche (p1,p2,niveau)))

71 else p1,p2

72 in itere (d,g,0) ;;

7374 let pont_superieur d g =

75 let saille p q r =

76 let d = determinant (vecteur p q) (vecteur q r)

77 in

78 (d > 0) or ( (d = 0) & ((ordonnee p) < (ordonnee r)) )

79 and rentre p q r =

80 let d = determinant (vecteur p q) (vecteur q r)

81 in

82 (d < 0) or ( (d = 0) & ((ordonnee p) < (ordonnee r)) )

83 in

84 let rec tourne_en_appui_gauche (p1,p2,niveau) =

85 if rentre (valeur_cycle p2)

86 (valeur_cycle p1)

87 (valeur_cycle (recule_cycle p2))

88 then tourne_en_appui_gauche (p1,(recule_cycle p2),0)

89 else (p1,p2,niveau + 1)

90 and tourne_en_appui_droit (p1,p2,niveau) =

91 if saille (valeur_cycle p1)

92 (valeur_cycle p2)

93 (valeur_cycle (avance_cycle p1))

94 then tourne_en_appui_droit ((avance_cycle p1),p2,0)

95 else (p1,p2,niveau + 1)

96 and itere (p1,p2,niveau) =

97 if niveau < 2 then

98 itere (tourne_en_appui_droit (tourne_en_appui_gauche (p1,p2,niveau)))

99 else p1,p2

100 in itere (d,g,0) ;;

Page 82: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

82 ENVELOPPE CONVEXE D’UN ENSEMBLE DE POINTS DU PLAN

Programme 10.5 Recherche de l’enveloppe convexe : l’algorithme proprement dit

101 let enveloppeConvexe l =

102 let rec enveloppeConvexeRecursif l = match l with

103 [] -> liste_en_cycle l

104 | [p] -> liste_en_cycle l

105 | [p;q] -> liste_en_cycle l

106 | [p;q;r] -> if saillant p q r

107 then liste_en_cycle [p;q;r]

108 else liste_en_cycle [p;r;q]

109 | _ -> let l1,l2 = coupe_en_deux l

110 in

111 let p1,p2 = (enveloppeConvexeRecursif l1),

112 (enveloppeConvexeRecursif l2)

113 in

114 let d,g = (point_droit p1),(point_gauche p2)

115 in

116 let p,q = pont_inferieur d g

117 and p’,q’ = pont_superieur d g

118 in

119 fusion_polygones p q p’ q’

120 and coupe_en_deux l =

121 let rec coupure_recursive i l =

122 if i <= 0 then [],l

123 else let l1,l2 = coupure_recursive (i-1) (tl l)

124 in ((hd l) :: l1) , l2

125 in coupure_recursive ((list_length l) / 2) l

126 and fusion_polygones p q p’ q’ =

127 let rec tour1 accu c val_p’ =

128 if (valeur_cycle c) = val_p’ then val_p’ :: accu

129 else tour1 ((valeur_cycle c) :: accu) (recule_cycle c) val_p’

130 and tour2 accu c val_q =

131 if (valeur_cycle c) = val_q then val_q :: accu

132 else tour2 ((valeur_cycle c) :: accu) (recule_cycle c) val_q

133 in

134 liste_en_cycle (tour2 (tour1 [] p (valeur_cycle p’))

135 q’ (valeur_cycle q))

136 in

137 enveloppeConvexeRecursif

138 (tri_fusion (fun (px,_) (qx,_) -> px < qx) l) ;;

Page 83: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Chapitre 11

Problemes de proximite dans leplan

Dans ce chapitre, nous etudions quelques problemes autour de la notion de proximite dans leplan euclidien, ou nous considerons un ensemble S de n points.

11.1 Quelques problemes classiques

11.1.1 La paire la plus rapprochee

Un des premiers problemes qu’on peut etre amene a resoudre est le probleme de la paire laplus rapprochee : il s’agit de determiner ceux des points qui sont les plus proches. Plusprecisement, il s’agit de determiner une paire {v,w} de points distincts de S tels que

d(v,w) = min{d(u1, u2) / u1 6= u2, u1, u2 ∈ S}.

Une application est la suivante : pour un aiguilleur du ciel, rechercher les deux avions lesplus proches et qui risquent donc le plus d’entrer en collision.

Nous nous interesserons en detail a ce probleme dans la section suivante.

11.1.2 Les plus proches voisins

Cette fois on dira que, si a et b sont deux points distincts de S, b est un plus proche voisin dea ce que nous noterons a→ b si

d(a, b) = minc∈S\{a}

d(a, c).

Notons qu’il n’y a bien sur pas unicite d’un plus proche voisin d’un point donne. Remar-quons encore que a → b n’implique pas b → a. D’ailleurs Pielou demontre en 1977 que laprobabilite qu’une paire de deux points distincts a et b du plan verifie a la fois a → b etb→ a est egale a

8π+ 3√3≈ 0,6215.

La figure 11.1, page suivante, montre le graphe de la relation sur un exemple.

11.1.3 Triangulation

Il s’agit maintenant de partitionner l’enveloppe convexe des points consideres en triangles.C’est ce que pratiquent regulierement les geometres.

Nous retrouverons ce probleme en liaison avec les diagrammes de Voronoı.

83

Page 84: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

84 PROBLEMES DE PROXIMITE DANS LE PLAN

Figure 11.1: La relation→ de plus proche voisinage

11.2 La paire la plus rapprochee

11.2.1 Premiere analyse

Nous allons nous attaquer au probleme de la paire la plus rapprochee a l’aide de notre strategiefavorite : diviser pour regner. Soit T(n) le cout de notre algorithme pour un ensemble S de npoints. Nous partageons S en deux sous-ensembles S1 et S2, sur lesquels nous appliquonsnotre algorithme de facon recursive. Voila qui coute deja 2T(n/2). Il reste a resoudre leprobleme pour S grace a l’information que fournissent les resolutions pour S1 et S2, ce quidoit etre fait en O(n) si nous visons un cout global en O(n logn). C’est bien la toute la l’algorithme naıf

tourne lui en O(n2)difficulte.

Supposons en effet que {p1, p2} (resp. {q1, q2}) soit la paire la plus rapprochee de S1(resp. S2). La paire la plus proche est alors ou bien {p1, p2}, ou bien {q1, q2}, ou bien encore{p3, q3} ou p3 ∈ S1 et q3 ∈ S2. Il faut donc a priori O((n/2)2) = O(n2) pour terminer, entestant les p3 et les q3. Cela nous conduirait a un cout global enO(n2 logn), ce qui fait trop.

11.2.2 Un cas particulier : la dimension 1

Nous allons nous interesser maintenant au cas particulier de la dimension 1 (tous les pointssont alignes sur une droite), en esperant trouver une idee generalisable a la dimension 2.

On a donc partitionne l’ensemble S en deux parties S1 et S2, en coupant la droite qui lescontient a une abscissem, puis on a trouve les paires les plus rapprochees, {p1, p2} et {q1, q2}, on confond ici points

et abscissesde S1 et S2 (cf. figure11.2).

m

q q qpp p1 2 3 3 1 2

S1

S2

Figure 11.2: Diviser pour regner sur une droite

Page 85: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.2. LA PAIRE LA PLUS RAPPROCHEE 85

Posons δ1 = d(p1, p2), δ2 = d(q1, q2), et δ = min{δ1, δ2}. La remarque qui va noussauver est la suivante : sip3 ∈ S1 etq3 ∈ S2 repondent au probleme pose, alors necessairementils sont a une distance de m inferieure a δ. En outre, il est certain qu’il y a au plus un pointde S1 a une distance de m inferieure a δ car δ ≤ δ1. La meme remarque peut etre faite pourS2. Ainsi la derniere etape de notre algorithme est-elle bien en O(n).

11.2.3 Retour au probleme plan

Considerons maintenant le cas du plan (voir figure 11.3).

δ δ

S1

P1 P2

S2

δ1

δ2

Figure 11.3: Diviser pour regner dans un plan

On partitionne l’ensemble S a l’aide d’une droite ∆ qui le coupe a peu pres en son milieu :nous preciseronsbientot. . . S1 est l’ensemble des points a gauche de ∆, et S2 l’ensemble des points a droite. On applique

recursivement notre procedure a chacune de ces parties, obtenant ainsi comme ci-dessus p1 etp2 qui realisent dans S1 la plus petite distance δ1, et q1, q2 de distance δ2 dans S2. On notetoujours δ = min{δ1, δ2}. On pourra chercher des paires qui sont plus proches et dont leselements sont un p3 ∈ S1 et un q3 ∈ S2 en imposant que p3 ∈ P1 et q3 ∈ P2 ou P1 (resp. P2)designe l’ensemble des points de S1 (resp. S2) qui sont distants d’au plus δ de ∆.

Le probleme est qu’on n’est plus du tout assure de l’unicite de ces points p3 et q3 : il sepourrait meme que S1 ⊂ P1 et S2 ⊂ P2, et on serait alors ramene a un algorithme en O(n2).En realite, tout n’est pas si grave. En effet, si p est fixe dans P1, il y a au plus 5 points dansvoir la figure 11.4,

page suivante P2 distants de p de moins de δ, puisque la distance de deux points quelconques de S2 estsuperieure a δ2, donc a δ.

Cette fois, ca semble bon : nous avons en effet n/2 points p au maximum, et pour chacund’entre eux au plus 5 points q, et donc cette etape de l’algorithme tourne en O(n), etla fusion apres les

deux appels recursifs l’algorithme entier en O(n logn).Sauf que. . .

Page 86: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

86 PROBLEMES DE PROXIMITE DANS LE PLAN

p q

2

3

q3

q3

q3

q3

5

1

2

3

4

δ

δ

P1

P

Figure 11.4: La position la plus defavorable, avec 5 points a visiter

Sauf que si nous savons que pour chaque point p il n’y a que 5 points au plus dans P2 aconsiderer, nous ne savons pas pour autant les trouver !

L’idee va consister a trier les points de P2 par ordre croissant des abscisses de leurs projec-tions sur ∆. Pour simplifier nous separerons les points de S par une droite ∆ parallele a l’axe pourquoi pas?des y. Si ∆ a pour equation x = m, P2 est l’ensemble des points tels que m ≤ x ≤ x + δ.Nous les trions grace a l’ordre sur leurs ordonnees. Si p a pour ordonnees y, nous trouveronsen un temps constant les points q de P2 tels quem ≤ xq ≤ m+ δ et y− δ ≤ yq ≤ y+ δ.

Mais au fait : trier les points de P2, qui sont au nombre de n/2 eventuellement, a un couten O(n logn) . . . nous sommes a nouveau pieges, notre algorithme tourne en O(n log2 n) !

Ce qui nous sauve encore ici est qu’on peut faire le tri une bonne fois pour toutes avanttout appel recursif et la, ouf, ca marche.

Ecrivons donc informellement l’algorithme obtenu :

1. trier l’ensemble des points de S par abscisses et par ordonnees ;

2. partager S en S1 et S2 grace au tri en abscisses croissantes effectue, et retenir l’abscissemedianem de separation ;

3. chercher par un appel recursif — sans l’etape 1 qui n’est effectuee qu’une fois pourtoutes — les paires (p1, p2) et (q1, q2) les plus rapprochees ;

4. poser δ = min{d(p1, p2), d(q1, q2)} ;

5. pour tous les points p tels que m − δ ≤ xp, et par ordre d’ordonnees croissantes,examiner les points q tels que m ≤ xq ≤ m + δ et |yq − yp| ≤ δ pour retenird’eventuels couples (p, q) qui optimiseraient la distance.

Cet algorithme tourne bien en O(n logn) d’apres l’etude que nous en avons faite.

Page 87: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.2. LA PAIRE LA PLUS RAPPROCHEE 87

11.2.4 Implementation en Caml

Quelques utilitaires simples

Programme 11.1 Paire la plus proche : quelques utilitaires

1 load_object "Cycles" ;;

2 #open "Cycles" ;;

34 type point == int * int ;;

5 type point_ou_bord = Point of point | Bord ;;

67 let rec decoupe_en_deux = function

8 [] -> [],[]

9 | [a] -> [a],[]

10 | a :: b :: q -> let l1,l2 = decoupe_en_deux q

11 in (a :: l1),(b :: l2)

12 and fusionne inf l1 l2 = match l1,l2 with

13 [],_ -> l2

14 | _,[] -> l1

15 | a :: q1 , b :: q2 -> if (inf a b) then a :: (fusionne inf q1 l2)

16 else b :: (fusionne inf l1 q2)

17 and tri_fusion inf = function

18 [] -> []

19 | [a] -> [a]

20 | l -> let l1,l2 = decoupe_en_deux l

21 in fusionne inf (tri_fusion inf l1) (tri_fusion inf l2) ;;

2223 let carre n = n * n ;;

2425 let distance (x,y) (x’,y’) = carre(x-x’) + carre(y-y’) ;;

2627 let numerote l =

28 let rec numerote_rec n = function

29 [] -> []

30 | a :: q -> (n,a) :: (numerote_rec (n+1) q)

31 in

32 numerote_rec 0 l ;;

3334 let rec filtre predicat = function

35 [] -> []

36 | a :: q -> if (predicat a) then a :: (filtre predicat q)

37 else (filtre predicat q) ;;

3839 let est_vide = function [] -> true | _ -> false ;;

On definit ici le type point, qui represente tout simplement un couple d’entiers, et le typeCaml prefere lesentiers, nous aussi point ou bord qui sera utile tout a l’heure quand nous utiliserons des cycles d’objets de ce

type. En effet, nous voudrons pouvoir avancer comme reculer dans la liste des points, maissans depasser la tete ou la queue, bref, en balisant le bord de la liste.

On retrouve le tri-fusion qu’on a deja vu, en lignes 7–21.le tri-fusion esttellement plus simplea ecrire !

Viennent ensuite quelques fonctions tres simples, comme carre (ligne 23), distance(ligne 25), ou est vide (ligne 39). On a ecrit egalement la classique fonctionnelle filtre

qui ne conserve de sa liste argument que ceux de ses elements qui satisfont au predicat fourni(lignes 34–37).

Enfin, on trouvera en lignes 27–32 la fonction numerote qui transforme une liste [a;b; c;d]

par exemple en [(0, a); (1, b); (2, c); (3, d)], et qui a le type ’a list -> (int * ’a) list.

Page 88: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

88 PROBLEMES DE PROXIMITE DANS LE PLAN

Une programmation astucieuse

Programme 11.2 Paire la plus proche : coupure d’une liste

40 let coupe_au_milieu l =

41 let rec avance l1 a1 = function

42 [] -> a1,l1

43 | l2 -> let a,l =

44 if est_vide (tl l2)

45 then avance (tl l1) a1 []

46 else avance (tl l1) a1 (tl (tl l2))

47 in ((hd l1) :: a),l

48 in avance l [] l ;;

On donne ici une programmation astucieuse qui permet de couper en deux une liste : apres prouve cetalgorithme !let l1,l2 = coupure_au_milieu l;;, on peut affirmer que l est identique a l1 @ l2,

et que l1 a la meme taille (a une unite pres) que l2.On pourrait bien sur ecrire plus simplement

let coupure_au_milieu l =

let rec coupure_recursive i l =

if i <= 0 then [],l

else let l1,l2 = coupure_recursive (i-1) (tl l)

in ((hd l) :: l1) , l2

in coupure_recursive ((list_length l) / 2) l

mais cela demande deux parcours complets de la liste l, un pour la procedure recursive,et un pour evaluer list_length l.

On verifiera que la fonction proposee ne fait qu’un passage dans la liste argument.

La fusion

cherche mieux realise l’essentiel de la fusion : il attend en argument les deux ensemblesP1 et P2 evoques dans la description de notre algorithme, sous la forme de cycles de points(pour pouvoir aller en avant comme en arriere) balises grace au constructeur Bord, ainsi quela paire la plus proche deja trouvee, constituee des deux points a et b a distance δ.

En lignes 66–69, on gere les cas d’arret.En lignes 70–71, on s’occupe du cas ou le point courant de P2 serait trop bas : il convient

alors de s’appeler recursivement avec le meme point de P1, mais le point suivant de P2.Sinon, il faut tester les 5 points a venir dans P2 (voir la discussion de l’algorithme) : c’est

le but des appels imbriques a la procedure essai, en ligne 74, appels qu’on englobe dans untry (lignes 73–78) pour s’arreter des qu’on envisage un point de P2 trop haut.

La procedure essai est definie en lignes 54–64. Elle evacue rapidement en lignes 57–60le cas ou le point courant de P2 est trop haut en declenchant une erreur (la definition del’exception est en lignes 49–51).

Sinon, elle compare les distances, en lignes 61–64.

Page 89: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.2. LA PAIRE LA PLUS RAPPROCHEE 89

Programme 11.3 Paire la plus proche : le cœur du probleme

49 exception Trop_haut of

50 (point*point*int*

51 point_ou_bord cycle_bien_chaıne*point_ou_bord cycle_bien_chaıne) ;;

5253 let rec cherche_mieux a b delta p1 p2 =

54 let essai (a,b,delta,p1,p2) =

55 match (valeur_cycle p1),(valeur_cycle p2) with

56 Bord,_ -> raise (Trop_haut (a,b,delta,p1,p2))

57 | _,Bord -> raise (Trop_haut (a,b,delta,p1,p2))

58 | Point(x1,y1),Point(x2,y2)

59 -> if (y2 > y1) & (carre(y2-y1) >= delta) then

60 raise (Trop_haut (a,b,delta,p1,p2))

61 else let d = distance (x1,y1) (x2,y2)

62 in

63 if d < delta then (x1,y1),(x2,y2),d,p1,(avance_cycle p2)

64 else a,b,delta,p1,(avance_cycle p2)

65 in

66 match (valeur_cycle p1),(valeur_cycle p2) with

67 Bord,_ -> [a;b]

68 | _,Bord -> [a;b]

69 | Point(x1,y1),Point(x2,y2)

70 -> if (y2 < y1) & (carre(y1-y2) >= delta) then

71 cherche_mieux a b delta p1 (avance_cycle p2)

72 else

73 try let a,b,delta,_,_ =

74 essai(essai(essai(essai(essai(a,b,delta,p1,p2)))))

75 in

76 cherche_mieux a b delta (avance_cycle p1) p2

77 with Trop_haut(a,b,delta,_,_)

78 -> cherche_mieux a b delta (avance_cycle p1) p2 ;;

Page 90: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

90 PROBLEMES DE PROXIMITE DANS LE PLAN

La fonction appelante

Programme 11.4 Paire la plus proche : la fonction principale

79 let paire_la_plus_proche l =

80 let rec paire_la_plus_proche_rec lx ly = match lx with

81 [] -> []

82 | [_,a] -> [a]

83 | [_,a;_,b] -> [a;b]

84 | [_,a;_,b;_,c]

85 -> if (distance a b) < (distance a c)

86 then if (distance a b) < (distance b c)

87 then [a;b]

88 else [b;c]

89 else if (distance a c) < (distance b c)

90 then [a;c]

91 else [b;c]

92 | _ ->

93 let l1x,l2x = coupe_au_milieu lx

94 in

95 let indexMilieu = match (hd l2x) with i,(_,_) -> i

96 in

97 let l1y = filtre (function (i,_) -> i < indexMilieu) ly

98 and l2y = filtre (function (i,_) -> i >= indexMilieu) ly

99 in

100 let [a1;b1] = paire_la_plus_proche_rec l1x l1y

101 and [a2;b2] = paire_la_plus_proche_rec l2x l2y

102 in

103 let delta1,delta2 = (distance a1 b1),(distance a2 b2)

104 in

105 let m = match (hd l2x) with _,(x,_) -> x

106 and aMin,bMin,delta = if delta1<delta2 then a1,b1,delta1

107 else a2,b2,delta2

108 in

109 let p1 = filtre (function (_,(x,_)) -> carre(x-m) <= delta) l1y

110 and p2 = filtre (function (_,(x,_)) -> carre(x-m) <= delta) l2y

111 in

112 let p1 = avance_cycle (liste_en_cycle

113 (Bord :: (map (function (_,a) -> Point(a)) p1)))

114 and p2 = avance_cycle (liste_en_cycle

115 (Bord :: (map (function (_,a) -> Point(a)) p2)))

116 in

117 cherche_mieux aMin bMin delta p1 p2

118 in

119 let lx = numerote

120 (tri_fusion (fun (ax,_) (bx,_) -> ax < bx) l)

121 in

122 let ly = tri_fusion (fun (_,(_,ay)) (_,(_,by)) -> ay < by) lx

123 in

124 paire_la_plus_proche_rec lx ly ;;

Pour s’assurer qu’on n’opere qu’une fois le tri, on l’effectue avant tout appel a la fonctionrecursive, en lignes 119–122, ou on commence par construire la liste des points triee en x, a lanumeroter (on a construit ainsi lx), puis a trier cette liste cette fois en y, obtenant ly.

La fonction recursive fait evidemment tout le travail. Elle commence par evacuer le casd’une liste de moins de 4 points (lignes 81–91). Ensuite on opere la decoupe en deux listes, enlignes 93–98 ; on notera tout particulierement l’interet d’avoir numerote les points par ordredes x croissants, sans quoi la decoupe de ly eut ete impossible. Les appels recursifs sur lesdeux moities ont lieu en lignes 100 et 101. On peut alors determiner la paire la plus proche,c’est-a-dire les a, b et δ qu’attend cherche mieux : c’est l’objet des lignes 103–107. Gracea filtre, on determine les deux ensembles P1 et P2, en lignes 109–115. Il n’y a plus qu’aappeler cherche mieux.

Page 91: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.3. DIAGRAMMES DE VORONOI 91

11.3 Diagrammes de Voronoı

11.3.1 Definition

On considere un ensemble fini S de n points p0, . . . , pn−1. Pour chaque indice i tel que0 ≤ i < n, on cherche a determiner l’ensemble Vi des points m du plan plus proches de mique des autresmj, defini par

Vi = {m / ∀j 6= i, d(m,pi) ≤ d(m,pj)}.

Si on considere le cas de deux points pi et pj, la reponse est elementaire : il s’agit des deuxdemi-plans limites par la mediatrice du segment [pipj]. Dans toute la suite nous noteronsnos demi-plans sont

tous supposesfermes, ilscontiennent leurfrontiere

H(pi, pj) celui de ces deux demi-plans qui contient le point pi. Ainsi H(pi, pj) ∪H(pj, pi)

est le plan entier. Alors, par definition meme de Vi, on dispose de

Vi =⋂j6=iH(pi, pj).

Ceci prouve que Vi est — en tant qu’intersection de demi-plans, et donc aussi de con-vexes — une partie convexe du plan qui contient pi et aucun autre point de S et dont lafrontiere est polygonale. De deux choses l’une : ou bien Vi est compact, c’est un polygone,ou bien Vi n’est pas borne, et sa frontiere est une suite de segments encadree par deux demi-droites. Le plus parlant est de considerer la figure 11.5 qui montre la partition du plan enles Vi dans un cas particulier. Un tel diagramme est appele diagramme de Voronoı, qui les aintroduits en 1908 a l’occasion d’un traite sur les formes quadratiques.

11.3.2 Quelques proprietes

Dans toute la suite, pour eviter des cas particuliers qui n’apportent pas grand chose a lacomprehension des notions introduites, nous ferons la supposition suivante :

Il n’y a pas dans S quatre points cocycliques.

Notons que comme une arete du diagramme de Voronoı est un segment de mediatrice, elleest donc arete commune d’exactement deux polygones. En outre nous avons le

Theoreme 5 Chaque sommet du diagramme de Voronoı est intersection d’exactement trois aretes.

✧ Soit a1, a2, . . . , ak les aretes qui se rencontrent en un sommet v du diagramme deVoronoı, numerotees dans l’ordre de leurs angles polaires. Renumerotons les points de Sde telle sorte que ai soit un cote commun aux polygones Vi−1 et Vi, avec la conventionhabituelle : a1 est un cote commun aux polygones V1 et Vk. Mais ces aretes sont desmediatrices, et c’est donc dire que v est equidistant des points p1, p2, . . . , pk. Gracea notre hypothese il est alors clair que k ≤ 3. Supposons pour terminer que k = 2.Alors a1 est cote commun a V1 et V2, et a2 aussi, bref ce sont deux segments contigusde la meme mediatrice, et il n’y aurait pas la de sommet v du diagramme de Voronoı.Finalement on a bien k = 3.✦

On peut egalement dire que chaque sommet du diagramme de Voronoı est le centre d’unconsiderer a nouveaula figure cercle passant par trois points de S. Dans la suite, si v est un sommet du diagramme de Voronoı,

nous noterons C(v) le cercle correspondant. On dispose alors de l’interessante proprietesuivante.

Theoreme 6 Pour chaque sommet v du diagramme de Voronoı, le disque ouvert de frontiereC(v)

ne contient aucun point de S.

Page 92: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

92 PROBLEMES DE PROXIMITE DANS LE PLAN

Figure 11.5: Un exemple de diagramme de Voronoı

Page 93: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.3. DIAGRAMMES DE VORONOI 93

x

u

p2

p1

pi

p3

12C12

A31

C

C

23C

Figure 11.6: Support de la demonstration du theoreme 8

✧ Raisonnons par l’absurde. Supposons queC(v) passe par les trois points p1, p2, p3,et que le disque ouvert contienne un quatrieme point p4. Mais alors v est plus prochede p4 que de chacun des p1, p2, p3, et donc v ∈ V4 mais v /∈ V1, v /∈ V2 et v /∈ V3. Cequi fournit notre contradiction puisqu’on a suppose que v est le sommet commun auxtrois polygones V1, V2, V3.✦

Voici encore un petit

Theoreme 7 Soit pi un point de S. Si pj est un plus proche voisin de pi alors le polygone Vi dudiagramme de Voronoı voit une de ses aretes portee par la mediatrice de [pipj].

✧ Soit m le milieu du segment [pipj]. Supposons un instant que m ne soit pas surla frontiere de Vi. Comme Vi est inclus dans H(pi, pj), m est alors necessairement al’exterieur de Vi. Ainsi le segment [piv] rencontre une arete de Vi en un certain pointu, et d(pi, u) < d(pi,m). u est sur une arete de Vi donc sur la mediatrice d’un certainsegment [pipk]. Mais alors d(pi, u) = 1

2d(pi, pk) < d(pi,m) = 12d(pi, pj), et donc

pk serait plus pres de pi que pj, ce qui est exclu par l’hypothese.✦

Voici ensuite un resultat qui permet de retrouver l’enveloppe convexe de S connaissant sondiagramme de Voronoı :

Theoreme 8 Le polygoneVi n’est pas borne si et seulement si pi est sur la frontiere de l’enveloppeconvexe de S.

✧ On pourra observer la figure 11.6 comme support de cette demonstration.Pour cette demonstration, nous utiliserons le lemme qui dit qu’un point p n’est pas unsommet de l’enveloppe convexe de S si et seulement si il est a l’interieur d’un trianglerst de points de S.Si doncpi n’est pas sur la frontiere de l’enveloppe convexe deS, c’est qu’il est a l’interieurd’un trianglep1p2p3 oup1, p2 etp3 sont trois points de S. On considere alors les cercles

Page 94: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

94 PROBLEMES DE PROXIMITE DANS LE PLAN

C12, C23 et C31 respectivement circonscrits aux triangles pip1p2, pip2p3 et pip3p1.Sur C12 on considere plus particulierement l’arc A12 qui est limite par p1 et p2 et quine contient pas pi. Remarquons que si m est un point de cet arc, m est plus prochede ou bien p1 ou bien p2 (ou bien des deux) que de pi. On a de meme des arcs A23 etA31 avec les proprietes analogues. Soit enfin C un cercle tel que le disque qu’il delimitecontienne l’integralite des trois cercles precedents.Nous allons montrer que tout point x exterieur a ce disque est plus proche de l’un oul’autre des points p1, p2 ou p3 que de pi. Ceci prouvera que Vi est tout entier inclusdans ce disque de frontiere C, et partant, qu’il est borne.En effet, le segment [pix] coupe l’un des cotes du triangle p1p2p3, par exemple p1p2.Mais alors il coupe egalement l’arc A12, en un point u. On a deja dit que u est plusproche de p1 ou p2 que de pi, et c’est gagne.Reciproquement, supposons que Vi soit compact, et montrons que pi n’est pas sur lafrontiere de l’enveloppe convexe de S. En effet,Vi a pour frontiere une suite de segmentsσ1, σ2, . . . , σk (avec k ≥ 3). Chaque σj est porte par la mediatrice d’un certain pip ′j,ou p ′j ∈ S. Mais alors il est clair que pi est a l’interieur du polygone de sommets p ′1,p ′2, . . . , p ′k, et donc pas sur la frontiere de l’enveloppe convexe de S.✦

11.3.3 Applications

Dual du diagramme de Voronoı : la triangulation de Delaunay

En 1934, Delaunay propose de considerer le graphe plan obtenu en reliant par un segmentde droite chaque paire de points pi, pj de S tels que les polygones Vi et Vj du diagrammede Voronoı partagent une arete commune (qui est donc alors contenue dans la mediatrice dusegment). Remarquons qu’il n’y a pas de raison pour que le segment de Delaunay coupe lamediatrice en question. On trouvera la triangulation de Delaunay associee au diagramme deVoronoı deja trace dans la figure 11.7, page suivante (le diagramme de Voronoı est en pointilles,la triangulation de Delaunay en trait epais). Il s’agit la d’un resultat lie a la dualite : le dualdu theoreme 5 sur le diagramme de Voronoı dit exactement que la manipulation precedenteconduit effectivement a une triangulation, c’est-a-dire a une partition de l’enveloppe convexede S en triangles (a trois cotes, comme il y avait trois aretes issues de chaque sommet dudiagramme de Voronoı).

Autres applications

On a deja dit comment la donnee du diagramme de Voronoı permet de reconstituer l’enveloppeconvexe (voir le theoreme 8).

D’autre part, depuis le theoreme 7, on sait que si pj est un plus proche voisin de pi, alorsla mediatrice de [pipj] porte une arete du diagramme de Voronoı. Ainsi, inversement, etantdonne pi, on cherchera ses plus proches voisins en considerant successivement les seuls pjtels que Vi et Vj aient une arete commune.

En fait, si nous savions construire en O(n logn) le diagramme de Voronoı, les remarquesprecedentes permettraient de resoudre le probleme de l’enveloppe convexe et celui des plusproches voisins pour un cout du meme ordre.

Page 95: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.3. DIAGRAMMES DE VORONOI 95

Figure 11.7: La triangulation de Delaunay

Page 96: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

96 PROBLEMES DE PROXIMITE DANS LE PLAN

11.4 Algorithme de Tsai

11.4.1 Quelques preliminaires

Nous presentons ici un algorithme propose par V. Tsai pour construire la triangulation de cet algorithme a etepublie en juin 1993Delaunay d’un ensemble den points du plan. Tsai annonce que son algorithme (pour ses deux

dernieres etapes, du moins) est a peu pres lineaire en n. . .Nous nous abstiendrons de toute justification complete de l’algorithme, ce qui depasserait

largement le cadre de ce poly. A ce jour, l’evaluation rigoureuse de son efficacite reste a faire.Voici tout d’abord deux resultats autour de la triangulation de Delaunay qui nous serviront

dans la suite.

Theoreme 9 (Critere de Delaunay) Le cercle circonscrit a un triangle pqr de la triangulationde Delaunay d’un ensemble de n points du plan ne contient aucun autre point de l’ensemble.

C’est en fait une reformulation du theoreme 6.Voici encore, sans demonstration, un autre critere, du a Lawson, en 1972 :

Theoreme 10 (Critere du max-min) Soit pqrs les sommets d’un quadrilatere convexe consti-tue de deux triangles pqs et qrs de la triangulation de Delaunay d’un ensemble de n points duplan qui partagent une arete commune qs. Soit α le plus petit des six angles aux sommets desdeux triangles. Considerons l’autre diagonale du quadrilatere, ou encore les triangles pqr et rsp,et soit β le plus petit des six angles aux sommets de ces deux nouveaux triangles. Alors α ≥ β.

On peut dire que la triangulation de Delaunay maximise le min des six angles en question ;voir la figure 11.8 qui illustre les deux criteres a la fois.

p

q

r

s

p

q

r

s

β

α

Delaunaypas Delaunay

α > βon a bien

Figure 11.8: Illustration du critere du max-min

Page 97: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.4. ALGORITHME DE TSAI 97

11.4.2 Premiere etape

La premiere chose a faire est de determiner l’enveloppe convexe desnpoints du plan consideres.Nous avons deja traite ce probleme dans le chapitre precedent. Nous n’y revenons donc pasdavantage.

11.4.3 Deuxieme etape

Il s’agit maintenant de construire la triangulation de Delaunay des seulsp points de l’enveloppeconvexe qu’on vient de determiner. Notons-les a0, a1, . . . , ap−1, en les numerotant dans lesens trigonometrique : les aretes du polygone convexe considere sont lesa0a1, . . . ,ap−2ap−1,ap−1a0.

Voici alors l’algorithme propose par Tsai pour trouver les triangles de la triangulation deDelaunay de l’enveloppe convexe.

etape 0. on definit un ensemble d’aretesA qu’on initialise avec la liste des aretes du polygone,c’est-a-dire les a0a1, . . . , ap−2ap−1, ap−1a0, ainsi qu’un ensemble T de trianglesinitialement vide ;

etape 1. si A est vide, on a fini : T contient la liste des triangles de la triangulation deDelaunay cherchee, STOP ; sinon, continuer a l’etape suivante ;

etape 2. on choisit une arete ab de A, qu’on retire de cet ensemble ;

etape 3. on passe en revue les points c du nuage jusqu’a trouver un triangle abc qui ne soitpas deja dans T et dont le disque limite par son cercle circonscrit ne contienne aucunautre point du nuage (c’est le critere de Delaunay) ;

etape 4. on ajoute a T le triangle abc ainsi trouve ;

etape 5. si l’arete bc etait dans A, on la supprime, sinon on l’ajoute ; on fait de meme poursurprenant, non?l’arete ac : on la supprime si elle se trouve deja dans A, sinon on l’ajoute ;

etape 6. on retourne a l’etape 1.

On trouvera un exemple en figure 11.9, page suivante.

Page 98: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

98 PROBLEMES DE PROXIMITE DANS LE PLAN

a

b

c d

e

f

g

a

b

c d

e

f

g

a

b

c d

e

f

g

a

b

c d

e

f

g

a

b

c d

e

f

g

a

b

c d

e

f

g

T = ØA = ab, bc, cd, de, ef, fg, ga

T = abg

A = bc, cd, de, ef, fg, ga, bg

T = abg, bcd, deg

A = ef, fg, bg, bd, eg, dg

T = abg, bcd

A = de, ef, fg, bg, bd

T = abg, bcd, deg, efg, bdg

A = Ø T = abg, bcd, deg, efg

A = bg, bd, dg

❶ ❷

❸ ❹

❺ ❻

Figure 11.9: Deuxieme etape de l’algorithme de Tsai

Page 99: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.4. ALGORITHME DE TSAI 99

11.4.4 Troisieme etape

Nous allons ajouter un a un chacun des n− p points que nous n’avons pas encore consideres.Pour ajouter un point m (et mettre a jour la triangulation de Delaunay, bien sur), nous

commencons par determiner l’ensemble des triangles abc de la triangulation courante quion procede ainsi : ontrouve un premiertriangle, puis onessaie ceux qui ontavec lui au moins uncote commun, et onitere. . .

chapeautentm : on dira qu’un triangle chapeaute un point si ce point est a l’interieur du cerclecirconscrit au triangle. La reunion de ces triangles constitue ce qu’on peut appeler la zoned’influence du pointm (voir la figure 11.10, ou la zone d’influence est grisee).

On supprime alors de la triangulation tous les cotes communs a deux triangles a l’interieurde la zone d’influence. On termine en ajoutant tous les segments qui relient m a un dessommets a la frontiere de sa zone d’influence.

m m

après insertion de mavant insertion de m

Figure 11.10: La derniere etape de l’algorithme de Tsai

Apres avoir ajoute ainsi tous les points manquants, on obtient la triangulation de Delaunaycherchee.

11.4.5 Remarques

Tsai propose en realite quelques raffinements que nous ne presentons pas ici, et qui ont pourc’est deja bien assezcomplique commeca !

but d’accelerer les differentes etapes de l’algorithme qu’il expose. En particulier, dans l’etape 2,il montre comment trouver rapidement le prochain triangle a ajouter a la liste. De meme, pour laphase finale, il explique comment accelerer la determination de la zone d’influence d’un point.Nous n’avons meme pas utilise la remarque marginale faite a l’occasion dans l’implementationque nous proposons ci-apres.

Page 100: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

100 PROBLEMES DE PROXIMITE DANS LE PLAN

11.4.6 Les diagrammes de Voronoı revisites

Il reste a expliquer comment reconstituer le diagramme de Voronoı a partir de la triangulationde Delaunay.

C’est chose facile (voir la figure 11.7 a nouveau) : on considere tour a tour chaque arete[pq] de la triangulation.

Alors, de deux choses l’une :

si l’arete est commune a deux triangles T1 et T2, on determine les centres c1 et c2 de leurscercles circonscrits, et on ajoute au diagramme de Voronoı l’arete [c1c2] et les sommetsc1 et c2 ;

si l’arete est sur la frontiere de l’enveloppe convexe, elle n’appartient qu’a un triangle [pqr] ;on determine le centre c de son cercle circonscrit, et on ajoute au diagramme de Voronoıle sommet c et la demi-droite portee par la mediatrice de [pq], d’extremite c, qui sedirige vers l’exterieur de l’enveloppe convexe.

11.5 Implementation en Caml

11.5.1 Quelques utilitaires

Programme 11.5 Quelques utilitaires pour l’algorithme de Tsai

1 let epsilon = 1.0e-8 ;;

23 let carre x = x *. x ;;

45 let d2 (ax,ay) (bx,by) = carre(ax -. bx) +. carre(ay -. by) ;;

67 let cercle_circonscrit (ax,ay) (bx,by) (cx,cy) =

8 let a = d2 (ax,ay) (0.,0.)

9 and b = d2 (bx,by) (0.,0.)

10 and c = d2 (cx,cy) (0.,0.)

11 and q = 2. *. (ax *. (by-.cy) +. bx *. (cy-.ay) +. cx *. (ay-.by))

12 in

13 ( (* abscisse du centre *)

14 (a *. (by-.cy) +. b *. (cy-.ay) +. c *. (ay-.by))/.q

15 , (* ordonnee du centre *)

16 -.(a *. (bx-.cx) +. b *. (cx-.ax) +. c *. (ax-.bx))/.q

17 )

18 , (* rayon du cercle *)

19 sqrt((d2 (ax,ay) (bx,by))*.(d2 (bx,by) (cx,cy))*.(d2 (cx,cy) (ax,ay)))

20 /. abs_float(q) ;;

2122 let determinant (ax,ay) (bx,by) = ax*.by -. ay*.bx ;;

2324 let vecteur (ax,ay) (bx,by) = (bx-.ax,by-.ay) ;;

2526 let sens_direct a b c = 0. <. (determinant (vecteur a b) (vecteur a c)) ;;

2728 let alignes a b c =

29 abs_float(determinant (vecteur a b) (vecteur a c)) <. epsilon ;;

3031 let est_triangle a b c = not (alignes a b c) ;;

Nous commencons par ecrire quelques utilitaires d’usage general, qui font l’objet du fichiertsai.util.ml dont on fournit le listing : c’est le programme 11.5.

On trouve les fonctions carre et d2 qui n’appellent pas de commentaires particuliers, pasplus que determinant, vecteur, sens direct ou est triangle. Signalons simplement

Page 101: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.5. IMPLEMENTATION EN CAML 101

qu’a cause des problemes de precision habituels aux flottants, on a introduit une constanteepsilon pour eviter dans alignes la comparaison avec 0. qui est toujours hasardeuse.

La fonction cercle circonscrit attend trois points representes par des couples de co-ordonnees flottantes, et renvoie un couple forme du centre et du rayon du cercle circonscrit auun grand merci a

Maple qui a donneles formules utilisees

triangle.

Programme 11.6 Algorithme de Tsai : 1ere etape

1 load_object "Cycles" ;;

2 #open "Cycles" ;;

34 load_object "Tri_fusion" ;;

5 #open "Tri_fusion" ;;

67 load_object "Convexe.float" ;;

8 #open "Convexe.float" ;;

910 include "tsai.util" ;;

1112 let liste_vide = function [] -> true | _ -> false ;;

1314 let rec premier_d’accord predicat = function

15 [] -> raise Not_found

16 | x :: q -> if predicat(x) then x else premier_d’accord predicat q ;;

1718 let rec discrimine predicat = function

19 [] -> [],[]

20 | x :: q -> let oui,non = discrimine predicat q

21 in

22 if predicat(x) then (x::oui),non else oui,(x::non) ;;

2324 let phase1 les_points =

25 let convexe = enveloppeConvexe les_points

26 in

27 let sommets_du_polygone = cycle_en_liste convexe

28 and aretes_du_polygone =

29 let rec cree_liste_des_aretes = function

30 a :: b :: q -> (a,b) :: cree_liste_des_aretes (b :: q)

31 | _ -> []

32 in cree_liste_des_aretes

33 ((valeur_cycle convexe) :: (cycle_en_liste (avance_cycle convexe)))

34 in

35 let sommets_internes = subtract les_points sommets_du_polygone

36 in

37 sommets_du_polygone , aretes_du_polygone , sommets_internes ;;

On peut alors ecrire la premiere partie de notre algorithme, ce qui constitue le pro-gramme 11.6.

On trouvera les incantations habituelles necessaires pour pouvoir acceder a la structure delistes circulaires, au tri-fusion, et a la recherche de l’enveloppe convexe. On inclut egalementil a fallu reecrire le

programme derecherche del’enveloppe convexepour l’adapter au casde points acoordonneesflottantes

les utilitaires que nous venons de decrire.On ajoute encore quelques fonctions d’interet general : est vide dit si la liste qu’on lui

passe en argument est vide ou non ; premier d’accord prend en argument un predicat et une

un predicat est dutype ’a -> bool

liste, et renvoie le premier element de la liste qui satisfait le predicat ou declenche l’exceptionNot found s’il ne s’en trouve pas ; de facon analogue discrimine, avec les memes arguments,renvoie le couple des listes oui et non formees par les elements qui satisfont ou non le predicat.

On est en position d’ecrire phase1 qui prend la liste des points du nuage et renvoie untriplet forme de la liste des sommets du polygone frontiere de l’enveloppe convexe, de la listedes aretes de ce polygone (sous la forme de couples de points), et de la liste des points du nuagequi sont a l’interieur du polygone.

Page 102: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

102 PROBLEMES DE PROXIMITE DANS LE PLAN

11.5.2 Deuxieme etape de l’algorithme

Le programme 11.7 permet d’implementer la deuxieme phase de l’algorithme de Tsai. Ilcommence par definir quelques fonctions auxiliaires. les utilitaires, c’est

comme les lemmes,plus il y en a, etmeilleur c’est . . .hummm

test de Delaunay prend en arguments trois points p q r et la liste de tous les sommets,et dit si disque (de frontiere le) cercle circonscrit au triangle pqr ne contient bien aucun autresommet que ces trois la. Notons que mem et forall sont des fonctions de la bibliothequestandard Caml.

la bibliothequestandard nous a dejadonne subtract

nouveau triangle p q r liste verifie que le triangle pqr n’est pas deja dans la listefournie, et que c’est bel et bien un triangle (id est pas trois points alignes).

On trouve ensuite meme arete qui est l’egalite entre aretes (il n’y a pas d’ordre sur lesextremites qui definissent une arete) ; et membre aretequi, a l’aide de la precedente, decouvresi l’arete passee en premier argument fait partie de la liste d’aretes passee en deuxieme argument.

difference et difference symetrique effectuent les fonctions classiques sur les en- les mathematiciensnotent \ et ∆sembles sur des listes d’aretes. C’est le cœur de la deuxieme phase de l’algorithme de Tsai : on

ajoute les nouvelles aretes si elles sont absentes, on les supprime si elles sont presentes, ce quin’est qu’une formulation de la difference symetrique.

Il n’y a plus qu’a ecrire phase2 qui a partir de la liste des sommets du polygone convexe etde la liste de ses aretes construit et renvoie la liste des triangles de sa triangulation de Delaunay.Elle utilise une procedure recursive itere qui epuise petit a petit son premier argument (laliste courante des aretes a traiter) en ajoutant au fur et a mesure a son deuxieme argument lestriangles construits.

Page 103: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.5. IMPLEMENTATION EN CAML 103

Programme 11.7 Algorithme de Tsai : 2e etape

38 let test_de_Delaunay p q r tous_les_sommets =

39 let centre,rayon = cercle_circonscrit p q r

40 in

41 let est_ok m = (mem m [p;q;r]) or (d2 centre m >. carre(rayon))

42 in

43 for_all est_ok tous_les_sommets ;;

4445 let nouveau_triangle p q r anciens_triangles =

46 let autre_triangle (a,b,c) =

47 not (liste_vide (subtract [p;q;r] [a;b;c]))

48 in

49 (est_triangle p q r) &

50 (for_all autre_triangle anciens_triangles) ;;

5152 let meme_arete (a,b) (a’,b’) =

53 ( (a = a’) & (b = b’)) or ((a = b’) & (b = a’)) ;;

5455 let rec membre_arete a = function

56 [] -> false

57 | t :: q -> (meme_arete t a) or (membre_arete a q) ;;

5859 let rec difference l1 l2 = match l1 with

60 [] -> []

61 | a :: q -> if membre_arete a l2 then difference q l2

62 else a :: (difference q l2) ;;

6364 let difference_symetrique l1 l2 =

65 let l = difference l1 l2

66 and m = difference l2 l1

67 in l @ m ;;

6869 let phase2 les_sommets les_aretes =

70 let rec itere aretes triangles = match aretes with

71 [] -> triangles

72 | (a,b) :: q

73 -> let candidat x = (nouveau_triangle a b x triangles)

74 & (test_de_Delaunay a b x les_sommets)

75 in

76 let c = premier_d’accord candidat les_sommets

77 in

78 itere (difference_symetrique q [(b,c);(c,a)])

79 ((a,b,c) :: triangles)

80 in

81 itere les_aretes [] ;;

Page 104: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

104 PROBLEMES DE PROXIMITE DANS LE PLAN

11.5.3 Troisieme etape

On commence, dans le programme 11.8 qui implemente la troisieme et derniere phase del’algorithme de Tsai, encore une fois par quelques fonctions auxiliaires.

chapeaute m (p,q,r)dit si le trianglepqr chapeaute le pointm, au sens ou nous l’avonsdefini plus haut.

supprime doublons renvoie la liste d’aretes passee en argument privee de ceux de seselements qui y figureraient en plus d’un exemplaire. Pour ce faire elle commence par formerla liste de ces elements particuliers grace a la fonction recursive doublons.

liste aretes de triangles prend une liste de triangles et renvoie la liste complete detoutes leurs aretes.

phase3 prend en arguments les listes des points deja traites (au depart : les sommets del’enveloppe convexe), des autres points du nuage, des triangles de la triangulation en cours(au depart : la triangulation du polygone obtenue grace a phase2). Elle renvoie la liste destriangles de la triangulation de Delaunay du nuage.

Programme 11.8 Algorithme de Tsai : 3e etape

82 let chapeaute m (p,q,r) =

83 let centre,rayon = cercle_circonscrit p q r

84 in

85 (d2 m centre) <. carre(rayon) ;;

8687 let supprime_doublons l =

88 let rec doublons = function

89 [] -> []

90 | a :: q -> if membre_arete a q then a :: (doublons q)

91 else doublons q

92 in

93 difference l (doublons l) ;;

9495 let rec liste_aretes_de_triangles = function

96 [] -> []

97 | (a,b,c) :: q -> (a,b) :: (b,c) :: (c,a) ::

98 (liste_aretes_de_triangles q) ;;

99100 let rec phase3 pts_traites pts_restants triangles =

101 let triangles_apres m =

102 let chapeautants,inertes = discrimine (chapeaute m) triangles

103 in

104 let aretes = supprime_doublons

105 (liste_aretes_de_triangles chapeautants)

106 in

107 (map (function (a,b) -> (a,b,m)) aretes) @ inertes

108 in

109 match pts_restants with

110 [] -> triangles

111 | m :: q -> phase3 (m :: pts_traites) q (triangles_apres m) ;;

112113 let tsai les_points =

114 let pts_polygone,aretes_polygone,pts_internes = phase1 les_points

115 in

116 let triangles_polygone = phase2 pts_polygone aretes_polygone

117 in

118 phase3 pts_polygone pts_internes triangles_polygone ;;

Pour ce faire, elle a ete ecrite de facon recursive, l’arret se produisant quand il n’y a plusde points a traiter (ligne 110). Dans le cas general, on considere le premier pointm a traiter, onconstruit la nouvelle liste des triangles et on itere (ligne 111). Bien sur c’est triangles apres

qui se charge de construire la nouvelle triangulation. Elle est definie en lignes 101–107. Ondiscrimine tout d’abord ceux des anciens triangles qui chapeautent ou nonm (ligne 102). On

Page 105: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.5. IMPLEMENTATION EN CAML 105

supprime toutes les aretes au moins en double de la liste de toutes les aretes de ces triangles dela zone d’influence de m (lignes 104–105). Il n’y a plus qu’a ajouter aux triangles hors zoned’influence (leur liste est appelee inertes) les triangles obtenus a l’aide de m et des aretesrestantes, ce qui est fait en ligne 107.

La procedure finale tsai se contente d’enchaıner les trois phases de l’algorithme.

11.5.4 Determination du diagramme de Voronoı

On en arrive a la creation du diagramme de Voronoı a partir de la triangulation de Delaunay.relis ce que nousavons dit en 11.4.6

Programme 11.9 Construction du diagramme de Voronoı

119 type voronoi = Segment of point * point | Demi_droite of point * direction

120 and direction == float * float ;;

121122 let direction (ax,ay) (bx,by) c =

123 if sens_direct (ax,ay) (bx,by) c then (by-.ay , ax-.bx)

124 else (ay-.by , bx-.ax) ;;

125126 let rec present (a,b) = function

127 [] -> raise Not_found

128 | ((p,q),_,c) :: l -> if meme_arete (a,b) (p,q) then c

129 else present (a,b) l ;;

130131 let rec supprime (a,b) = function

132 [] -> []

133 | ((p,q),r,s) :: l -> if meme_arete (p,q) (a,b) then supprime (a,b) l

134 else ((p,q),r,s) :: (supprime (a,b) l) ;;

135136137 let voronoi_et_delaunay les_points =

138 let rec combine = function

139 [] -> []

140 | ((a,b),c,centre) :: q

141 -> try let centre’ = present (a,b) q

142 in

143 Segment(centre,centre’) :: (combine (supprime (a,b) q))

144 with

145 Not_found ->

146 Demi_droite(centre,(direction a b c)) :: (combine q)

147 and developpe = function

148 [] -> []

149 | (a,b,c) :: q -> let centre,_ = cercle_circonscrit a b c

150 in

151 ((a,b),c,centre) :: ((b,c),a,centre) ::

152 ((c,a),b,centre) :: (developpe q)

153 in

154 let delaunay = tsai les_points

155 in

156 let les_aretes = developpe delaunay

157 in

158 let diagramme = combine les_aretes

159 in

160 delaunay , diagramme ;;

Les elements du diagramme de Voronoı sont de deux types, les segments, caracterises parles deux points extremites, et les demi-droites, caracterisees par leur unique point extremite,et un vecteur donnant la direction de la demi-droite ; c’est ce que traduisent les types Camldefinis en lignes 119–120.

Pour les demi-droites, nous aurons besoin de savoir determiner la direction de la mediatrice

de [ab] qui “sort du triangle” (abc). Tout depend en fait de l’orientation du repere (a,−→ab,−→ac),

c’est ce que realise la procedure direction.

Page 106: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

106 PROBLEMES DE PROXIMITE DANS LE PLAN

Avant de commenter present et supprime, interessons-nous a la fonction objet de tous enfin, tous. . . ?nos desirs : voronoi et delaunay qui prend en argument la liste des points du nuage etrenvoie un couple forme de la liste des triangles de la triangulation de Delaunay et de la listedes objets constitutifs du diagramme de Voronoı (segments et demi-droites).

Apres avoir appele tsai en ligne 154, on applique a la liste des triangles obtenus la fonctiondeveloppe (definie en lignes 147–152) qui se charge de creer une liste d’elements de la forme((a,b),c,centre) pour chaque arete [ab] de chaque triangle. Bien sur centre represente lecentre du cercle circonscrit associe au triangle, qu’on ne calcule ainsi qu’une fois pour chaque.Mais pourquoi conserver aussi le troisieme point c du triangle? tout simplement pour pouvoir bonne question,

merci de me l’avoirposee !

orienter (comme il a ete dit plus haut) la demi-droite correspondante dans le cas ou [ab] estun cote du polygone frontiere de l’enveloppe convexe du nuage.

Que fait-on ensuite? on applique mot a mot ce qui a ete explique en 11.4.6, en consideranttour a tour les aretes ainsi listees.

Si une arete figure deux fois, c’est a dire si elle est presente dans la liste des aretes nonencore considerees, elle figure en association avec un nouveau centre : il suffit de construire lesegment qui relie ces deux centres, et d’appliquer recursivement combine a la liste des aretesdebarrassee de ces deux aretes maintenant traitees. C’est ce qu’on fait en lignes 140–143. Onvoit maintenant ce que faitpresent (a,b) liste : siabfigure dans la liste des aretes fournieon renvoie le centre de cercle circonscrit associe, et sinon on declenche l’exception Not found.Quant a supprime, elle enleve d’une liste d’arete tout triplet ((c,d),e,centre’) tel que(c, d) et (a, b) soient la meme arete.

Sinon, on construit la demi-droite voulue avant de realiser l’appel recursif (lignes 145–146).

11.5.5 Affichage dans une fenetre graphique

Nous donnons le petit programme 11.10 qui permet, en usant de la bibliotheque graphiquede Caml, d’afficher diagramme de Voronoı et triangulation de Delaunay d’un nuage de pointsaleatoire.

Nous commencons par redefinir les fonctions de trace de base pour qu’elles s’appliquentaux points a coordonnees flottantes. En outre, en les combinant, on ecrit petit a petit lesfonctions de trace de cercles circonscrits, ou d’un polygone, etc. Notons que nous avons choiside dessiner les triangulations en trait gras et les diagrammes en trait fin. De plus chaque pointdu nuage est represente par un petit disque de rayon 3 pixels.

On ecrit liste aleatoire qui renvoie une liste aleatoires de points en verifiant qu’ilstiennent dans la fenetre graphique de Caml et qu’elle n’a pas deux points egaux.

La procedure affiche realise alors l’affichage de figures pour un nombre fixe de nuagesaleatoires de taille egalement fixee. Il n’y a plus qu’a se faire plaisir, en regardant l’ecran. . .

On trouvera dans la figure 11.11 une copie de l’ecran de mon Macintosh lors d’une executionde ce programme.

Page 107: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

11.5. IMPLEMENTATION EN CAML 107

Programme 11.10 Affichage graphique des diagrammes de Voronoı de nuages de pointsaleatoires

1 include "Tsai.ml" ;;

2 #open "graphics" ;;

34 random__init 0 ;;

5 open_graph "" ;; (* 480 * 280 *)

67 let moveTo (ax,ay) = moveto (int_of_float ax) (int_of_float ay) ;;

8 let lineTo (ax,ay) = lineto (int_of_float ax) (int_of_float ay) ;;

9 let trace_triangle a b c = moveTo a ; lineTo b ; lineTo c ; lineTo a ;;

10 let trace_cercle (ax,ay) r =

11 draw_circle (int_of_float ax) (int_of_float ay) (int_of_float r) ;;

12 let trace_cercle_circonscrit a b c =

13 let o,r = cercle_circonscrit a b c in trace_cercle o r ;;

14 let trace_point (ax,ay) = fill_circle (int_of_float ax) (int_of_float ay) 3 ;;

15 let trace_liste_de_points l = do_list trace_point l ;;

16 let traceTriangle (a,b,c) = trace_liste_de_points [ a ; b ; c ] ;

17 trace_triangle a b c ;;

18 let infini (ax,ay) (dx,dy) = (ax +. 20.*.dx,ay +. 20.*.dy) ;;

19 let trace_objet_du_diagramme = function

20 Segment(a,b) -> moveTo a ; lineTo b

21 | Demi_droite(a,d) -> moveTo a ; lineTo (infini a d) ;;

2223 exception Gagne of point list ;;

2425 let rec liste_aleatoire = function

26 0 -> []

27 | taille -> let l = liste_aleatoire (taille - 1)

28 in

29 try while true do

30 let a = (120.+.(random__float 240.),20.+.(random__float 240.))

31 in

32 if not (mem a l) then raise (Gagne(a :: l))

33 done ;

34 l

35 with Gagne res -> res ;;

3637 let attend () = wait_next_event [ Button_down ; Key_pressed ] ; () ;;

3839 let affiche nb_points nb_images =

40 for i = 1 to nb_images do

41 clear_graph () ;

42 let l = liste_aleatoire nb_points

43 in

44 let t,v = voronoi_et_delaunay l

45 in

46 set_line_width 2 ; map traceTriangle t ;

47 set_line_width 1 ; map trace_objet_du_diagramme v ;

48 if i < nb_images then attend ()

49 done ;;

Page 108: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

108 PROBLEMES DE PROXIMITE DANS LE PLAN

Figure 11.11: Une copie d’ecran de mon Macintosh

Page 109: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Partie IV

Annexes

109

Page 110: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 111: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Annexe A

Un petit manuel de reference deCaml

111

Page 112: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

112 UN PETIT MANUEL DE REFERENCE DE CAML

A.1 Types de base

A.1.1 Unit

Le type unit ne contient qu’un element, note (). On l’utilise pour les fonctions “sans argu-ments” (en fait, elles en ont un, c’est () ). Exemple : quit : unit -> unit qui s’invoquepar quit() ;; On l’utilise aussi pour les fonctions qui ne renvoient “rien” (en fait, ellesrenvoient quelque chose : () ). Exemple : les fonctions d’impression.

A.1.2 Booleens

Le type bool contient deux valeurs : true (vrai) et false (faux). Les operateurs sur les depuis la version 0.7de Caml, nousdisposons d’unenouvelleorthographe : && et||

booleens sont : & et or qui seront detailles en A.3.2 (ce sont, bien sur, le ET et le OU),not (la negation). Tous ces operateurs sont de faible priorite : on s’astreindra a parenthesercompletement les expressions booleennes.

A.1.3 Nombres entiers

Le type int correspond aux entiers (signes). Les operateurs correspondants sont : +, -, *, /,mod. La division est une division entiere ; exemple : 7/3 s’evalue en 2 ; 7 mod 3 donne 1, lereste de la division precedente.

succ et pred sont respectivement les fonctions successeur et predecesseur.On dispose des comparaisons standards : < <= = > >= <> et de la valeur absolue abs

ainsi que de min et max qui attendent deux arguments.

A.1.4 Nombres a virgule ou “flottants”

Le type float correspond aux nombres “a virgule”, on les reconnaıt (paradoxalement) a lapresence du point : 1.2e6 correspond a 1,2 106. Attention, 1 est un int alors que 1. est unfloat.

On dispose des operateurs courants : +. -. *. /. et des comparaisons : <. <=. =. >. >=.<>.

Les fonctions transcendantes s’orthographient : sin cos tan asin acos atan sqrt log

exp. Le logarithme (log) est neperien et sqrt designe la racine carree (square root). L’elevationa la puissance s’obtient par power : float -> float -> float ; la valeur absolue parabs_float.

Enfin, les conversions entiers/flottants sont

int_of_float : float -> int et float_of_int : int -> float

qui font ce qu’elles peuvent.

A.1.5 Caracteres

Le type char correspond aux caracteres. On les ecrit ‘a‘ ‘b‘ ou encore ‘;‘ par exemple.Les caracteres speciaux courants sont : ‘\\‘ et ‘\‘‘ et ‘\r‘ et ‘\t‘ pour, respectivement, lebackslash, le backquote (ou accent grave), le retour-chariot et la tabulation.

Les seuls operateurs de comparaison disponibles sont = et <>Pour les conversions on dispose de

int_of_char : char -> int et char_of_int : int -> char

qui s’appuient sur le codage ascii des caracteres.

Page 113: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.1. TYPES DE BASE 113

A.1.6 Chaınes de caracteres

Le typestring correspond aux chaınes de caracteres. La chaıneabc"def est notee"abc\"def" ;les caracteres speciaux vus precedemment sont autorises.

Les operateurs correspondants sont ^ pour la concatenation et = et <> pour les compa-raisons.

La bibliotheque standard fournit entre autres les fonctions suivantes.

• string_length : string -> int renvoie la longueur d’une chaıne ;

• nth_char : string -> int -> char renvoie le n-ieme caractere d’une chaıne (lanumerotation commence a zero) ;

• set_nth_char : string -> int -> char -> unit modifie le n-ieme caractered’une chaıne ;

• sub_string : string -> int -> int -> string renvoie une nouvelle chaıne :par exemple, sub_string "abcdef" 2 3 donne "cde" qui commence au 2 e caractereet est de longueur 3 ;

• make_string : int -> char -> string cree une chaıne dont on precise la tailleet le caractere de remplissage.

Depuis la version 0.7, on dispose d’une nouvelle syntaxe : si s est une chaıne, si i est unc’est bien pluspratique indice dans la chaıne, s.[i] designe le caractere correspondant de la chaıne, et s.[i] <- c

permet de le modifier.Les fonctions de comparaison sont toutes du type string -> string -> bool ; elles

s’appuient sur l’ordre lexicographique et se notent respectivement : eq_string (egalite),neq_string (inegalite), ge_string (relation superieur ou egal), le_string (relation inferieurou egal), gt_string (relation superieur strict), lt_string (relation inferieur strict).

A.1.7 Couples et multiplets

Les couples correspondent au produit cartesien de deux types. Exemple : (1,"asd") est dutypeint * string. Bien sur, on dispose de meme des multiplets comme(false, 3, "asd")

du type bool * int * string.La bibliotheque standard fournit entre autres les fonctions suivantes.

• fst : ’a * ’b -> ’a renvoie le premier element d’un couple ;

• snd : ’a * ’b -> ’b renvoie le second ;

• split : (’a * ’b) list -> ’a list * ’b list prend une liste de couples etrenvoie le couple des listes formees des premiers et des seconds elements ;

• combine : ’a list * ’b list -> (’a * ’b) list effectue l’operation inverse ;

• map_combine : (’a * ’b -> ’c) -> ’a list * ’b list -> ’c list fait ceque l’on pense :

#map_combine (function (x,y) -> x*y) ([1; 2; 3], [4; 5; 6]) ;;

- : int list = [4; 10; 18]

• do_list_combine : (’a * ’b -> ’c) -> ’a list * ’b list -> unit fait dememe, mais ne renvoie rien : n’a d’interet que si la fonction passee en premier argumentrealise des effets de bord.

Page 114: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

114 UN PETIT MANUEL DE REFERENCE DE CAML

A.1.8 Listes

Une liste se note [x1; x2; x3; ...; xN] ou les objets xi sont tous d’un meme type ’a,le type de la liste est alors ’a list. La liste vide se note [] ; l’operateur de constructiondes liste est :: qu’on lit quatre points, ainsi 1 :: 2 :: 3 :: [] ou 1 :: 2 :: [3] ou1 :: [2; 3] valent [1; 2; 3], du type int list. On dit que l’operateur :: est associatifa droite. Les deux fonctions d’acces fondamentales sont hd (pour head) qui renvoie la tetede la liste, et tl (pour tail) qui renvoie la queue de la liste. Ces deux fonctions declenchentFailure "hd" ou Failure "tl" si on tente de les appliquer a la liste vide. La plupart dutemps, on leur preferera le filtrage.

L’operateur de concatenation des listes se note @ ; son execution ne se fait pas en tempsconstant mais lineaire en la taille de la premiere liste.

La bibliotheque standard fournit entre autres les fonctions suivantes.

• list_length : ’a list -> int renvoie la longueur de la liste ;

• rev : ’a list -> ’a list fabrique une copie renversee de la liste passee en argu-ment, ainsi rev [1; 2; 3] vaut rev [3; 2; 1] ;

• map : (’a -> ’b) -> ’a list -> ’b list construit la liste des resultats de l’ap-plication de la fonction donnee en premier argument a chacun des elements de la listepassee en second argument ;

• do_list : (’a -> ’b) -> ’a list -> unit fait comme map mais sans renvoyeraucun resultat : n’est interessante que pour les effets de bord ;

• for_all : (’a -> bool) -> ’a list -> booldit si le predicat (fonction a valeurbooleenne) passe en premier argument est satisfait par tous les elements de la liste passeeen second argument ;

• exists : (’a -> bool) -> ’a list -> bool dit si le predicat (fonction a valeurbooleenne) passe en premier argument est satisfait par au moins un element de la listepassee en second argument ;

• mem : ’a -> ’a list -> booldit si l’element passe en premier argument est elementde la liste passee en second argument ;

• except : ’a -> ’a list -> ’a list renvoie la liste passee en deuxieme argumentprivee du premier de ses elements egal a l’objet passe en premier argument ;

• substract : ’a list -> ’a list -> ’a list renvoie la liste des elements de lapremiere liste qui ne figurent pas dans la seconde ;

• union : ’a list -> ’a list -> ’a list renvoie la deuxieme liste augmenteedes elements de la premiere qui n’y figurent pas deja ;

• intersect : ’a list -> ’a list -> ’a list renvoie une copie de la premiereliste privee des elements qui ne figurent pas dans la seconde ;

• index : ’a -> ’a list -> int renvoie la position de la premiere occurrence dupremier argument dans la liste passee en second argument, la numerotation commencanta 0 ;

• assoc : ’a -> (’a * ’b) list -> ’b gere les listes associatives : par exempleassoc 2 [(1,‘a‘); (2,‘b‘) ; (2,‘c‘) ; (3,‘d‘)] vaut ‘b‘. Declenche l’ex-ception Not_found en cas d’echec.

Page 115: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.2. TYPES CONSTRUITS 115

A.2 Types construits

A.2.1 Types produit

Comme les multiplets, les types produit sont des produits cartesiens, mais ils permettent enoutre de nommer les projections, qu’on appelle des champs.

Par exemple

type individu = { Nom : string ; Prenom : string ; Secu : int }

definit un type produit qui comporte trois champs. L’acces aux differents champs (c’est-a-direles differentes projections) repond a la syntaxe suivante : si moi est un objet de type individu,on accede a ses projections par moi.Nom, moi.Prenom, moi.Secu. Un objet de ce type estpar exemple { Nom = "Lanturlu" ; Prenom = "Anselme" ; Secu = 123042 }. Onnotera que le filtrage ne necessite pas l’explicitation de tous les champs, alors qu’elle estindispensable lors de la definition d’un objet.

A.2.2 Types somme

Mathematiquement parlant, il s’agit ici de sommes disjointes.Par exemple type booleen = Vrai | Faux est une definition parfaitement satisfaisante

des booleens, Vrai et Faux sont des constructeurs du type. Attention : la definition suivanten’est pas licite type nombre = int | float ; il faut passer par l’usage des constructeurs, enecrivant par exemple type nombre = Entier of int | Flottant of float. Un objetde ce type se note Entier(123) ou Flottant(3.14159). Le filtrage permet de discriminerles valeurs.

A.2.3 Types parametres

Toute definition de type construit peut etre parametree par des variables de types.Par exemple type ’a numerote = int * ’a, ou, pour definir un type analogue aux

couples, type (’a,’b) paire = {Premier : ’a ; Second : ’b}. Ou encore, pour lestypes somme, par exemple

type (’a,’b) simple_ou_double = Simple of ’a | Double of ’a * ’b

qui permet de decrire quelque chose comme l’union disjointe de A et A× B.

A.2.4 Types (mutuellement) recursifs

Donnons quelques exemples :

type ’a liste = Nil | Cons of ’a * ’a liste

permettrait de definir un type parametre semblable au type predefini Caml ’a list.

type ’a arbre = Nil | Nœud of ’a nœud_interne

and ’a nœud_interne = { Cle : ’a ; Fils_droit : ’a arbre ; Fils_gauche : ’a arbre }

fournit une description d’un arbre binaire.

A.2.5 Abreviations de type

Caml nous permet de definir des synonymes pour des types simples.Par exemple type point3d == float * float * float permet d’invoquer par la

suite cette abreviation pour definir un nouveau type, ou pour une coercion de type. Plussimplement, cela offre une interpretation lisible d’un type complexe.

Page 116: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

116 UN PETIT MANUEL DE REFERENCE DE CAML

A.3 Structures de controle

A.3.1 Sequencement

Une sequence d’expressions est constituee d’une suite d’expressions separees par des points-virgules. Sa valeur est celle de la derniere expression evaluee. Cela n’a d’interet que si lespremieres expressions realisent des effets de bord. Le plus souvent, on encadre une sequencepar le couple begin-end, ou, a la rigueur, par une paire de parentheses.

A.3.2 Operateurs & et or

Les operateurs booleens & et or ne peuvent pas etre definis en tant que fonctions. Eneffet, ils n’evaluent leurs arguments qu’au fur et a mesure de leur besoin. Par exemple,true or (raise (Failure "plante !")) ne declenche pas d’erreur mais vaut true ; dememe false & ((3/0) > 0) ne declenche pas d’erreur mais vaut false.

Notons qu’a partir de la version 0.7 de Caml, nous disposons d’une alternative pour un emprunt aulangage Cl’orthographe de ces operateurs, a savoir && et ||.

A.3.3 Conditionnelles

Une expression conditionnelle est de la forme

if expression test then expression 1 else expression 2

ou expression test est de type bool et ou expression 1 et expression 2 doivent etred’un meme type, qui sera celui de l’expression conditionnelle toute entiere.

Dans le seul cas ou le type de expression 1 est unit la clause else peut etre omise.

A.3.4 Filtrage

L’expression match

Sa syntaxe est :

match expression with

motif1 -> expr1| motif2 -> expr2. . .

| motifN -> exprN

Caml tente de filtrer expression avec motif1 ; en cas de succes, l’expression match

complete vaut expression1 . En cas d’echec, les motifs suivants sont essayes dans l’ordre.Si aucun motif ne filtre expression , l’exception Match_failure est declenchee. Il faut quetoutes les expressions expri soient du meme type, qui est celui de l’expression match touteentiere.

Par exemple, le filtrage typique sur les listes est :

match liste with

[] -> ...

| tete :: queue -> ...

Page 117: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.3. STRUCTURES DE CONTROLE 117

Motifs de filtrage

Les motifs sont definis par :

motif ≡

| identificateur| constante| []

| [ motif ; . . . ; motif ]| motif :: motif| motif, . . . , motif| { etiquette = motif ; . . . ; etiquette = motif }| constructeur-constant| constructeur-non-constant motif| ( motif )| motif | motif| motif as identificateur| ( motif : expression-de-type )

• filtre tout et n’importe quoi ;

• un identificateur filtre tout mais en effectuant la liaison ;

• une constante ne filtre qu’elle-meme ;

• []filtre la liste vide ;

• une liste de motifs filtre, motif par motif, les elements d’une liste ;

• motif1 :: motif2 filtre une liste dont la tete est filtree par motif1 et la queue parmotif2 ;

• un multiplet de motifs filtre, motif par motif, les elements d’un multiplet ;

• { etiquette = motif ; . . . ; etiquette = motif }filtre champ par champ uneexpression de type produit ;

• un constructeur constant ne filtre que lui-meme ;

• constructeur-non-constant motif filtre une expression de type somme ;

• on peut parentheser les motifs ;

• motif1 | motif2 filtre ce que filtrent motif1 ou motif2 , qui n’ont pas le droit deproceder a des liaisons ;

• motif as identificateur filtre ce que filtremotif et introduit la liaison de l’expressionfiltree avec identificateur ;

• ( motif : expression-de-type ) filtre une expression filtree par motif qui a letype expression-de-type .

Depuis la version 0.7, Caml reconnaıt ce qu’on peut appeler des surveillants de motifs quiguards en anglaispeuvent s’ajouter apres chaque motif. Avec une grammaire BNF, on ecrirait

motif avec surveillants ≡motif

| motif when cond

Page 118: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

118 UN PETIT MANUEL DE REFERENCE DE CAML

Dans la nouvelle syntaxe, le motif invoque n’est repute filtrer l’expression testee que si lacondition ecrite s’evalue en true, avec les liaisons eventuellement introduites par le filtrage,bien sur. A defaut, on passe, comme d’habitude, au motif suivant.

Expressions fonctionnelles

Une fonction se definit naturellement par filtrage :

function motif1 -> expr1| motif2 -> expr2. . .

| motifN -> exprN

Si ’a est le type le plus general des expressions filtrees par les motifi et si ’b est le typecommun aux expri , le type de cet objet (fonctionnel) se note ’a -> ’b.

Remarques :

• l’air de rien function x -> x fait deja intervenir un filtrage ;

• l’expression

match expression with

motif1 -> expr1| motif2 -> expr2. . .

| motifN -> exprN

est en fait equivalente a

( function motif1 -> expr1| motif2 -> expr2. . .

| motifN -> exprN ) ( expression )

• function motif1 -> . . . -> function motifN -> expression

peut s’abreger en

fun motif1 . . . motifN -> expression

• -> est associatif a droite, c’est-a-dire que le type ’a -> ’b -> ’c doit s’entendrecomme etant le type ’a -> ( ’b -> ’c )

• Caml comprend l’expression x y z comme etant (x y) z et non pas x (y z).

Page 119: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.3. STRUCTURES DE CONTROLE 119

Expressions let locales

L’expression

let motif1 = expression1and . . .

and motifN = expressionNin expression

opere des liaisons (par filtrage) qui ne seront que locales a l’evaluation de expression . Onnotera que toutes les expressioni sont evaluees avant la premiere liaison. Elle pourrait s’ecrire

( fun motif1 . . . motifN -> expression ) expression1 . . . expressionN

En ecrivant let rec au lieu de let, on autorise des definitions mutuellement recursives.

Expressions let globales : definitions

L’expression

let motif1 = expression1and . . .

and motifN = expressionN

opere des liaisons (par filtrage) qui seront globales : on ajoute de nouvelles definitions al’interprete. On notera que toutes les expressioni sont evaluees avant la premiere liaison.Ainsi :

#let x = 0 and y = 1 ;;

x : int = 0

y : int = 1

#let x = y and y = x ;;

x : int = 1

y : int = 0

En ecrivant let rec au lieu de let, on autorise des definitions mutuellement recursives.

Sucre syntaxique

let f motif1 . . . motifN = expression

est compris par Caml comme

let f = fun motif1 . . . motifN -> expression .

A.3.5 Boucles

On dispose de deux types de boucles. La boucle while obeit a la syntaxe suivante

while expression test do expression done

La boucle for obeit a la syntaxe suivante

for identificateur = expr1 to expr2 do expression done

ou expr1 et expr2 sont du type int. L’identificateur est implicitement local. Caml connaıtaussi la version downto.

Page 120: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

120 UN PETIT MANUEL DE REFERENCE DE CAML

A.3.6 Exceptions

Il y a deux facons de definir des exceptions : exception identificateur et exceptionidentificateur of expression-de-type . Les definitions d’exception ajoutent de nou-veaux constructeurs au type somme predefini exn des valeurs “exceptionnelles”.

On declenche une exception en invoquant la fonction raise : exn -> ’a. On rattrapeune exception par la structure de controle try, dont la syntaxe est :

try expression with motif1 -> expression1|. . .

|motifN -> expressionN

L’evaluation de expression peut declencher une exception, celle-ci est eventuellement in-terceptee par le filtrage du try (sans quoi elle se propage), l’evaluation de expression sepoursuit alors par l’evaluation du expri adequat.

Il peut etre interessant d’utiliser des exceptions non constantes pour recuperer l’etat ducalcul au moment du declenchement de l’exception.

Par exemple :

#exception Trouve of int ;;

Exception Trouve defined.

#let trouve_indice predicat =

let rec trouve n = function

[] -> raise ( Failure "absent" )

| a :: q -> if predicat(a) then raise (Trouve n)

else trouve (n+1) q

in trouve 0 ;;

trouve_indice : (’a -> bool) -> ’a list -> ’b = <fun>

#trouve_indice (function x -> (x mod 2) = 0) [ 3 ; 5 ; 8 ; 7 ] ;;

Uncaught exception: Trouve 2

L’exception Failure of string est predefinie. failwith chaıne est equivalent araise ( Failure chaıne ).

A.4 Effets de bord : types mutables

A.4.1 Types mutables

Caml permet de definir des valeurs mutables de type produit en introduisant le mot-clefmutable devant un (ou plusieurs) champ d’un type produit ; on dispose alors de l’operationd’affectation, qui permet de modifier physiquement les valeurs correspondantes. Exemple :

#let toto = {Secu = 111 ; Age = 14} ;;

toto : personne = {Secu=111; Age=14}

#toto.Age ;;

- : int = 14

#toto.Age <- 15 ;;

- : unit = ()

#toto ;;

- : personne = {Secu=111; Age=15}

A.4.2 Autre type mutable : les references

Si ’a est un type, le type ’a ref est le type des references de ce type. On dispose des deuxoperateurs ! de dereferencement, et := d’affectation. Voici une petite session Caml pour enmontrer l’usage :

#let a = ref 1 ;;

a : int ref = ref 1

#a := ((!a) + 1) ;;

Page 121: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.4. EFFETS DE BORD : TYPES MUTABLES 121

- : unit = ()

#a ;;

- : int ref = ref 2

#!a ;;

- : int = 2

Voici comment pourraient etre definies les references :

#type ’a reference = { mutable Reference : ’a } ;;

Type reference defined.

#let reference x = { Reference = x } ;;

reference : ’a -> ’a reference = <fun>

#let deref {Reference = x} = x ;;

deref : ’a reference -> ’a = <fun>

#let recoit r x = r.Reference <- x ;;

recoit : ’a reference -> ’a -> unit = <fun>

##infix "recoit" ;;

#let a = reference 1 ;;

a : int reference = {Reference=1}

#a recoit ((deref a) + 1) ;;

- : unit = ()

#a ;;

- : int reference = {Reference=2}

#deref a ;;

- : int = 2

A.4.3 Autre type mutable : les vecteurs

Un vecteur se note [| x1; x2; x3; ...; xN |] ou les objets xi sont tous d’un meme type’a, le type du vecteur est alors ’a vect. Si v : ’a vect est un vecteur, on accede a soni-eme element par v.(i), la numerotation commencant a 0 ; la structure est mutable : onmodifie en place le i-eme element en ecrivant v.(i) <- expression .

La bibliotheque standard fournit entre autres les fonctions suivantes.

• vect_length : ’a vect -> int renvoie la taille du vecteur ;

• make_vect : int -> ’a -> ’a vect fabrique un nouveau vecteur dont la taille estfixee par le premier argument, le second initialisant chacun de ses elements ;

• list_of_vect : ’a vect -> ’a list construit la liste des elements du vecteur ;

• vect_of_list : ’a list -> ’a vect realise l’operation inverse.

Page 122: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

122 UN PETIT MANUEL DE REFERENCE DE CAML

A.5 Effets de bord : entrees / sorties

A.5.1 Entrees/sorties clavier/ecran

La bibliotheque standard fournit entre autres les fonctions suivantes d’affichage a l’ecran :

• print_char : char -> unit affiche un caractere a l’ecran ;

• print_string : string -> unit affiche une chaıne de caracteres a l’ecran ;

• print_int : int -> unit affiche la representation decimale d’un entier a l’ecran ;

• print_float : float -> unit affiche la representation decimale d’un flottant al’ecran ;

• print_newline : unit -> unit affiche un saut de ligne a l’ecran.

Signalons au passage l’existence de quatre fonctions de conversion :

string_of_int : int -> string et string_of_float : float -> string,

et leurs inverses

int_of_string : string -> int etfloat_of_string : string -> float.

La bibliotheque standard fournit entre autres les fonctions suivantes d’entree au clavier :

• read_line : unit -> string attend l’entree au clavier d’une chaıne de caractereslimitee par un saut de ligne, renvoie cette chaıne sans le saut de ligne ;

• read_int : unit -> int est la composee de int_of_string sur read_line ;

• read_float : unit -> float est la composee defloat_of_string surread_line ;

A.5.2 Fichiers texte

Les fichiers texte utilisent le format ascii standard et sont traitables par tous les editeurs detexte.

La bibliotheque standard fournit entre autres les fonctions d’entree suivantes :

• open_in : string -> in_channel prend le nom du fichier a ouvrir et fournit unobjet de type in_channel via lequel se feront toutes les operations d’entree sur cefichier ;

• input_char : in_channel -> char lit un caractere sur le fichier ;

• input_line : in_channel -> string lit sur le fichier une chaıne de caractereslimitee par un saut de ligne, renvoie cette chaıne sans le saut de ligne ;

• close_in : in_channel -> unit ferme le fichier en lecture.

Toute lecture qui tente d’aller au dela de la fin du fichier declenche l’exceptionEnd_of_file.La bibliotheque standard fournit entre autres les fonctions de sortie suivantes :

• open_out : string -> out_channel prend le nom du fichier a ouvrir et fournitun objet de type in_channel via lequel se feront toutes les operations de sortie sur cefichier ;

• output_char : out_channel -> char -> unit ecrit un caractere sur le fichier ;

• output_string : out_channel -> string -> unit ecrit une chaıne de caracteressur le fichier ;

• close_out : out_channel -> unit ferme le fichier en ecriture.

Page 123: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

A.6. EFFETS DE BORD : LA BIBLIOTHEQUE GRAPHIQUE 123

A.5.3 Fichiers binaires

Les fichiers binaires utilisent un format particulier a Caml pour representer les valeurs dulangage, et ne sont lisibles que par des programmes Caml.

La bibliotheque standard fournit entre autres les fonctions d’entree suivantes :

• open_in_bin : string -> in_channel prend le nom du fichier binaire a ouvrir etfournit un objet de type in_channel via lequel se feront toutes les operations d’entreesur ce fichier ;

• input_value : in_channel -> ’a lit une valeur Caml sur le fichier ;

• close_in : in_channel -> unit ferme le fichier en lecture.

L’exception End_of_file est declenchee dans les memes conditions que precedemment.La bibliotheque standard fournit entre autres les fonctions de sortie suivantes :

• open_out_bin : string -> out_channel prend le nom du fichier binaire a ouvriret fournit un objet de type in_channel via lequel se feront toutes les operations desortie sur ce fichier ;

• output_value : out_channel -> ’a -> unit ecrit une valeur Caml sur le fichier ;

• close_out : out_channel -> unit ferme le fichier en ecriture.

A.6 Effets de bord : la bibliotheque graphique

La bibliotheque graphique n’est accessible qu’apres l’incantation #open "Graphics" ;; Onouvre la fenetre graphique par l’incantation open_graph "highest" qui renvoie ().

On dispose alors entre autres des fonctions et types suivants :

• le type color est predefini comme un synonyme de int. La fonction rgb attend troisentiers en arguments et renvoie la couleur decrite par ce codage RVB. black white

red green blue yellow cyan magenta sont predefinies ;

• set_color : color -> unit fixe la couleur courante des traces ;

• clear_graph : unit -> unit efface la fenetre graphique ;

• size_x : unit -> int et size_y : unit -> int renvoient la taille de la fenetregraphique ;

• plot : int -> int -> unit affiche un point ;

• moveto : int -> int -> unit deplace le point courant sans dessiner ;

• lineto : int -> int -> unit trace un segment entre le point courant et le pointspecifie qui devient le nouveau point courant ;

• current_point : unit -> int * int renvoie la position du point courant ;

• draw_circle : int -> int -> int -> unit attend les coordonnees du centre etle rayon du cercle a tracer ;

• fill_rect x y largeur hauteur peint un rectangle ;

• fill_circle x y rayon peint un disque ;

• wait_next_event [ Button_down ; Key_pressed ] attend sans rien faire quel’utilisateur declenche un evenement clavier ou souris, peu importe la valeur renvoyee ;

• close_graph : unit -> unit supprime la fenetre graphique.

Page 124: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 125: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Annexe B

Quelques idees pour laprogrammation fonctionnelle

125

Page 126: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

126 QUELQUES IDEES POUR LA PROGRAMMATION FONCTIONNELLE

B.1 Fonctionnelles en guise de structures de controle

Nous allons ici montrer comment les structures de controles habituelles des autres langages(branchement, boucles, ou meme sequences) peuvent etre mimees en Caml a l’aide de fonc-tionnelles. Les methodes obtenues s’averent souvent plus agreables d’utilisation, et avec unpeu d’habitude elles viennent plus naturellement a l’esprit.

B.1.1 Un exemple

Supposons qu’on veuille ecrire en Caml une fonction qui produise les memes effets que lespseudo-instructions Pascal suivantes : ou les instructions en

pseudo-Pascal?begin z := 1 ;

while x <= y do

begin

z := z * x ;

x := x + 1

end

end

Clairement ce programme renvoie dans z le produit de tous les entiers de l’intervalle [x..y].Bien entendu, on pourrait reproduire tres exactement ces instructions en Caml en utilisant

des references. beurk !

Programme B.1 Une version fort laide avec references

let z = ref 1 and x’ = ref x and y’ = ref y

in

while !x’ <= !y’ do

z := !z * !x’ ;

x’ := !x’ + 1

done ;

!z

Cela fonctionne, certes, mais c’est tout sauf de la programmation fonctionnelle, et je diraimeme plus, tout sauf du Caml !

Une premiere reflexion qu’il faut mener concerne la boucle en cause : il faudra de toutesfacons, en style imperatif, construire un invariant de boucle pour prouver l’algorithme. Ce style imperatif ≡ a la

Pascalfaisant, on s’apercoit que l’etat de l’algorithme tout au long des iterations est caracterise parle triplet (x, y, z). oui, ici c’est evident !

Considerons les instructions du programme une a une et ecrivons, en Caml, la modificationcorrespondante du triplet en question.

La premiere ligne, z := 1 correspond a la procedure Caml

let f1 (x,y,z) = (x,y,1) ;;

De meme le corps de la boucle est constitue de deux instructions que l’on peut ecrire ainsi :

let f2 (x,y,z) = (x,y,z*x) ;;

let f3 (x,y,z) = (x+1,y,z) ;;

let corps = compose f3 f2 ;;

ou la fonction compose est classiquement definie par

let compose f g x = f (g x) ;;

On aura note que f1, f2, f3 et corps sont du type int*int*int -> int*int*int.Remarquons encore qu’on aurait pu ecrire directement

let corps (x,y,z) = (x+1,y,z*x) ;;

Page 127: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

B.1. FONCTIONNELLES EN GUISE DE STRUCTURES DE CONTROLE 127

Interessons-nous maintenant au test, qu’il est facile d’ecrire :

let test (x,y,z) = x <= y ;;

Reste a ecrire un equivalent du while :

let rec boucle (x,y,z) =

if test (x,y,z) then boucle (corps (x,y,z))

else (x,y,z) ;;

Finalement let prog = compose boucle f1 definit tout simplement le programmecomplet, ou, en developpant et simplifiant :

Programme B.2 Une version fonctionnelle

let prog (x,y,z) =

let rec boucle (x,y,z) =

if x <= y then boucle (x+1,y,z*x)

else (x,y,z)

in

boucle (x,y,1) ;;

En relisant le code obtenu, on s’apercoit aisement qu’il n’est pas utile de faire figurer ydans la definition de boucle, ce qui permettrait de simplifier encore un peu notre programme.En outre, il est plus naturel de demander au programme de ne rendre que la valeur finale de z,et de n’attendre en argument que les valeurs de x et y. Enfin, on pourrait aussi tout reecriresous forme curryfiee. On trouve alors la version definitive :

Programme B.3 La version finale

let prog x y =

let rec boucle x z =

if x <= y then boucle (x+1) (z*x)

else z

in boucle x 1 ;;

Cette derniere version paraıt assez naturelle, je trouve, et avec de l’habitude c’est celle quel’on ecrit directement.

B.1.2 Retour au probleme general : la sequence

Comme nous venons de le voir, nous allons simuler les structures habituelles a l’aide de fonctionsCaml qui modifieront une variable etat qui contient l’ensemble des informations a maintenirtout le long du programme. Dans notre exemple, etat etait le triplet (x, y, z).

La sequence est facile a ecrire : si f1 et f2 traduisent l’effet sur la variable etat desinstructions C1 et C2, alors c’est bien entendu compose f2 f1 qui traduit la sequence C1;C2.

On notera pour simplifier C1Ã f1, C2Ã f2, puis C1; C2Ã compose f2 f1, voire toutsimplement ;Ã compose.

On dira ainsi que la structure de controle que constitue la sequence est implementee parla fonctionnelle compose.une fonctionnelle est

une fonction quiopere sur desfonctions B.1.3 Boucle while

Considerons maintenant le probleme de la structure de controle while. Il s’agit d’ecrire lafonctionnelle bouclewhile correspondante. Elle attendra en arguments des fonctions, biensur, a savoir test et corps.

Page 128: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

128 QUELQUES IDEES POUR LA PROGRAMMATION FONCTIONNELLE

Finalement, si la variable etat est du type Caml α, notre fonctionnelle bouclewhile apour type

(α→ bool)→ (α→ α)→ α→ α,

puisque α → bool est le type de test, et α → α le type de chaque instruction, donc decorps mais aussi du resultat de bouclewhile test corps.

L’exemple etudie plus haut nous guide vers la definition de bouclewhile :

let bouclewhile test corps =

let rec boucle etat = if (test etat) then boucle (corps etat)

else etat

in boucle ;;

ou, plus simplement encore :

let rec bouclewhile test corps etat =

if (test etat) then bouclewhile test corps (corps etat)

else etat ;;

B.1.4 Boucle de comptage

Dans le meme ordre d’idee, ecrivons maintenant la fonctionnelle qui correspond a une bouclede comptage, qu’on pourrait ecrire de facon imperative par

repeter n fois

begin

<corps de la boucle>

end

Ainsi notre fonctionnelle aura cette fois, avec les conventions precedentes, un type egal a

int→ (α→ α)→ α→ α.

Elle n’est pas difficile a ecrire :

let rec repeter n corps etat =

if n < 1 then etat

else repeter (n-1) corps (corps etat) ;;

B.2 Gestion de suites infinies

Dans cette section, nous allons etudier le moyen de representer en Caml des structures infinies,au moyen de ce qu’on appelle d’habitude l’evaluation paresseuse. en anglais, lazy

evaluation

B.2.1 L’idee de l’evaluation paresseuse

On ne peut evidemment pas — en quelque langage qu’on travaille — conserver physiquement du hardune information de taille infinie. On va donc plutot conserver l’information necessaire aucalcul d’un nombre infini de termes.

Plus precisement, voulant implementer des listes infinies denombrables, ce que nous ap-pellerons des suites, nous utiliserons le type suivant :

type ’a suite_infinie = Nil | Cellule of (unit -> ’a * ’a suite_infinie) ;;

Nil represente bien sur la suite vide. Sinon, une suite est de la forme Cellule(f) ou f estune fonction sans argument dont l’evaluation fournit le couple (tete,queue) qui representela liste. Ainsi, ce ne sont pas les valeurs qui sont conservees, mais plutot les methodesnecessaires a leur calcul. Si l’on veut acceder a la dixieme valeur de la suite, il faudra passerpar 10 evaluations successives.

On ecrit sans difficulte les fonctions d’acces aux suites :

Page 129: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

B.2. GESTION DE SUITES INFINIES 129

let cons x l =

let f () = (x,l)

in Cellule f ;;

let tete = function

Nil -> raise Suite_Vide

| Cellule f -> match f() with x,_ -> x ;;

let queue = function

Nil -> raise Suite_Vide

| Cellule f -> match f() with _,q -> q ;;

let est_vide = function

Nil -> true

| _ -> false ;;

ou on a bien sur defini l’exception Suite Vide.On ecrit egalement une fonction force, qui prend en arguments un entier n et une suite et

qui rend un couple forme de la liste (une vraie liste Caml) des n premiers elements de la suiteet du reste de la suite, ce qui forme une suite infinie.

C’est une fonction recursive simple qui s’ecrit :

let rec force n l = match n,l with

0,l -> [],l

| n,Nil -> raise Suite_Vide

| n,Cellule f ->

match f() with x,q -> let liste,reste = force (n-1) q

in (x :: liste),reste ;;

A partir de la, on peut, pour la facilite d’utilisation, definir :

let premiers n l = match force n l with liste,_ -> liste ;;

let reste n l = match force n l with _,r -> r ;;

B.2.2 Quelques suites simples

On ecrit facilement la suite des entiers superieurs ou egaux a n :

let rec a_partir_de n = let f () = n,(a_partir_de (n+1)) in Cellule f ;;

Alors on a tout simplement : let N = a_partir_de 0 ;; qui represente bien tout N !On pourrait aussi definir une suite constante egale a 1 par :

let suite_de_1 = let rec f() = 1,(Cellule f) in Cellule f ;;

Si en revanche on souhaite implementer pour les suites un genre de fonction filter commeon l’a fait ci-dessus pour les listes, il faudra etre plus prudent, et ne pas executer la recursioncomplete (elle serait infinie), mais la retarder grace a l’utilisation d’une fonction locale.les Americains

utilisent le verbedelay

Voici le code obtenu :

let rec filtre predicat = function

Nil -> Nil

| Cellule f -> match f() with x,q ->

if (predicat x) then

let g () = x,(filtre predicat q)

in Cellule g

else filtre predicat q ;;

Par exemple, ecrivant let non_multiple a b = (b mod a) <> 0 ;;, on obtiendrales entiers naturels impairs par l’instruction

let entiers_impairs = filtre (non_multiple 2) N ;;

Page 130: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

130 QUELQUES IDEES POUR LA PROGRAMMATION FONCTIONNELLE

B.2.3 La suite des nombres premiers

On pourrait s’amuser a definir les operations courantes sur les suites : addition, produit deCauchy, derivation, etc. Nous allons plutot, car cela semble plus amusant, definir la suitedes nombres premiers, grace au celebre crible d’Eratosthene. En fait nous avons fait le plusdifficile. Il ne reste plus qu’a ecrire la fonction crible elle-meme.

let elimine x l = filtre (non_multiple x) l ;;

let rec crible = function

Nil -> raise Suite_Vide

| Cellule f -> match f() with x,q ->

let g() = x,(crible (elimine x q))

in Cellule g ;;

Pour terminer il suffit d’ecrirelet nombres_premiers = crible (a_partir_de 2) ;;

Programme B.4 Utilisation des suites infinies

1 > Caml Light version 0.6

23 #include "Suites_infinies.ml";;

4 ...

5 - : unit = ()

6 #premiers 10 entiers_naturels ;;

7 - : int list = [0; 1; 2; 3; 4; 5; 6; 7; 8; 9]

8 #premiers 20 nombres_premiers ;;

9 - : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41;

10 43; 47; 53; 59; 61; 67; 71]

Page 131: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

B.2. GESTION DE SUITES INFINIES 131

Programme B.5 Implementation des suites infinies

1 type ’a suite_infinie = Nil | Cellule of (unit -> ’a * ’a suite_infinie) ;;

23 exception Suite_Vide ;;

45 let cons x l =

6 let f () = (x,l)

7 in Cellule f ;;

89 let tete = function

10 Nil -> raise Suite_Vide

11 | Cellule f -> match f() with x,_ -> x ;;

1213 let queue = function

14 Nil -> raise Suite_Vide

15 | Cellule f -> match f() with _,q -> q ;;

1617 let est_vide = function

18 Nil -> true

19 | _ -> false ;;

2021 let rec force n l = match n,l with

22 0,l -> [],l

23 | n,Nil -> raise Suite_Vide

24 | n,Cellule f ->

25 match f() with x,q -> let liste,reste = force (n-1) q

26 in (x :: liste),reste ;;

2728 let premiers n l = match force n l with liste,_ -> liste ;;

29 let reste n l = match force n l with _,r -> r ;;

3031 let rec a_partir_de n = let f () = n,(a_partir_de (n+1)) in Cellule f ;;

3233 let entiers_naturels = a_partir_de 0 ;;

3435 let suite_de_1 = let rec f() = 1,(Cellule f) in Cellule f ;;

3637 let rec filtre predicat = function

38 Nil -> Nil

39 | Cellule f -> match f() with x,q ->

40 if (predicat x) then

41 let g () = x,(filtre predicat q)

42 in Cellule g

43 else filtre predicat q ;;

4445 let non_multiple a b = (b mod a) <> 0 ;;

4647 let entiers_impairs = filtre (non_multiple 2) entiers_naturels ;;

4849 let elimine x l = filtre (non_multiple x) l ;;

5051 let rec crible = function

52 Nil -> raise Suite_Vide

53 | Cellule f -> match f() with x,q ->

54 let g() = x,(crible (elimine x q))

55 in Cellule g ;;

5657 let nombres_premiers = crible (a_partir_de 2) ;;

Page 132: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais
Page 133: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Annexe C

Glossaire franco-anglais

133

Page 134: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

134 GLOSSAIRE FRANCO-ANGLAIS

a to a reculons vers downtoabandonner abort afficher displayalgorithme algorithm alors thenanneau ring arbre treearete edge avec withbooleen boolean chaıne stringchemin path cle keycompilateur compiler corps (algebre) field (algebra)court short couvrant spanningcreer create dans inde of debut beginen tant que as enregistrement recordensemble set entier integerenveloppe convexe convex hull erreur errorespace vectoriel vector space essayer tryet and exponentiel exponentialextraire extract faire dofait done faux falsefile d’attente queue file de priorite priority queuefiltrage pattern matching filtrer matchfin end fonction functionforet forest fusion uniongraphe graph groupe groupimpair odd imprimer printinserer insert interprete interpreterjusque until langage languagelien link listage listingliste list lister listlogarithme logarithm longueur lengthmatrice matrix modifiable mutablemotif pattern nœud nodenombre premier prime number nombre reel floating point numbernon not pair evenou or ou whereparesseux lazy partition partitionperforer punch pile stackpointeur pointer polynome polynomialpreuve proof programme programprefixe prefix racine rootracine carree square root retarder delaysi if sinon elsesoit let sommet vertexsortir exit stopper stopstructure structure supprimer deletetant que while tas heaptri sort trier sorttrouver find type typeunion union valeur valuevecteur vector vers tovide empty vrai true

Page 135: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

Annexe D

Glossaire anglo-francais

135

Page 136: Une boˆıte d’outils pour la programmation en Camlpauillac.inria.fr/~cheno/taupe/poly.pdf · B Quelques idees pour la programmation fonctionnelle 125 ... D Glossaire anglo-franc¸ais

136 GLOSSAIRE ANGLO-FRANCAIS

abort abandonner algorithm algorithmeand et as en tant quebegin debut boolean booleencompiler compilateur convex hull enveloppe convexecreate creer delay retarderdelete supprimer display afficherdo faire done faitdownto a reculons vers edge areteelse sinon empty videend fin error erreureven pair exit sortirexponential exponentiel extract extrairefalse faux field (algebra) corps (algebre)find trouver floating point number nombre reelforest foret function fonctiongraph graphe group groupeheap tas if siin dans insert insererinteger entier interpreter interpretekey cle language langagelazy paresseux length longueurlet soit link lienlist liste list listerlisting listage logarithm logarithmematch filtrer matrix matricemutable modifiable node nœudnot non odd impairof de or oupartition partition path cheminpattern motif pattern matching filtragepointer pointeur polynomial polynomeprefix prefixe prime number nombre premierprint imprimer priority queue file de prioriteprogram programme proof preuvepunch perforer queue file d’attenterecord enregistrement ring anneauroot racine set ensembleshort court sort trisort trier spanning couvrantsquare root racine carree stack pilestop stopper string chaınestructure structure then alorsto vers to atree arbre true vraitry essayer type typeunion fusion union unionuntil jusque value valeurvector vecteur vector space espace vectorielvertex sommet where ouwhile tant que with avec