Vous êtes sur la page 1sur 80

collection Informat

Table des matires

1 . Introduction.. .............................................................................. 1 .l. Quelques mots sur lenvironnement ............................................... 1.2. Notes bibliographiques sommaires.. .............................................. 1.3. Remerciements.. ........................................................................ 1.4. Le choix de programmes ............................................................. 2 . Des programmes pour commencer.. ........................................... 2.1. Le mode dun vecteur .................................................................. 2.1.1. Construction de la premire version du programme .................. 2.1.2. Remarques mthodologiques ............................................... 2.2. Recherche dun objet.. ................................................................ 2.2.1. Recherche linaire.. ........................................................... 2.2.2. Un pige.. ....................................................................... 2.2.3. La dichotomie.. ................................................................ 2.3. De la complexit des algorithmes.. ................................................ 2.4. Rsum des principes introduits.. .................................................. 2.4.1. Un apparte sur les preuves de programmes.. ........................... 2.4.2. Le style dcriture ............................................................. 2.5. Adressage dispers...................................................................... 2.5.1. Algorithme avec chanage.. ................................................. 2.5.2. Autant de cl& que de cases.. ................................................ 2.5.3. Choix de cl et efficacit .................................................... 2.6. Exercices .................................................................................. 3. Les tris ....................................................................................... 3.1. Recherche du plus petit lment.. ..................................................

13 16 17 17 18 21 21 21 23 24 25 28 29 34 35 36 37 37 38 40 42 43 45 46

@ Hermbs, Paris, 1992 ditions Herms 34, rue Eugne Plachat 75017 Paris ISBN 2-86601-323-g

ALGO~Q~IE

m PROGRAmION

3.2. Tri par insertion ........................................................................ 3.3. Tri par bulles.. .......................................................................... 3.4. Diviser pour &gner .................................................................... 3.4.1. Diviser pour rkgner avec partition ........................................ 3.4.2. Solution sans appel r&rsif.. .............................................. 3.4.3. Quelques commentaires sur la nkursivit ............................... 3.4.4. Deux pivots.. ................................................................... 3.4.5. Tri par fusion.. ................................................................. 3.5. F&um de la complexite des algorithmes ....................................... 3.6. Exercices.. ................................................................................ Des 4.1. 4.2. 4.3. structures de donnes.. ........................................................ Les piles.. ................................................................................ Les files ................................................................................... Les arbres.. ............................................................................... 4.3.1. Arbres binaires et arbres n-aires ........................................... 4.3.2. Reprsentation des arbres .................................................... 4.3.3. Parcours darbres ............................................................... 4.3.4. Parcours prfix et post-fix................................................ Les treillis.. .............................................................................. Les graphes .............................................................................. 4.5.1. Algorithme de Schorr-Waite ................................................ 4.5.2. Amlioration de lalgorithme de Schorr-Waite.. ....................... 4.5.3. Reprsentation dun graphe sur une matrice boolenne.. ............ 4.5.4. Fermeture transitive .......................................................... Ordres partiels et totaux .............................................................. Exercices.. ................................................................................

48 51 54 54 57 59 61 63 66 66 67 67 68 70 71 72 73 74 78 79 80 84 87 88 89 90 93 93 96 97 99 100 101 105 105 108 109 111 111 114

6.1.2. Marche arriere, arbres et graphes .......................................... 6.2. Les huits reines ......................................................................... 6.2.1. Une version amliore ....................................................... 6.2.2. Une deuxieme approche ...................................................... 6.3. Exercices .................................................................................. 7. lkansformation de programmes.. ................................................ 7.1. Revenons sur le mode vecteur ...................................................... 7.2. La multiplication des gitans ......................................................... 7.3. Exercices .................................................................................. 8 . Quelques structures de donnes particulires.. .......................... 8.1. Les arbres ordonns.. .................................................................. 8.2. Les arbres quilibrs ................................................................... 8.2.1. Algorithmes de manipulation darbres quilibrs.. .................... 8.3. Les B-arbres.. ............................................................................ 8.4. Exercices .................................................................................. Bibliographie e t rfrences.. ........................................................... Glossaire ......................................................................................... Solutions de certains exercices.. ....................................................

115 116 118 119 122 125 126 128 131 133 133 135 137 138 143 145 148 151

4.4. 4.5.

4.6. 4.7.

5. Rcurrence et rcursivit ........................................................... 5.1. Lexemple type . Les tours dHanoi ............................................... 5.1.1. Cot de lalgorithme .......................................................... 5.1.2. Une analyse plus pousse.. ................................................. 5.2. Des permutations....................................................................... 5.2.1. Permutations par changes de voisins ................................... 5.2.2. Le programme .................................................................. 5.2.3. Relation avec les tours dHanoi ............................................ 5.2.4. Une variante .................................................................... 5.2.5. Une version rcursive ........................................................ 5.3. Exercices .................................................................................. 6. La marche arrire ........................................................................ 6.1. La souris et le fromage ............................................................... 6.1.1. Version t+cursive ..............................................................
6

Tables et figures

2.1. Comparaison entre la recherche I&%re et la dichotomie (02.3) 2.2. Table pour ladressage dispers avec chanage ($25.1) 2.3. Table sans zone de dbordement ($2.52) 3.1. Appels aprs parution (83.4.3) 3.2. Vecteur en cours de partition (93.4.4) 4.1. Arbre du tri diviser pour rgner (94.3) 4.2. Transformation darbre n-aire en arbre binaire (4.3.1) 4.3. Reprsentation de larbre de la figure 4.2 ($4.3.2) 4.4. Parcours en profondeur dabord (ordre prefix) ($4.3.4) 4.5. Parcours en largeur dabord ($4.3.4) 4.6. Triple visite des nuds dun arbre ($4.3.4) 4.7. Parcours en ordre infix ($4.3.4) 4.8. Parcours en ordre post-fix ($4.3.4) 4.9. Un treillis (94.4) 4.10. Etats dans Schorr-Waite ($4.52) 4.11. Etats dans Schorr-Waite amlior ($4.5.2) 4.12. Un graphe ($4.5.3) 4.13. Reprsentation sur une matrice binaire ($4.5.3) 4.14. Fermeture transitive du graphe de la figure 4.13 ($4.5.4) 4.15. Fermeture transitive, une ligne (94.5.4) 4.16. Arbre binaire simple ($4.6) 5.1. Position de dpart des tours dHanoi ($5.1) 5.2. Arbre dappels pour trois disques (5.1) 5.3. Permutations de quatre entiers ($5.2.1) 5.4. Valeurs de i et du pivot dans permutations(4) ($5.2.2) 5.5. Nouvelles permutations de quatre objets ($5.2.4)

ALGORITHMIQUE

JZ P R O G R A M M A T I O N

6.1. Arbre de possibilites pour la souris ($6.1.2) 7.1. Multiplication de deux entiers ($7.2) 8.1. Un arbre binaire ordonne ($8.1) 8.2. Arbre ordonn inefficace ($8.2) 8.3. Equilibrage de larbre de la figure 8.1 ($8.2) 8.4. Avec deux nuds en plus ($8.2) 8.5. Rquilibrage de larbre de la figure 8.4 ($8.2) 8.6. Un B-arbre complet avec d=l ($8.3) 8.7. Apres lecture de 1 et 2 ($8.3) 8.8. Apres lecture du 3 ($8.3) 8.9. Apres lecture du 5 ($8.3) 8.10. Apres lecture du 7 ($8.3) 8.11. Reorganisation pour viter dintroduire un niveau ($8.3)

Les programmes

2.1. Le mode dun vecteur ($2.1.1) 2.2. Recherche dun objet dans un vecteur ($2.2.1) 2.3. Recherche par dichotomie (82.2.3) 2.4. Dichotomie, version 2 ($2.2.3) 2.5. Dichotomie, version 3 ($2.2.3) 2.6. Adressage dispers ($2.5.1) 2.7. Adressage dispers, version 2 ($2.5.2) 3.1. Schma du tri par recherche du plus petit lement (93.1) 3.2. Boucle interne (83.1) 3.3. Programme complet ($3.1) 3.4. Tri par insertion ($3.2) 3.5. Version avec ETPUIS ($3.2) 3.6. Tri par bulles primitif ($3.3) 3.7. Tri par bulles normal ($3.3) 3.8. Partition dun vecteur ($3.4.1) 3.9. Insertion dans une proc&lure nkursive (93.4.1) 3.10. Version avec pile ($3.4.2) 3.11. Diviser pour rgner avec deux pivots ($3.4.4) 3.12. Tri par fusion ($3.4.5) 4.1. Miseen uvre dunepile ($4.1) (y 4.2. Une file discutable ($4.2) g 4.3. Une file circulaire (84.2) 4.4. Parcours dun arbre binaire ($4.3.3) 4.5. Utilisation dune bute ($4.3.3) J- 4.6. Parcours avec pife ($4.3.3) 4.7. Triple visite dun nud (44.3.4)

10

ALGORITHMIQIJE

ET ~m~bmmf.mo~

4.8. Visite dun graphe ($4.5) 4.9. Marquage avec trois valeurs (94.51) 4.10. Version sans rcursivit ($4.51) 4.11. Mise en uvre du prdcesseur (4.5.1) 4.12. Schorr-Waite amliore ($4.5.2) 4.13. Version condense ($4.5.2) 5.1. Tours dHanoi, version de base ($5.1) 5.2. Calcul du pivot (5.2.2) 5.3. Permutations par changes (95.2.2) 5.4. Permutations par changes, version 2 (55.2.2) 5.5. Encore les tours dHanoi ($5.2.3) 5.6. Traverse unidirectionnelle ($5.2.4) 5.7. Version rkursive (45.2.5) 6.1. La souris et le fromage ($6.1) 6.2. Forme rcursive (96.1.1) 6.3. Les huit reines ($6.2) 6.4. Les huit reines, version 2 ($6.2) ~6.5. Suppression dune pile ($6.2.1) 6.6. Avec un compteur octal ($6.2.2) 6.7. Avec des permutations ($6.2.2) 6.8. Avec permutations et marche arrire ($6.2.2) 7.1. Encore le mode dun vecteur (97.1) 7.2. Le mode amlior ($7.1) 7.3. Multiplication avec n%.trsivit ($7.2) 7.4. Suppression de la rcursivit ($7.2) 7.5. Version avec dcalages ($7.2) 8.1. Arbre ordonn ($8.1) 8.2. Arbre Cquilibr (48.2.1) 8.3. B-arbres ($8.3)

Chapitre 1

Introduction

Depuis de nombreuses annes, dans diffrents pays, les informaticiens ayant quelques prtentions acadmiques ont lutt pour tablir leur discipline de manire indpendante. Sans vouloir dire que la lutte est termine (certains nayant pas encore accept que la terre nest pas plate), on peut constater que, dans les universits respectes, il existe des laboratoires dinformatique independants, des diplmes spkialiss, et *les enseignants et/ou chercheurs en informatique sont dsormais considr& comme des scientifiques a part entire. Pourquoi cette lutte ? Et pourquoi en parler dans un livre sur lalgorithmique ? Le fait est que les informaticiens ont reprsent - et reprsentent toujours - un enjeu conomique. Comme cet enjeu a t concrtis par des moyens matriels et financiers mis a la disposition des enseignants et des chercheurs en informatique, tout un chacun a prouv le besoin de rclamer letiquette. Le tri entre les vrais et faux informaticiens nest pas termin. Dailleurs, notre avis il ne le sera jamais, et cest peut-tre heureux ainsi. Malheureusement, en voulant affirmer leur indpendance par rapport aux autres disciplines, les informaticiens ont perdu une partie de lessentiel. En se concentrant sur les techniques non-numriques (importantes et indispensables), ils ont perdu jusqu la notion de lexistence des nombret rels. De mme, un peu en singeant les mathmaticiens, qui ont montr la voie vers la tour divoire, le besoin scientifique mais aussi psychologique de la construction dune (ou de plusieurs) thorie(s) a fait perdre la vraie justification de notre existence : lcriture de programmes qui sont utiles. On est donc en prsence de plusieurs guerres, numrique contre nonnumerique, thorie contre application, utilisateurs contre spcialistes, vendeurs de vent contre professionnels srieux. Si certaines guerres peuvent tre utiles et salutaires, dautres resteront toujours striles.

12

&GORllMMIQUEEl-PROG?bU&MIlON

Ce livre ne saurait bien sr en corriger les carts. Mais nous voulons tmoigner de notre foi dans lexistence de linformatique en tant que discipline indpendante, mais surtout utile. Linformaticien comme le mathmaticien - mme si ce dernier la peut-tre oubli - est un esclave des autres. Sa raison dtre est de rendre service, cest--dire rsoudre des problbmes dapplication. Il ny a pas dinformatique acadmique diffrente de celle de lapplication numrique ou de gestion. Il ny a pas une micro-informatique diffrente de linformatique classique. Il y a une seule discipline, appele intervenir dans un trs grand nombre de domaines de lactivit humaine. Dans cette optique, la formation des informaticiens dans les universits et les coles dingnieurs doit se faire de manire quilibre. Un de nos critres est que si nous ne traitions pas tel sujet, nous pourrions par la suite avoir honte de nos &ves ?. Le monde du travail sattend ce que nos lves sachent programmer (dans le langage quutilise la compagnie concerne), quils connaissent un minimum de mthodes de rsolution de problmes numriques, et que probabilits et statistiques ne soient pas des mots sotkiques rechercher dans le dictionnaire. Le cours dcrit dans ce livre essaie de prendre en compte ces considrations. Nous lenseignons, dans des variantes appropries, depuis vingt-cinq ans, des Bves de premier et de deuxime cycle, en formation permanente, dans des diplmes sptkialiss ou non. Lexprience a montr que lenseignement de ce cours de base de linformatique est difficile, que personne ne possbde une recette miracle, mais que tout le monde (surtout ceux qui nont jamais crit un logiciel utilise par dautres personnes) pense lenseigner mieux que les autres. Nous prsentons donc ici, humblement (ce nest pas notre tendance profonde), quelques ides dun cours dalgorithmique et de programmation dont le niveau correspond a la premire anne dun deuxibme cycle pour spcialistes en informatique, en supposant que les lves concerns nont pas ncessairement subi une formation pralable dans la matire, mais quils sachent tous un peu programmer Cela pose dailleurs un probleme particulier. Nous avons lhabitude de mlanger des tudiants dorigines divers. Les uns peuvent avoir obtenu de bons rt%ultats dans un IUT dinformatique, tandis que dautres nont que peu touch un clavier (situation de plus en plus rare, mais ltkiture dun programme de 10 lignes en BASIC peut tre considre comme une exprience vide, voire ngative, en informatique). A la fin de la premire anne de deuxime cycle, il reste une corrlation entre les tudes prealables et les rsultats acadmiques. Cette corrlation disparat au cours de la deuxime anne, provoquant quelques remontes spectaculaires au classement. La seule solution que nous avons trouv ce probleme de non-homognit est de lignorer en ce qui concerne le cours, mais damnager des binmes mixtes en TP. Lexprience jusqualors est positive.

Cest en respectant lide que tous nos lbves savent un minimum sur la programmation qua disparu le chapitre de ce livre destins aux d6butants. Ainsi, les types de donnes de base (entiers, tiels, caractres), leurs reprkwnations en machine et les instructions simples (affectations, boucles, conditions) ne sont pas dfinis dans ce volume. Dans sa forme actuelle, le cours dure une anne scolaire, au rythme de deux heures par semaine. Il est ncessairement accompagn de travaux diriges et de travaux pratiques. Chez nous, il y a trois heures de travaux dirigs et quatre heures de travaux pratiques par semaine. De plus, la salle informatique est ouverte en libre service aux Ctudiants autant de temps que possible en respectant les rgles lCmentaires de scurid. Ce nest quau prix de la mise disposition dun mat&iel suffisant que les &udiants peuvent rellement apprendre. Nous proposons de nombreux exercices et problbmes. Le sujet que nous attaquons ncessite un investissement personnel important. Le cours doit servir stimuler des efforts individuels et la ralisation de projets de toutes sortes. Il doit obligatoirement se terminer par la cration dun logiciel en grandeur nature, de pr6fkence produit par un petit groupe dleves (deux quatre). Les exemples dans ce livre sont rkligs dans un langage de programmation fictif qui ressemble a PASCAL. Comme boutade bien connue des quelques universits layant subi, le langage dans lequel nous rdigeons nos algorithmes a pris le nom GRIFFGOL, qui rsulte de rflexions de lpoque de MEFIA [Cunin 19781. Cest un style de programmation relativement indpendant dun langage de programmation particulier, cest--dire que nous utilisons les concepts fondamentaux dans la forme qui nous semble la plus approprie. La construction des algorithmes se passe mieux quand on se permet une certaine libert dexpression. Leur mise en uvre dans un ordinateur ncessite une simple mise en forme en fonction du langage et du compilateur disponibles. Un corollaire est que nous disons nos tudiants quils doivent toujours r+ondre oui a la question connaissez-vous le langage X ?, quelle que soit la valeur de X. En effet, sous condition que le langage soit de style algorithmique classique, lapprentissage dun langage et/ou dun compilateur inconnu ne devrait durer que quelques jours. Dailleurs, un exercice classique que nous pratiquons est de faire recoder un travail pratique dans lun ou lautre des langages grande diffusion que nos tudiants ne connaissent pas. Cest ainsi quils absorbent, par exemple, FORTRAN. En pratique, a lheure actuelle, ils programment dans une version de PASCAL, avec des prolongements en C. Le choix est remis en cause chaque rentre universitaire, car nous ne sommes pas des missionnaires dun langage quelconque.

14

15

&GORlTHMIQUEETPROGR4MMATION

&CiORITHMIQUFi

I?I PROGRAMMMION

1.1.

Quelques

mots

sur

lenvironnement

1.2.

Notes

bibliographiques

sommaires

Une petite phrase ci-dessus merite quon sy attarde un peu plus longtemps. Nous avons parl de la disponibilit de matriels, essentielle lapprentissage de la programmation, qui est une activit constructive. On ne peut apprendre quen sexerant. Or pour sexercer de manire satisfaisante, lidal, si nous pouvons nous permettre le parallele, est que les ordinateurs doivent tre comme les WC : il y en a toujours un de libre quand on en a besoin, voire mme quand on en a envie. (Cette phrase a t prononce pour la premire fois, notre connaissance, par P.M. Woodward, du Royal Radar Establishment MaIvern, Angleterre. Cest un plaisir de rendre cet amical hommage a un matre mal connu, en se rappelant quen 1964 il fallait une vision tr&s large pour sexprimer ainsi.). Certains de nos collgues restreignent volontairement le ct exprimental de la programmation, dans le but dimposer, ds le dbut de lapprentissage, toute la rigueur ncessaire. Cest une reaction saine par rapport une situation historique datant de lpoque o la programmation se faisait nimporte comment. Mais nous nallons pas jusqu empcher nos leves de faire leurs btises. Cest en comparant des versions sauvages de programmes avec dautres qui sont bien faites quils comprennent rellement lintrt dune mthodologie. Ainsi, nous voulons que les lves passent beaucoup de temps dans la salle des machines. Au dbut, ils travaillent mal, mais deviennent - pour la plupart raisonnables la fin de la premire anne. Cette approche profite dune certaine fascination pour lordinateur (la jouissance de le dominer ?), qui sestompe aprs cette premire anne. Le fait de vouloir rflchir, plutt que de se lancer immdiatement sur la machine, est un signe majeur de maturit chez llve. Cette tape ne peut tre franchie que parce que le matriel est toujours disponible. Un tudiant ne doit pas tre stress par la longueur dune file dattente, ni frustr par des difficults matrielles. Nous notons dailleurs que, bien que les programmes crits en deuxime anne soient assez importants, loccupation des postes de travail diminue. Lentranement la programmation est une ncessit pour tous les informaticiens, quelle que soit leur exprience. Une source de stimulation pour les leves est de travailler en contact avec des enseignants qui programment bien. Tant quils sentent quil leur reste du chemin a faire pour arriver au niveau de rendement de ce modle, ils se piquent au jeu. Cela signifie que lenseignant doit lui-mme continuer a programmer rgulirement, comme le musicien qui continue faire des gammes. Mme si nous navons plus le temps de produire de gros logiciels, il faut sastreindre rsoudre rgulirement des petits problmes. Ceux-ci peuvent, par la suite, contribuer au renouvellement de notre stock dexemples et dexercices.

Il existe dj, en franais, un certain nombre de livres [Arsac 19801, [Arsac 19831, [Berlioux 19833, [Boussard 19831, [Courtin 1987a,bl, [Gerbier 19771, [Grgoire 1986, 19881, [Lucas 1983a,b], [Meyer 19781, [Scholl 19841 dans le domaine que nous abordons ici. Un pourcentage lev porte un nom dauteur qui nous est familier, car il sagit de collgues et souvent amis. Cela indique que les reflexions concernant ce sujet sortent, en France au moins, dun nombre limite dcoles qui, en plus, ont pris lhabitude de discuter. Le nombre de livres indique limportance que chacun accorde au sujet, et les diffrences dapproche dmontrent quil est loin dtre puise. Comme dans la tradition littraire, il y aura toujours des ides differentes sur ce sujet. En continuant le parallble avec lcriture, nous recommandons aux &udiants de prendre connaissance de plusieurs styles diffrents, puis de dvelopper leur propre style en profitant des apports de chacun. Ayant indiqu quelques livres en franais, il serait draisonnable de laisser limpression que la France poss&le un monopole des ides. Au niveau international, il existe une bibliographie consquente en langue anglaise, dont voici les rferences qui correspondent une selection personnelle parmi les ouvrages les plus connus : [Aho 1974, 19831, [Dijkstra 19761, [Gries 19811, [Ledgard 19751, I\irirth 1976, 19771.
1.3. Remerciements

Sur un sujet tel que le ntre, il serait impensable de citer tous ceux qui ont influenc notre reflexion. Nous nous limitons donc la mention de quelques groupes de personnes, en nous excusant vis--vis de tous les collegues individuellement. que nous ne citons pas

Comme premire influence directe, il y a eu le groupe de P.M. Woodward Malvem au dbut des annes soixante. Lauteur y a fait ses premieres armes, et ses premiers cours, partir de 1962, sous la direction de J.M. Foster et D.P. Jenkins, avec IF. Currie, A.J. Fox et P.R. Wetherall comme compagnons de travail. Le foisonnement clidees Grenoble entre 1967 et 1975 a te dune grande importance. Signalons seulement une collaboration directe avec P.Y. Cunin, P.C. Scholl et J. Voiron [Cunin 1978, 19801, bien que la liste aurait pu tre nettement plus longue. LCcole de C. Pair Nancy a montre la rigueur et a donn des opportunits de comparaison de styles. Finalement, la cr&tion du diplme dingnierie informatique a Marseille en 1985 a provoqu un effort de synthese dont le r&sultat est ce volume. De nouvelles versions du polycopi ont vu le jour Nantes en 1990 et 1991.

16

17

ftWORIIUMlQUE

FX PROGRAMWUTON

Des rencontres ponctuelles ont aussi eu leur importance, en particulier a travers les t5coles organiss par F.L.Bauer [Bauer 1974, 1975, 1976, 19791. Celles-ci ont permis de travailler avec son quipe et de rencontrer et de se confronter avec E.W.Dijkstra, G.Goos, D.Gries, J.J.Horning, P.C.Poole et W.M.Waite, parmi de nombreux autres collegues. Le feedback de gnrations dtudiants nous a permis de constater que de linformatique nest pas toujours aise si lon veut atteindre un bon niveau. Nous remercions ces jeunes (certains ne le sont plus) pour leur apport, dont ils ne se sont pas toujours rendu compte (nous aussi, nous apprenons !). Emmanuel Gaston du DU-II et un groupe du mastere dintelligence artificielle de Marseille, promotion 198889, ont corrige un certain nombre derreurs de franais. Nathalie Wurbel a aide en signahtnt des erreurs de franais et de programmation. Christian Paul et Annie Ttiier ont galement apport des corrections au niveau du franais. Au cours dun projet du DU-II, Vita Maria Giangrasso et Thierry Guzzi [Giangrasso 19891 ont men bien une tude comparative de certains algorithmes. Le journal Jeux et Stratgie, source de problmes intressants, nous a aimablement permis den utiliser dans certains exemples.
lapprentissage

Libre a chacun dapprcier notre choix. De toute faon, il est entendu que chaque enseignant mettra la matire a sa sauce. Dans la mesure du possible (ou en fonction de nos connaissances), nous avons essay dattribuer la paternit des exemples, mais beaucoup dentre eux ont des origines dj inconnues. Toute information supplementaire sera le bienvenue.

Ce livre a t prpar sur des micro-ordinateurs mis a notre disposition par differents organismes, dont feu le Centre mondial dinformatique et ressources humaines, le CNRS, luniversit dAix-Marseille III, linstitut mditerranen de technologie et luniversit de Nantes, que nous remercions. Un dernier mot sera pour mon collgue, et surtout ami, Jacek Gilewicz, professeur a luniversit de Toulon. Au dbut, nous avions envie de prparer un livre combinant lalgorithmique numrique et non numrique. Pour diffrentes raisons, ce projet na pas vu le jour. Lutilisation du nous dans cette introduction reprsente ce pluriel. Je lai laiss dans cette version definitive, en esprant que Jacek redigera le volume numrique dont nous avons besoin, en mme temps que je lui tmoigne ma reconnaissance pour son soutien et son amiti sans faille. Ce livre lui est ddi. 1.4. Le choix de programmes On apprend crire des programmes en pratiquant. Cest pour cette raison que nous travaillons partir dexemples. Ceux-ci sont de plusieurs types : - les classiques, qui se trouvent dj dans dautres ouvrages, mais qui sont essentiels a la culture dun informaticien, - les pdagogiques, que nous avons cres ou repris comme matriel de base. Ici, on attrait pu en choisir dautres, mais chaque enseignant a sa liste dexercices, souvent partage avec des collegues, - les amusements, qui sont la parce quils nous ont fait plaisir, mais qui prsentent nanmoins un intrt pour Itudiant.
18

19

Chapitre 2

Des programmes pour commencer

2.1. Le mode dun vecteur Ce problme nous est venu de D. Gries, qui lutilise depuis longtemps dans ses cours dintroduction linformatique. 11 a t repris par diffrents auteurs dans le cadre de lanalyse dalgorithmes [Arsac 19841, [Griffiths 19761. On considre un vecteur, disons dentiers, dont les lments sont ordonns. Son mode est la valeur de llment qui y figure le plus grand nombre de fois. Ainsi, prenons le vecteur suivant :
(1 3 3 6 7 7 7 11 13 13)

Le mode de ce vecteur est la valeur 7, qui figure trois fois. Nous allons crire un progmmme qui prend en entre un vecteur ordonn et qui livre le mode du vecteur en
sortie.

2.1.1. Construction de la premire version du programme Commenons par une premire ide dalgorithme. On note que lordonnancement du vecteur fait que les diffrentes occurrences dun lment qui se rpkte sont contigus. II sagit donc de trouver la plus longue chane dlments identiques. On va considrer les lments tour de rle, en se rappelant de la plus longue chane vue jusqu prsent. Pour chaque nouvel lment, on se posera la question de savoir si sa lecture mne une chane qui est plus longue que la pr&dente. Dans ce cas, cest la chane courante qui devient lachane la plus longue.

hOORlTHMQUE

J.?T PROGRAMhUTlON

liLOORllHMlQUE

W PROGRAMMKCION

Pour garder les informations ncessaires, il faut disposer des valeurs suivantes : - n est le nombre dlments dans le vecteur v, - i est le nombre dlements qui ont dj t considrs, - lmaj, est la longueur de la plus longue chane en v[l..il, - m est lindex en v dun lment dans la chane de longueur lmax : v[m] = mode(v[l ..i]), - lc est la longueur de la chane courante (a laquelle appartient V[il). On appellera cet ensemble de definitions ltat du monde (state of the world). Le programme 2.1, qui met en uvre ces ides, est une application du schma suivant : Initialiser TANTQUE NON fini FAIRE avancer FAIT Le programme est comment par la suite. DEBUT DONNEES n: entier; v: TABLEAU [1 ..n] DE entier; VAR i, max, m, lc: entier; i:=l ; Imax:=l ; m:=l ; Ic:=i ; TANTQUE kn FAIRE i:=i+l ; SI V[i] = V[i-l ] ALORS IC:=I~+~; SI blmax ALORS Imax:=lc; m:=i FINSI SINON Ic:= 1 FINSI FAIT FIN Programme 2.1. Le mode dun vecteur

monde (i, lmax, m, lc), la phase dinitialisation sert leur donner des valeurs permettant de dmarrer. Cette ligne correspond au traitement du premier lment : la chane maximum est la chane courante, de longueur 1. Dans le TANTQUE, comme i est le nombre dlments dja traits, le test de la fin est bien icn (autrement dit, il reste des ellments a considrer). Pour avancer, on prend le prochain element (i:=i+l), en se demandant si lexamen de cet Blment met en cause ltat du monde. Il y a deux CaS : soit on allonge la chane courante (V[i] = V[i-l]), soit on commence une nouvelle chane. La nouvelle chane, dcrite dans la partie SINON, est de longueur 1 (1~~1) et ne met pas en cause lmax ou m (qui ne sont donc pas modifis). Si la chane courante a t allonge (partie ALORS), on augmente lc (lc:=lc+l) avant de tester si la chane courante est devenue la chane maximale (SI lolmax). Dans ce cas, hnax et m sont mis a jour (hnax:=lc; m:=i). Nous prtendons que cette construction dmontre la justesse du programme donn. La dmonstration depend de lacceptation de la rcurrence ( partir du cas i-l on cr6e le cas i, le cas 1 tant trait dans linitialisation) et de la validit de ltat du monde. En particulier, dans la boucle, avancer est fait par i:=i+l et les autres instructions remettent les ClCments de ltat du monde jour en fonction de ce que Ion y trouve. Pour tre complet, revenons sur la confirmation du nouvel tat du monde. i a avanc de 1, ce qui est correct. lc a pris une valeur de 1 (nouvelle chane) ou de lc+l (allongement de la chane en cours). La chane courante devient la chane la plus longue si lolmax. Dans ce cas, lmax et m reoivent des valeurs appropries. Comme tous les lments de ltat du monde ont des valeurs correctes, le programme entier est juste. 2.1.2. Remarques mthodologiques

La construction dun programme correct dpend de la logique employee par son c&teur. Cette logique ne peut sexercer que si les bases dclaratives sont saines. Cest ici quune bonne formalisation de letat du monde est fondamentale. Cet tat du monde peut tre dcrit en franais (ou en anglais ou en polonais, nous ne sommes pas racistes) ou en style mathmatique. Le choix entre les deux (ou les quatre) styles est une question de got et de type dapplication. Mais, que ltat du monde soit crit en franais ou en formules mathmatiques, il se doit dtre prcis. De nombreux programmeurs agrmentent leurs programmes avec des commentaires du type Y est lindex de llment de Y. Ils font souvent lerreur classique de confondre le dernier lment traite avec son suivant (celui qui va tre mit).

Les donnes du problme sont n, la longueur du vecteur, et le vecteur ordonn, v Les deux sont initialiss par ailleurs. Aprs les dclarations des objets de ltat du
22

23

.&GORlTHMIQUEETPROGRAhM4TION

&GORITHMIQUE ET PROGRAhthUTlON

Lutilisation de tableaux et de boucles impose dautres contraintes. En particulier, il faut dmontrer la terminaison de chaque boucle et confirmer que lindex de chaque rc?f&ence a un lment de tableau est entre les bornes (i doit tre contenu en [l..nl). Pour la terminaison de la boucle, il ny a pas de probleme ici. La variable de contrle i est augmentee de 1 chaque tour de la boucle, et arrivera donc n pour arrtfx le massacre. Les seules n5ferences a des lements de tableaux sont dans le test SI v[i]=v[i-11. i commence a un et la boucle sarrte quand i=n. Avant laffectation i:=i+l lintkieur de la boucle, on a donc lingalite suivante : I~i43 Apres laugmentation de i, ceci devient : 1 <isn On voit facilement que i et i-l sont tous les deux linterieur du domaine l..n. Cette solution du problbme, dune grande simplicit, est correcte, mais elle nest pas la meilleure possible. Il existe une variante qui est moins longue, qui utilise moins de variables, et qui sexcute plus vite. Il est mme surprenant que lamlioration 6chappe a la quasi-totalit des programmeurs. Nous y reviendrons dans un paragraphe ultrieur (cf. 7.1). Par ailleurs, la solution donne comporte une faiblesse. En gnral, il y a au moins un Ctudiant, ou une tudiante, dans la classe qui reste suffisamment n%eill(e) pour poser la question suivante : Quest ce qui se passe si deux chanes sont de mme longueur, cest--dire si deux modes se prsentent ?. Bonne question ! La rponse peut tre de prendre nimporte lequel des modes possibles, mais la faiblesse rside dans le fait que notre spcification du problme ne parlait pas de cette possibilit. On voit quil nest pas simple de bien spcifier des algorithmes, surtout quand il faut tre complet. 2.2. Recherche dun objet Dans ce nouveau programme, il sagit de trouver lemplacement quoccupe un objet (disons un entier) dans un tableau. Cest une opration mene frquemment dans des programmes de tous types. Supposons en premier lieu que nous ne savons
24

rien du tableau, cest--dire quil faut en examiner tous les lements pour dmontrer labsence eventuelle de lobjet recherche. Bien sr, le programme sarr&era ds que lobjet est trouv6. Par la suite, nous montrerons comment rendre lalgorithme plus efficace en introduisant des connaissances concernant la forme des donnes. 2.2.1. Recherche linaire Lalgorithme le plus simple examine tour de rle chaque lment jusqu la r6ussite de la recherche ou lpuisement des candidats. Il ncessite ltat du monde suivant : DONNEES n: entier, le nombre dlments de 1, i: TABLEAU [l ..n] DE entier, le tableau examiner, objet: entier, lobjet trouver, VARIABLES i: entier, le nombre dlments dj examins, trouv: bool, trouv z t[i]=objet. Le programme 2.2 est une mise en uvre possible.

DONNEES n, objet: entier; t: TABLEAU [l ..n] DE entier; PRECOND n>O DEBUT VAR i: entier, trouv: bool; i:=O; trouv:=FAUX; TANTQUE kn ET NON trouv FAIRE i:=i+l ; trouv := t[i]=objet FAIT POSTCOND (trouv ET objet=t[i]) OU (NON trouv ET i=n) FIN Programme 2.2. Recherche dun objet dans un vecteur Le programme est encore une application du schma suivant : Initialisation TANTQUE NON fini FAIRE avancer FAIT
25

hOORITHMIQUE! GT PROGRA~ION

fiLGORITHhtIQUEETPROGRA-ION

Linitialisation de ltat du monde dit que, le programme nayant rien vu (i:=O), lobjet na pas encore t trouv (trouv6:=FAUX). Pour avancer, on augmente i de 1 et on remet trouv a jout Le processus peut terminer soit par la russite (trouve devient VRAI), soit par linspection du dernier lment (i=n). Notons que les deux conditions peuvent tre vrifies en mme temps, car lobjet peut se trouver en t[nl. La boucle termine, car i augmente chaque tour. Lindex de la seule rfrence tri] est juste, car linitialisation et la condition de la boucle font que k-icn a lentre dans la boucle. Avec laugmentation de la valeur de i, ceci devient Cki<n au moment de la nSf&ence, cest--dire I&n. Dans ce programme, nous avons introduit deux nouvelles notions : la prcondition ~ptecondition) et la post-condition Cpostcondition). La prd-condition est ce qui doit tre vrai pour que le programme soit excutable. Ici, il faut quil y ait au moins un lment dans le tableau. Notons en fait que, si cette condition ntait pas vrifiCe, par exemple n=O, le programme dirait que lobjet est absent, mais ceci est un hasard. La post-condition est ce que lon sait la fin du programme. Elle doit servir de spdcification du rsultat. Prenons lexemple dune procdure de calcul dune racine carree. On aurait la structure suivante : DONNEE x: rel; PRECOND ~20; Procbdure de calcul de y=sqrl(x) POSTCOND y y = x (en gnral, epsilon prs)
l

Que sait-on sur ce programme ? Une premiere dduction, vidente, est que, comme le programme a quitte la boucle, la condition de continuation de la boucle est maintenant fausse. Ainsi, apres FAIT, on peut dduire : NON (icn ET NON trouv). Par application de la loi de Morgan, ceci devient : irn OU trouv. Comme i navance que dun pas la fois, on peut dduire : i=n OU trouv. Les OU de linformatique tant inclusifs, les deux clauses peuvent tre vraies en mme temps (cas de t[n] = objet). Les post-conditions du programme donn suivent logiquement, en considrant laffectation la variable trouv dans lintrieur de la boucle. En gnral, on peut souhaiter que la post-condition se retrouve partir de la pr-condition, de dductions du type donn ci-dessus et des dfinitions de ltat du monde. Mais la post-condition que nous avons jusquici est incomplte. Quand lobjet a t trouv, i indique bien son emplacement, mais la non-russite est mal dcrite. Nous navons pas encore expliqu que NON trouv veut dire que lobjet est absent. Lerreur, classique, a t de construire les pr- et post-conditions aprbs coup. Il aurait fallu spcifier le programme avant de lcrire, les conditions servant de spcification. Dans notre cas, on aurait : PRECOND Soient un objet et un vecteur t de longueur n, n>O; POSTCOND Ou t[i]=objet, ou il nexiste pas i, O&n, tel que t[i]=objet. Notons que la variable trouv ne figure plus dans la post-condition. Elle sert distinguer les deux cas de solution. Il faut maintenant dmontrer la vrit de cette nouvelle post-condition. Quand lobjet a t trouv, la logique prcdente est suffisante. Pour montrer labsence de lobjet, il nous faut une clause de plus. Reprenons le texte avec de nouvelles dcorations :

On sait calculer les racines carrees des nombres non ngatifs (pr-condition) et le rt%ultat est un nombre dont le carre est la donne dorigine (post-condition). Dans notre programme de recherche linaire, la pr-condition est raisonnable, mais la post-condition nest pas entirement satisfaisante. Elle comporte deux lacunes : elle nest pas directement dductible du texte et le sens de la variable trouve nest pas bien dfini, mme si le programme est juste. Regardons maintenant le processus de dduction. Reprenons le programme avec sa pr-condition, mais sans post-condition pour le moment : PRECOND n>O; DEBUT VAR i: entier, trouv: bool; i:=O; trouv:=FAUX; TANTQUE kn ET NON trouv FAIRE i:=i+l ; trouv := objet=t[i] FAIT FIN 26

27

DONNEES objet, n: entier; t: TABLEAU [l ..n] DE entier; P R E C O N D n>O; DEBUT VAR i: entier, trouv: bool; i:=O; trouv:=FAUX; TANTQUE kn ET NON trouv FAIRE INVARIANT O<jsi => t[j] f objet; i:= i+l; trouv := t[i]=objet FAIT POSTCOND t[i]=objet OU i (O&n => t[i]#objet) FIN

i:=l ; TANTQUE ig ET t[i]+objet FAIRE i:=i+l FAIT

Linvariant dit que la boucle nest excute que si lobjet na pas encore t trouv. La post-condition peut maintenant tre dmontre. En effet, Zi la terminaison de la boucle, si trouvb est FAUX, alors i=n ET t[i] nest pas lobjet. Mais avant dexcuter linstruction i:=i+l, aucun tu], O<jii, ntait lobjet. Aprs laugmentation de i, si t[i] nest pas lobjet, linvariant reste confirm. Quand i=n, la deuxikme partie
de la post-condition est correcte.

Malheureusement, ces deux programmes comportent une faiblesse qui ne se montrera quavec certains compilateurs. Supposons, dans la demikre version cidessus, que lobjet nest pas prsent dans le vecteur. La boucle tourne pour la dernire fois avec i=n. Lexcution de la boucle augmente la valeur de i, donnant i=n+l. On teste de nouveau avec TANTQUE. La premire partie de la condition (iln) est fausse, mais on peut quand mme en valuer la deuxime partie afin de disposer des deux oprandes de loprateur ET. Cette deuxime partie comporte une rfrence t[i], cest--dire a t[n+l]. Or, cet lkment nexistant pas, le programme peut terminer par lerreur index en dehors des bornes. Cette faiblesse peut sliminer avec lintroduction de loprateur ETPUIS (CAND), qui ordonne les tests. Ainsi, on pourrait Ccrire :
TANTQUE ig ETPUIS t[i]+objet

Lutilisation dassertions telles que les invariants de boucles permet darriver des preuves formelles de programmes. En pratique, la preuve complkte dun programme de taille industrielle savre longue et coteuse. Mais ce nest pas une raison pour ltudiant de ne pas connatre ces techniques. Une familiarit avec les bases des preuves de programmes est une des cls de lamlioration de la performance dun programmeur. Il crke de la sorte des schmas mentaux qui font quil analyse ses problmes de manire plus rigoureuse, produisant des programmes de meilleure qualit en commettant moins derrwrs. 2.2.2. Un pige Les programmeurs de la gnration prcdente naimaient pas les variables bool6ennes. Ils prfraient crire le programme ci-dessus dans une forme priori plus simple :
i:=O; TANTQUE kn ET t[i+l ]+objet FAIRE i:=i+l FAIT

Quand i>n, cest--dire que le premier oprande est FAUX, on nvalue pas le deuxikme. Cela dpend du fait que (FAUX ET b) est toujours FAUX, quelle que soit la valeur de b. Il existe galement loprateur OUALORS (COR). Les dfinitions de ces oprateurs sont les suivantes :
a ETPUIS b J SI a ALORS b SINON FAUX FINSI a OUALORS b J SI a ALORS VRAI SINON b FINSI

Notons que ces deux dfinitions sont celles qui se trouvent dans beaucoup de livres de logique pour ET et OU, mais ces oprateurs ne sont pas mis en uvre de cette faon dans tous les compilateurs. 2.2.3. La dichotomie Quand on ne sait rien sur les lments dun tableau, pour tablir quune valeur donne ne sy trouve pas, il faut inspecter tous les lments, car la valeur peut figurer nimporte quelle place. Maintenant, nous allons considrer un cas plus intressant, o les lments du tableau sont ordonns. Cest comme laccs un annuaire tlphonique. Pour trouver le numro de M.Dupont, on ne regarde pas toutes les entres de A DUPONT, On procde par des approximations. Pour nous faciliter la programmation, nous allons procder par des approximations simples. Dans un annuaire de 1000 pages, on regarde la page 500.
29

En supprimant la variable trouv, le test de russite se fait sur le prochain lment t[i+l]. Afin dviter le calcul de lindex, on peut redfinir i comme lindex de Glment traiter et non plus llment qui vient dtre trait :

28

ALOORIlNh4IQUE

ET PROGRAMMtUlON

&GORIITMQUE

I ? I - PROGRAMMtWON

Si llment recherch est alphabtiquement plus petit, on a restreint la recherche aux pages 1 499, ou, sil est plus grand, aux pages 501 a 1000. Chaque regard coupe le domaine de recherches en deux. Les recherches sarrtent si llment examine est le bon, ou si le domaine de recherches est devenu nul (lobjet est absent). Considrons une Premiere version de ce programme, avec ltat du monde suivant : bas, haut: entier, SI t[iJ=objet ALORS bas s ii haut centre: entier, t[centre] est Ielment examiner trouv: boolen, trouv j t[centre]=objet Appliquons comme dhabitude le schma : Initialisation TAfiTQUE NON fini FAIRE avancer FAIT Le programme 2.3 est une solution possible. DONNEES n: entier, t: TABLEAU [l ..n] DE entier; PRECOND n>l ET (O&jg => t[i]s[i]) DEBUT VAR bas, haut, centre: entier, trouv: bool; bas:=1 ; haut:=n; trouv:=FAUX; TANTQUE haut-bas>1 ET NON trouv FAIRE centre:=entier((haut+bas)/2); CHOIX t[centre]cobjet: bas:=centre, t[centre]=objet: trouv:=VRAI, t[centre]>objet: haut:=centre FINCHOIX FAIT; SI NON trouv ALORS SI t[bas]=objet ALORS centre:=bas; trouv:=VRAl SINON SI t[haut]=objet ALORS centre:=haut; trouv:=VRAl FINSI FINSI FINSI FIN POSTCOND trouve => t[centre]=objet, NON trouv => i, O&n, t[i]#objet Programme 2.3. Recherche par dichotomie
30

La pr-condition exprime le fait que nous disposons dun vecteur ordonn dau moins deux lments. Linitialisation indique que le domaine de recherches est t[l..n], lobjet ntant pas encore trouv. La condition apres TANTQUE mrite des explications. La proposition NON trouv est vidente, mais la partie avant le ET lest moins. Pour considrer une valeur intermdiaire, nous imposons quelle soit diffrente des deux extrmes, cest--dire que linegalit suivante soit vraie : Proposition A. bas < centre e haut

Cette inegalit ncessitant lexistence dau moins une valeur entre bas et haut, on retrouve : Proposition B. haut - bas > 1

Dans la boucle, le calcul de la valeur de centre ncessite une conversion, par la fonction entier, du rsultat de la division, qui est un nombre rel, en un nombre entier. On confirme facilement que centre est bien entre haut et bas en appliquant la proposition B ci-dessus. Quand la somme (haut + bas) est impaire, la division par deux donne un nombre relle de la forme n,5. Que larrondi vers un entier donne n ou n+l na aucun effet sur lalgorithme (les deux possibilits respectent la proposition A). Par la suite, la clause CHOIX force un choix entre les trois possibilits ouvertes aprs comparaison de lobjet avec t[centre]. Ou lobjet a t trouv (tkentre] = objet), ou le domaine de recherches est coupe en deux (bas:=centre ou haut:=centre, suivant la condition). Si lobjet est trouv, tout va bien. Si la boucle se termine sans trouver lobjet, haut et bas sont maintenant deux indices successifs : haut = bas + 1 La derniere partie du programme teste si lobjet se trouve en t[haut] ou en @as]. Ce test est irritant pour le programmeur. Il nest utile que si t[l]=objet ou t[n]=objet, car ds que haut ou bas change de valeur, ils prennent celle de centre, t[centre] ntant pas lobjet. Donc le test ne sert que si lune des variables haut ou bas a garde sa valeur initiale. En fait, le test est d a une faiblesse de spcification. Il faut dcider si oui ou non t[haut] ou t[bas] peuvent contenir lobjet, et cela de maniere permanente. Essayons deux nouvelles versions du programme. La Premiere respecte les initialisations de loriginal, ce qui veut dire que t[haut] ou t[bas] peut toujours contenir lobjet :

31

hGORlTHMIQUEETPRWRAhtMTION

hCiORIll+MIQUE ET PROGRAMMMION

DONNEES n: entier, t: TABLEAU (1 ..n] DE entier; PRECOND n>l ET O&jg => t[ikt[i] DEBUT VAR bas, haut, centre: entier, trouv: bool; bas:=1 ; haut:=n; trouv:=FAUX; TANTQUE haukbas ET NON trouv FAIRE centre:=entier((haut+bas)/2); CHOIX t[centre]<objet: bas:= centre + 1, t[centre]=objet: trouv:= VRAI, t[centre]>objet: haut:= centre - 1 FINCHOIX FAIT FIN POSTCOND trouv => t[centre]=objet, NON trouv => i, O&n, t[i]+objet Programme 2.4. Dichotomie,
version 2

Al. O&bas => t[i].cobjet A2. haut&n => t[i]>objet La condition de terminaison bas > haut montre alors labsence de lobjet dans t. Cela correspond la deduction que lon peut faire la sortie de la boucle : NON (haut 2 bas ET NON trouv) Cest--dire : bas > haut OU trouv bas > haut est la condition dabsence, trouv implique t[centre] = objet. La deuxime bonne solution notre problme est de faire en sorte que ni t[bas] ni t[haut] ne peut tre lobjet. Considrons la version du programme 2.5. DONNEES n: entier, t: TABLEAU [i ..n] DE entier; PRECOND n>l ET O&jg => t[ikt[i] DEBUT VAR bas, haut, centre: entier, trouv: bool; bas:=O; haut:=n+l ; trouv:=FAUX; TANTQUE haut-bas>1 ET NON trouv FAIRE centre:=entier((haut+bas)/2); CHOIX t[centre]<objet: bas:=centre, t[centre]=objet: trouv:=VRAI, t[centre]>objet: haut:=centre FINCHOIX FAIT FIN POSTCOND trouv => t[centre]=objet, NON trouv => i (O&n => t[i]#objet) Programme 2.5. Dichotomie, version 3 Dans cette version, seules les initialisations de bas et haut ont t modifies. Ces variables prennent des valeurs dindices inexistantes. Mais ce nest pas grave, car les Kments correspondants ne seront jamais rfrencs. La preuve de cette version du programme est facile. Elle est la mme que celle de la version originale, sauf pour Itive a la situation : haut = bas + 1

Trois changements figurent dans cette version du programme par rapport loriginal. Dans le choix, quand t[centre] nest pas lobjet, la nouvelle valeur de bas (ou haut) ne doit pas se situer sur le centre, mais un pas plus loin, sur le premier candidat possible (centre est le dernier lment rejet). En plus, dans le TANTQUE, au lieu de proposition B, nous avons simplement : haut 2 bas Quand la proposition B est vraie, la situation na pas change par rapport la version originale. Quand haut=bas, centre prend cette mme valeur et lon teste le dernier candidat. Si ce nest pas le bon, on ajuste haut ou bas, avec le rsultat : bas > haut Quand haut suit immdiatement bas, centre va prendre une valeur qui est soit celle de haut, soit celle de bas (il ny a pas despace entre les deux). Mais, grce au fait que bas, OU haut, prend sa nouvelle valeur un cran plus loin que le centre, an prochain tour de la boucle, on aura : bas = haut ou bas > haut

La boucle termine toujours. La preuve de la correction de ce programme peut se faire partir des assertions suivantes :

32

33

kOORIlWMIQUE

F?I- PROGRAMMATION

/UOO-QUE

ET PROGRAhGfA-MON

Dans ce cas, comme ni t[haut], ni t[bas] nest lobjet, celui-ci nest pas dans t, car il ny a pas dlment entre t[haut] et @as]. La technique consistant a utiliser comme bute une valeur inexistante (ici en considrant [O..n+l] au lieu de [l..n]) est utilise frquemment par de bons programmeurs. Notons quil na pas t ncessaire de crer rellement les lments fictifs introduits.

Dans le jargon de linformatique, on parle de la r6duction dun problme en n vers un problme en n-l (recherche linaire), ou vers un problme en n/2 (dichotomie). Par la suite, dans le chapitre 3 sur les tris, nous verrons que dans certains cas, on rduit un problme en n vers deux problbmes en n/2. On appelle cette demiete technique lart de diviser pour rgner (divide and conquer). Nous tirons deux conclusions de cette brve discussion. La premire est que des connaissances minimales sur le calcul de la complexit des algorithmes sont necessaires si lon veut pratiquer de linformatique B un bon niveau. Le sujet, assez difficile, est trs tudi par des chercheurs. Ces recherches demandent surtout des comptences leves en mathmatiques. Mais on peut faire des estimations utiles avec un bagage limite. La deuxieme conclusion concerne lefficacit des programmes. On voit souvent des programmeurs schiner sur leurs programmes pour gagner une instruction ici ou l. Evidemment, dans certains cas prcis, cela peut devenir ncessaire, mais cest rare. Le gain defficacit est limit. Mais le probleme nest pas le mme au niveau des algorithmes, comme lattestent les chiffres de la table 2.1. Des gains defficacit travers lalgorithmique peuvent tre importants. Il nest pas tres sens doptimiser un mauvais algorithme - mieux vaut commencer avec un bon. Loptimisation locale peut se faire par la suite en cas de besoin.

2.3. De la complexit des algorithmes Le parallle avec lannuaire tlphonique montre que les performances de ces deux algorithmes (recherche lin6aire et recherche dichotomique) sont trs diffrentes. Leur complexit est facile tablir. Pour la recherche linaire : - Si lobjet est prsent dans le tableau, il peut tre nimporte o. En moyenne, le programme examine n/2 lments avant de trouver le bon. Si lobjet est absent, on examine tous les n lments. La complexit de lalgorithme est donc de o(n). Cela veut dire que si lon doublait le nombre dl6ments, les recherches dureraient en moyenne deux fois plus longtemps. La dichotomie est tout autre. Ici, un doublement de la taille du tableau ncessite un seul pas supplmentaire (chaque examen dun lment divise la taille du domaine de recherche par deux). Considrons le cas dun tableau de 2k lments. Apres un pas, on a 2k-1 lments a considrer, aprs deux pas, 2k-2, . . . . aprs k pas, un seul lment (20). La dichotomie a donc une complexit de o(log2 (n)). La diffrence entre ces deux courbes est trs importante, surtout quand le nombre dlments est grand. La table 2.1 compare le nombre maximum de pas (n) dans le cas de recherche linaire avec le nombre maximum de pas avec la dichotomie. n 10 100 1000 1 000 000 dichotomie 4 7 10 20 (24= 16) (2= 128) (2O= 1024= l k ) (2O = 1024k = 1 048 576)

2.4. Rsum des principes introduits Au cours de ce chapitre, nous avons introduit plusieurs principes importants, qui forment la base de notre technique de programmation. Nous travaillons souvent B partir dun schma de programme (proscheme). Cest une maquette qui indique la structure gnrale. Le seul schma utilise jusquici est celui dun processus linaire : Initialiser TANTQUE NON fini FAIRE avancer FAIT On complte le schma linaire en utilisant un tat du monde, qui est une dfinition prcise des variables. Cet tat permet de confirmer la justesse du programme en lexploitant comme une liste a cocher en @onse des questions du type suivant : - Est ce que ltat du monde est completement initialis avant la boucle ? - Quel est leffet de lopration avancer sur chaque lment de ltat du monde ? Lexistence dune definition prcise des variables facilite la dfinition de la
34 35

Table 2.1. Comparaison entre la recherche linaire et la dichotomie

&OORITEIMIQUFi

ET PROGRAMMtWION

&GORITlMQUE

EI- PROGRAMMtWION

condition de terminaison. De mme, expliciter la notion davancer diminue la probabilit de lcriture de boucles infinies. Nanmoins, pour viter cette msaventure, on dmontre consciemment (et consciencieusement) la terminaison de chaque boucle. Le r6flexe de dmonstration de validit doit aussi jouer chaque fois que lon repre un lment de tableau. On dmontre que les indices sont n&essairement entre les bornes. Nous avons utilis les notions de pr-condition et post-condition. La prcondition indique les limitations du programme, cest-a-dire les caractristiques des donnes en entre. Elle sert pour des dmonstrations de correction, mais aussi pour la documentation dun programme. Avec la pr-condition, un utilisateur eventuel peut confirmer quil a le droit dappeler le programme avec les donnes dont il dispose, La post-condition indique ce que le monde extrieur sait aprs lexcution du programme. On doit pouvoir remplacer tout programme par nimporte quel autre qui respecte les mmes pr-condition et post-condition, sans que Iutilisateur ventuel sen rende compte. Une partie difficile du processus de la mise en uvre est la spcification du programme. Mais le travail fait a ce niveau est payant. En effet, le cot dune erreur augmente avec le temps quelle reste prsente. Mieux vaut passer un peu plus de temps en dbut du processus que de payer trs cher, plus tard, llimination des erreurs. Pour dmontrer la correction dun programme, on utilise des assertions et des invuriants. Les assertions sont des formules logiques qui sont vraies aux endroits o elles figurent dans le programme. Un invariant est une assertion qui est vraie chaque tour dune boucle. Une boucle est compltement dfinie par son tat du monde et son invariant. Certains auteurs incluent ltat du monde dans linvariant. Les deux techniques sont quivalentes. 2.4.1. Un apart! sur les preuves de programmes Les techniques rsumes ci-dessus reprennent des notions manant des travaux sur la preuve de programmes. Le but est dimprgner les cerveaux des tudiants de m&nismes mentaux allant dans cette direction, sans passer a une approche trop rigoureuse pour tre soutenue dans la pratique. On doit savoir pourquoi le programme marche, sans avoir explicit tout le dveloppement mathmatique. Pour le puriste, ou le mathmaticien, cette approche nest pas satisfaisante. Il serait normal - dans leur monde idal - que tout programme soit accompagn dune preuve formelle. Ce sont les impratifs conomiques qui font que le monde nest pas idal, surtout en acceptant les capacits et les motivations des programmeurs. 36

Le style present est donc un effort de compromis, matrisable par les etudiants dont nous disposons tout en les guidant. Au cours de leurs travaux dirigs, ils menent a bien au moins une preuve formelle complte afin de comprendre les outils sous-jacents. 2.4.2. Le styLe Cet prsents ensemble problme dcriture

ouvrage sadresse aux problmes algorithmiques. Aucun des programmes ne dpasse la taille dun module dans un programme complet. La mise dunits de programme pour la construction dun produit industriel est un aborde ailleurs (cours de gnie logiciel, projets).

Mais il ne faut pas perdre de vue cette question de modularit. Lutilisation de la clause DONNEES, avec les pr-conditions et post-conditions, vise, parmi dautres buts, prparer la dfinition de modules, avec leurs spcifications, interfaces et corps. En pratique, dans le cours enseign, ces notions forment la matibre de discussions, prparant ainsi le travail en profondeur venir.

2.5. Adressage dispers Les premiers programmes dans ce chapitre sont des exemples simples, introduits pour des raisons pdagogiques. Mais, en mme temps, nous avons examin des mthodes de recherche dun lment dans un vecteur. Dans une premire liste dalgorithmes de ce type, il faut introduire celui de ladressage dispers (hash code), qui est frquemment utilis dans la gestion, dans les compilateurs ou dans lintelligence artificielle. La mthode a t invente pour acclrer des recherches de positions joues dans le jeu de dames [Samuels 19591. Supposons que nous voulons crker un annuaire tlphonique au fur et mesure de larrive de numeros connus, sans faim de tri chaque arrive. Cest ce que lon fait habituellement dans un carnet dadresses. Dans un tel carnet, pour viter davoir examiner tous les noms de personnes, on les divise en 26 classes, en fonction de la premire lettre du nom. Dans le carnet, on commence une nouvelle page pour chaque lettre. Les recherches vont plus vite parce que les comparaisons ne sont faites quavec les noms ayant la mme premire lettre. La premire lettre sert ici de clk (key). Une clC est une fonction des caractres constituant le mot qui sert diviser lensemble de mots possibles dans des classes. Si les noms Ctaient distribus de manire gale entre les classes, on divise le nombre de comparaisons par le nombre de classes. Pour un carnet, on ne regarde quun nom sur 26. Notons que ce rendement nest pas atteint en pratique, parce que, par exemple, les pages K, X, Y, . . . contiennent moins de noms que certaines autres. 37

.&3ORlTHMIQUE

El- PROGRAMhHMON

hGORlTRMIQLJE

El PRoGRAhftWDON

Ladressage dispers est une mise en uvre du principe du carnet, avec quelques changements en raison des caractristiques des ordinateurs. En particulier, un carnet comporte beaucoup de lignes vides sur les pages moins remplies. Nous dcrivons deux versions de lalgorithme dadressage dispersk, une premihre avec un nombre de ~16s plus petit que la taille de la mmoire disponible, et une deuxime o le nombre de cl& est le mme que le nombre de cases disponibles.
2.51.

Dans ce tableau, les 26 premires cases sont r&ervCes pour le premier nom reu de chaque classe (en supposant que la cl est la premikre lettre). La colonne de droite contient lindex de lentrt5e contenant le prochain nom de mme cl. Un successeur (suivant) dindex -1 indique que le nom dans cette entre est le dernier rencontr dans sa classe (il na pas de successeur). Une case vide a galement -1 comme successeur. Les noms comportent 8 caractres, tant compltks par des espaces. Une case vide comporte 8 espaces. La figure montre ltat de la table aprs la lecture des noms suivants : X, DUPONT, TOTO, Y, DURAND, TINTIN, DUPOND. A la rception dun nom, on calcule sa cl i, ici la premire lettre. Diffrents cas se pdsentent : - Le nom est la premier occurrence dun nom avec cette cl. Alors, la case dindex i est vide. Le nom sinsre a cette place et le processus est termin. - Un nom de cl i a dj t rencontr. On compare le nouveau nom avec celui dindex i dans la table. Si les deux sont identiques, le nom est trouv et le processus se termine. - Si les deux noms sont diffrents, il faut examiner les successeurs ventuels comportant la mme cl. Un successeur de valeur - 1 indique que la liste est termine. On ajoute le nouveau nom la premire place disponible et le processus est termin. - Si le successeur existe, son index est donn dans la colonne correspondante. Oncomparelenouveaunomavecle successeur,enseramenantaucaspr&&nt. Cela donne lalgorithme du programme 2.6, page ci-aprs, appel larrive de chaque occurrence dun nom. A la fin de cet algorithme, la variable adresse contient lindex du nom dans la tabIe. La variable pl indique la premire case de dbordement libre dans la table (avec la valeur de 27 au dpart de lalgorithme). Lalgorithme ne traite pas le problme dun dbordement ventuel du tableau.

Algorithme avec chanage

Dans cet algorithme, le nombre de cl& est plus petit que le nombre de cases disponibles en mmoire. Considrons le carnet, o il y a 26 cls. On cre un tableau du type dond dans la figure 2.2.
Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fi 22 23 CM fi 27 28 29 ..

Nom

DUPONT

Suivant -1 -1 -1 27 -1 -1 -1 -1 -1 -1 1 1 -1 1 -1 1

TOT0

X Y
DURAND TINTIN DUPOND

1 28 1 1 -1 -1 -1 -1 29 -1 -1

Figure 2.2. Table pour ladressage dispers avec chanage


38 39

hGORITHMIQUE ET PROGRAMMMION

DONNEES cl: PROC(chane) -> entier; nom: chane(8); cars: TAB [l ..taille] DE chane(8); SU~C: TAB [l ..taille] DE entier; DEBUT VAR i, adresse: entier; trouv: bool; pl: entier INIT 27; i:=cl(nom); S I carqJ= % complt par des espaces % ALORS cars[i]:=nom; adresse:=i; trouv:=VRAI SINON trouv:=FAUX; TANTQUE NON trouv FAIRE SI nom = cars[ij ALORS adresse:=i; trouv:=VRAI SINON SI SUC~[~ = -1 ALORS cars[pl]:=nom; % avec des espaces % succ[i]:=pl; succ[pl]:= - 1; adresse:=pl; trouvb:=VRAI; pl:=pl+l SINON i:=succ[i] FINSI FINSI FAIT FINSI FIN Programme 2.6. Adressage dispers 2.5.2. Autant de cls que de cases Ltablissement dun chanage entre les diffrents noms dune mme cl gaspille de la mmoire. Pour viter cette dpense, on peut choisir une fonction qui donne autant de cls que de cases dans le tableau. En restant avec notre cl (peu raliste) de la premibre lettre, on peut refaire la table de la figure 2.2 pour obtenir celle de la figure 2.3.

Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

Nom

DUPONT DURAND DUPOND

TOT0 TINTIN

X Y

Figure 2.3. Table sans zone de dbordement

Cette figure reprend la situation de la figure 2.2. On note que les noms supplmentaires commenant par D ont pris les places des noms commenant par E et F. Que se passe-t-il alors si lon rencontre le nom ESSAI ? On comparera ESSAI avec DURAND (case 5), puis avec DUPOND (case 6), avant de dcouvrir que la case 7 est vide. ESSAI rentrera dans la case 7. La cl sert tablir un point de dpart des recherches, donnant ainsi lalgorithme du programme 2.7.

40

41

fiLGORITHhtIQUJ2

I?I PROGRAMMUYON

A L G O R I T H M I Q U E ET PROGRAhSIbGWON

DONNEES cl: PROC(chane) -> entier; nom: chane(8); tab: TAB [ 1 ..taille] DE chane(8); DEBUT VAR i, adresse: entier; trouv: bool; i:=cl(nom); trouv:=FAUX; TANTQUE NON trouv % Case vide % FAIRE SI tab[fl = ALORS trouv:=VRAI; tab[i]:=nom SINON SI tab[i]=nom ALORS trouv:=VRAI SINON SI i=taille % Dernire case du tableau % ALORS i:=l % Recommencer en haut % SINON i:=i+l FINSI; SI i=cl(nom) ALORS table pleine FINSI FINSI FINSI FAIT FIN Programme 2.7. Adressage dispers, version 2

garde ses qualits jusquau remplissage du tableau, au prix dune occupation de mmoire suprieure. Rappelons que les deux algorithmes ncessitent une fonction qui distribuent bien les noms parmi les cls. Si cette distribution est mauvaise, les deux algorithmes se rapprochent de lalgorithme de recherche linaire. Notons finalement que les algorithmes dadressage dispers ne diminuent pas la complexit thorique des recherches par rapport celle des recherches linaires. Lamlioration de cette mthode rside dans la diminution de la constante dans le formule. Toute la famille est dordre o(n) pour la recherche dun nom.

2.6.

Exercices

1. Avant de passer au chapitre qui donne la solution, chercher lamlioration du programme de recherche dun mode de vecteur. Ce nouveau programme est plus court et plus efficace que lancien. En particulier, il comporte une variable de moins et un test (SI ALORS . . . ) de moins. 2. Considrons une nouvelle version du programme de dichotomie, crit dans le style sans boolens : DEBUT VAR bas, haut, centre: entier; bas:=1 ; haut:=n; centre:=entier((n+l)/2); TANTQUE haut>bas ET t[centre]+objet FAIRE SI t[centre]cobjet ALORS bas:=centre SINON haut:=centre FINSI; centre:=entier((haut+bas)/2) FAIT; SI t[centre]=objet % cest trouv % ALORS . . . % absent % SINON . . . FINSI FIN Cette version, qui est souvent propose par des lbves, contient un pige. Lequel ? Comme mise sur la voie, on considrera le problkme de la terminaison de la boucle.

La table doit tre circulaire (on recommence regarder en haut si lon arrive B la fin). Si la table est pleine, la recherche dun nouveau nom fait tout le tour du tableau. Sauf si la table est pleine et le nom est absent, la fin du programme, i contient lindex du nom dans le tableau. 2.5.3. Choix de cl et efficacit Jusqualors, nous avons utilis comme cl la premire lettre du nom. Cette cl nest pas la meilleure dans la plupart des cas. Le choix de la bonne cl dpend des caractristiques des noms que lon va rencontrer. Dans les compilateurs, on utilise souvent comme cl la somme des reprsentations internes des caractres (code ASCII ou EBCDIC) modulo la taille de la table. La taille choisie est habituellement une puissance de deux afin de calculer le modulus par dcalage sur les bits. Ce choix est assez bon pour les identificateurs dans des programmes. Il permet dutiliser le deuxi&me algorithme, sans chanage. Le deuxime algorithme est assez efficace tant que la table ne se remplit pas. Son rendement devient mauvais si la table est presque pleine. Le premier algorithme
42

43

&GORlTHMIQUEJTPROGR4MMPUION

3. Mise en uvre dans lordinateur des algorithmes dadressage disperse. On prendra des textes de programmes afin de disposer de suites de noms (identificateurs). En prenant des programmes de grande taille, on peut mesurer lefficacit des diffrents algorithmes de recherche dobjets (linaire, dichotomie, adressage dispers) en dressant des graphiques du temps dexcution contre le nombre de noms lus. On examinera aussi linfluence du choix de la fonction de calcul des cls sur les algorithmes dadressage dispers.

Chapitre 3

Les tris

Ce nest pas la premire fois quun livre sur lalgorithmique et la programmation aborde le sujet des tris (sorting), loin de l. Mais le sujet est essentiel - on ne peut pas sappeler informaticien sans avoir quelques connaissances des algorithmes de base. En plus, la matire est un excellent terrain dentranement. Cest donc sans honte que nous abordons ce chapitre, mme si dillustres pr&lecesseurs ont trac la route. Parmi ceux-ci, accordons une mention particuliere [Knuth 19731, pour un volume tout entier consacr aux recherches et aux tris. Trier un vecteur, cest lordonner. On peut trier des entiers, des rels, des chanes de caractres, . . . Il suffit quil existe une relation dordre entre chaque paire dlements, cest--dire que, si a et b sont des lments, une et une seule des relations suivantes est vraie : ad3 a=b aA

Les axiomes habituels sont vrifiCs : acb <=> bsa (a<b ET b<c) => ad a=b <=> b=a On peut galement dcrter que les lments du vecteur sont tous diffrents, vitant ainsi de considrer 1Cgalit. Cette restriction napporte pas grand-chose pour la programmation.

44

itLGORITHMIQUE

ET PROGRAhhWIlON

fkGORITlIMIQUEETPROGR4hfMATION

3.1. Recherche du plus petit lment Un premier algorithme, que nous ne recommandons pas pour des applications pratiques, consiste en la recherche du plus petit lment restant. Ainsi, au premier tour, on recherche t[min], le plus petit lment en t[l..n]. 11 devient t[l] dans le vecteur tri. Le plus simple est dchanger t[ l] avec t[min], ce dernier arrivant ainsi sa place dfinitive. Le probl8me en n est rduit un problme en n-l, car il reste trier le vecteur t[2..n]. Au deuxibme tour on recherche le plus petit lment en t[2..n] pour lchanger avec t[2], et ainsi de suite. On aurait pu prendre llment le plus grand, en lchangeant avec t[n] . . . Le programme 3.1 montre un schma dalgorithme appliqu a un vecteur dentiers : DONNEES n: entier; t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT MONDE i: entier, t[l ..i] est tri; i:=O; TANTQUE i < n-l FAIRE i:=i+l ; trouver j tel que t[j] est le plus petit lment dans t[i..n] changer(t[i], t[i]) FAIT FIN POSTCOND t[l ..n] est tri, cd i,j (O<i<j<n => t[i]<t[i]) Programme 3.1. Scht%na du tri par recherche du plus petit lment La seule instruction ncessitant une explication est la condition aprks TANTQUE. 11 est ncessaire de trier n-l lments, le dernier, t[n], tant ncessairement sa place la fin (tous les lments qui le prcdent sont plus petits que lui par construction, sauf cas dlments gaux). Reste coder le contenu de la boucle dans ce texte. Ce sera une nouvelle

MONDE k: entier, lindex du dernier lment considr6 dans la recherche du plus petit; min: entier, t[min] est le plus petit lment en t[i..k]; k:=i; min:=i; TANTQUE k<n FAIRE k:=k+l ; SI t[k]<t[min] ALORS min:=k FINSI FAIT change(t[i],t[min]) Programme 3.2. Boucle interne

Cette boucle ne ncessite pas dexplication. Notons nanmoins que le programme accepte lgalit. Si le plus petit lment existe en plusieurs exemplaires, on prend le premier arriv, le restant (ou les restants) tant considr(s) au tour suivant. Le programme 3.3, complet, utilise les mondes dcrits pk%demment. DONNEES n: entier; t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT VAR i, k, min, temp: entier; i:=O; TANTQUE icn-1 FAIRE i:=i+l ; k:=i; min:=i; TANTQUE k<n FAIRE k:=k+l ; SI t[k]<t[min] ALORS min:=k FINSI FAIT; temp:=t[i]; t[i]:=t[min]; t[min]:=temp FAIT FIN POSTCOND t[l ..n] est tri Programme 3.3. Programme complet

boucle (programme 3.2).

46

47

&OORITHMIQUE FI PROGRAMh4ATION

,kGORlTHMIQUE ET PROGRAhiMATION

Lchange a t programm de maniere vidente, avec une variable temporaire. La complexit de cet algorithme est simple calculer. Au premier tour, n-l comparaisons sont ncessaires pour trouver le plus petit lment en t(l..n). Au deuxieme tour il en faut n-2, puis n-3, . . . Le nombre de comparaisons est donc : 1 + 2 + 3 + . . . + (n-l) = n*(n-1)/2 Comme les autres oprations (il y a n-l changes) se font moins souvent, lalgorithme est dordre o(n2). Le nombre de comparaisons est indpendant du contenu du vecteur. 3.2. IX par insertion Dans le programme du paragraphe prcdent, aprs i tours, le vecteur t[l..i] est tri, mais en plus, ses i premiers lments sont dj leur place, cest--dire : O<j<k<i => tfj]<t[k] ick<n => t[i]lt[k] La premiere assertion dit que les i premiers lments sont tris, la deuxime dit que les lments apres t[i] sont plus grands ou gaux ceux dj tris, t[i] tant le plus grand des lments tris. Une nouvelle version du tri ne ncessite pas cette deuxime condition. Aprs i tours, les i premiers lments sont tris. On considre t[i+l]. Cet lment va &e insr sa place en t[l..i+l], ce qui implique, en gnral, la recopie un cran plus loin de chaque lment de t[l..i] qui est plus grand que t[i+l] (voir le programme 3.4).

DONNEES n: entier; t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT VAR i, j, temp: entier; arrt: bool; MONDE i: t[l ..i] est tri; i:=l ; TANTQUE i<n FAIRE i:=i+l ; MONDE j est la position du trou, temp = t[i], arrt E t[j-l]ltemp; temp:=t[i]; j:=i; arrt:= t[j-l]<temp; TANTQUE j>l ET NON arrt FAIRE SI tb-l]>temp ALORS t[j]:=t-11; j:=j-1 SINON arrt:=VRAl FINSI FAIT; t]:=temp FAIT FIN POSTCOND t[l ..n] est tri Programme 3.4. Tri par insertion Au tour i, ce programme fait remonter t[i] sa place, ou plutt il fait descendre les lments de t[l..i-1] qui sont plus grands que t[i], afin de lui laisser sa place. On a donc lide du trou. On enlve llment considrer du vecteur, en le mettant dans la variable temporaire temp. Cela laisse un trou. On regarde le predcesseur du trou. Sil est plus grand que temp, il descend, cest--dire le trou monte. Si le predcesseur nest pas plus grand, ou sil ny a pas de prdcesseur (llment considr est le plus petit vu jusquici), la recherche sarrte, llment en temp trouvant sa place dans le trou. Pour confirmer la validit des indices, notons que la boucle externe impose icn. Apres i:=i+l, on a iln. Dans la boucle interne, j>l (condition) et jli (initialisation, avec j:=j-1 dans la boucle). On dduit : lcjln, donc t[i] et t[j-l] existent.

Les boucles se terminent, car i et j avancent, lun vers n (i:=i+l), lautre vers 1 (i:=j-1). 48 49

hOORlTHMIQlJE ET PROGRAMMtUlON

La preuve de ce programme est faite en dmontrant que le nouvel tlment retrouve bien sa place, ce qui revient a demontrer qu la fm de la boucle interne on a: O<kcj => t[k]stemp j<&i => temp<t[k] Comme les lments Ctaient dj ordonns, et comme le nouveau est compatible avec lordonnancement par les deux assertions ci-dessus, linvariant de la boucle externe (t[ l..i] est tri) reste vrai. On pourrait supprimer la variable boolenne arrt en utilisant loprateur ETPUIS (programme 3.5). DONNEES n: entier; t: TABLEAU [1 ..n] DE entier; PRECOND n>O; DEBUT VAR i, j, temp: entier; MONDE i: t[l ..i] est tri; i:=l ; TANTQUE i<n FAIRE i:=i+l ; MONDE j est la position du trou, temp:=t[i]; j:=i; TANTQUE j>l ETPUIS tjj-l]>temp FAIRE tjj]:=tjj-11; j:=j-1 FAIT; tjj]:=temp FAIT FIN POSTCOND t[ 1 ..n] est tri Programme 3.5. Version avec ETPUIS

On est toujours en ordre O(n*), mais cet algorithme est moins mauvais que le prtktdent (sans pour autant tre recommande dans le cas gn&al). En effet, si le . tableau est deja tri ou presque, le premier algorithme fait le mme nombre de comparaisons que dans le cas dune distribution alatoire, tandis que dans le second, on constate n fois de suite quaucun mouvement nest ncessaire, descendant ainsi lordre o(n). Cet algorithme est donc bon pour des vecteurs que lon sait dj tries, ou presque.

3.3. hi par bulles Bien connu aussi, ce tri a une mauvaise rputation du point de vue de lefficacid, reputation qui nest pas tout a fait justifie. A chaque parcours du vecteur, on compare successivement chaque paire de voisins. Si leur ordre nest pas le bon, on les echange. Une des raisons de la mauvaise rputation de lalgorithme est que certains enseignants montrent la version du programme 3.6, qui est effectivement partkuli&rement inefficace. DONNEES n: entier; t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT VAR fin, i: entier; MONDE fin: t[l ..fin] reste trier, fin:=n; TANTQUE fin>1 FAIRE MONDE i: on va comparer t[i] et t[i+l]; i:=l ; TANTQUE icfin FAIRE ASSERTION O<j<i => tjj]<t[i]; SI t[i+l]ct[i] ALORS change(t[i],t[i+l]) FINSI; i:=i+l FAIT; ASSERTION O<j<fin => tjj]<t[fin]; fin:=fin-1 FAIT FIN POSTCOND t[l ..n] est tri. Programme 3.6. Tri par bullesprimitif

On peut aussi faire la mme chose avec une bute (voir exercice la fin du chapitre). La complexit de cet algorithme dpend du nombre dlments qui descendent B chaque tour. Avec une distribution alatoire, la place de llment considere va tre en moyenne au milieu des autres. Ainsi, le nombre dlments dplacer est : (1 + 2 + 3 + . . . + n-l)/2 = n*(n-1)/4
50

51

&GORITHMIQIJE

ET PRoGRAMhUTION

iiLL3ORlTHMIQUE

ET

PROGRAMMATION

Cette version de lalgorithme est dmontrable partir des assertions donnes dans le texte. A la fin de chaque tour, 18ment le plus lourd est passe la fin de la zone considree. Cest une version moins efficace de lalgorithme du $3.1, en trouvant le plus grand lment au lieu du plus petit. Il y a deux faons damCliorer cette version : - Considrons le vecteur suivant : (23456789101) A chaque passage dans le vecteur, le seul change se fera entre la valeur 1 et son predcesseur immdiat. Nous proposons de faire des passages alternativement de gauche droite, puis de droite gauche. Dans ce cas, le deuxieme passage ramnerait le 1 au dbut du vecteur, qui serait donc trie aprs deux passages. - Mais cela ne suffit pas, car lalgorithme ne se rend pas compte que le vecteur est trie. Considrons le vecteur suivant : (21345678910) On voit quil sera trie la fin du premier passage. Ce quil faut ajouter au programme est la ralisation du fait que, tant donn quaucun change na eu lieu, pendant ce premier passage, a partir de la valeur 3, t[2..10] est tri et les ellments sont dj a leurs places dfinitives. La preuve de cette affirmation vient du fait que le test dmontre que ces lements sont ordonns deux deux. Lordonnancement tant transitif (aSb ET bic => tic), ils sont donc tous ordonns. En plus, lassertion montre que le dernier objet prendre sa place (ici t[2]) est le plus grand de tous ceux vu jusqualors. Il en rsulte que lon peut rkduire lespace trier plus rapidement. Ces amliorations, qui prennent en compte le travail accompli en route, cest--dire les changes intermdiaires, donnent lieu lalgorithme du programme
3.7,

DONNEES n: entier, t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT VAR bas, haut, i: entier; MONDE bas, haut: t[bas..haut] reste trier; INVARIANT t[l ..bas], t[haut..n] sont ordonns, t[l ..bas-11, t[haut+l ..n] sont placs; bas:=1 ; haut:=n; TANTQUE baschaut FAIRE MONDE i: t[i..haut] comparer deux deux, der: dernier objet boug; INVARIANT t[der..i] est ordonn; i:=bas; der:=i; TANTQUE khaut FAIRE SI t[i+l]<t[i] ALORS echange(t[i],t[i+l]); der:=i FINSI; i:=i+l FAIT; MONDE i: t[bas..i] comparer, der: dernier objet boug; INVARIANT t[i..der] est ordonn; haut:=der; i:=haut; TANTQUE basci FAIRE SI t[i]+l] ALORS change(t[i],t[i-11); der:=i FINSI; i:&l FAIT; bas:=der FAIT FIN POSTCOND t[l..nJ est tri Programme 3.7. Tripar bulles normal Dans ce texte, ordonne veut dire que le vecteur indiqu est trie, cest--dire que ses lements sont dans lordre, mais quils pourront changer de place dans le vecteur final en fonction dautres lments a insrer. Un vecteur place est non seulement ordonn, mais ces lments occupent les places quils auront dans le vecteur final. Ainsi :

52

53

fiLWRI-lHUIQUEETPROORAMMATION

yi..jj est ordonn <=> (i&&j => t[k]4[1]) yi..jJ est plac6 <=> (t[i..j] est ordonn ET (O-ck.4, i&j => t[k]5t[l]) ET (i%j, jemsn => t[l]n[m])) Les invariants permettent la dmonstration directe de la justesse du programme. On peut comparer la complexit de cet algorithme avec celle de lalgorithme par insertion. Les deux mnent un mme nombre dchanges, que lon calcule de la mani& suivante : en prenant les l6ments deux deux, le nombre dkchanges est le nombre de fois o une paire dlments nest pas ordonne. Mais le nombre de comparaisons nest pas le mme pour les deux algorithmes. Malheureusement, ni lun, ni lautre nest le meilleur dans tous les cas. Les deux algorithmes sont du mme ordre de complexiti, et ils partagent les proprits dtre bons dans le cas dun vecteur bien conditionn et dtre mauvais pour un vecteur mal conditionn. 3.4. Diviser pour rgner Les trois algorithmes de tri tudis jusquici sont tous dune complexit dordre o(n2). Dans chaque cas, un passage du vecteur rduit un probEme en n un probkme en (n-l). Comme pour la dichotomie par rapport la recherche linaire, il est possible de faire mieux en tiuisant un probEme en n deux problkmes en nf2. Le terme gnt%alement employ dans ces cas est celui de diviser pour rgner. On divise le vecteur en deux moitis, on trie chaque moiti4 et on remet les deux moitis ensemble. Il existe deux groupes dalgorithmes dans cette catgorie : les tris par partition et les tris par fusion. Le tri par partition permet de travailler sur place, cest--dire en gardant les t%ments dans le vecteur en vitant de copier le vecteur entier dans un nouvel emplacement dans la mmoire. Le hi par fusion, utilis du temps hroque des programmes de gestion sur support de bandes magnCtiques, ncessite la cr6ation dun deuxime vecteur dans la mmoire (ventuellement secondaire), vecteur qui reoit les 616ments dans le nouvel ordre. Pour des raisons videntes, nous accordons plus dimportance, dans cet ouvrage, au tri par partition. Le tri par fusion est souvent pris comme exemple dans hz cours de programmation avec le langage PROLOG. 3.41. Diviser pour rgner avec partition

dans cet ouvrage. Le principe est de partitionner les lments en deux classes en les comparant avec un lment dit pivot. Tous les Blments plus petits que le pivot vont se trouver sa gauche, les autres se trouvant sa droite. Considrons le programme 3.8, qui met en uvre cette classification par rapport au pivot t[ 11. DONNEES n: entier, t: TABLEAU [l ..n] DE entier; PRECOND n>O; DEBUT VAR pivot, camp, bas, haut: entier; MONDE pivot: base de la comparaison, camp: valeur comparer au pivot, bas: index du trou gauche, haut: index du trou droit; pivot:=t[l]; comp:=t[n]; bas:=l; haut:=n; TANTQUE baschaut FAIRE SI comp<pivot ALORS t[bas]:=comp; bas:=bas+l ; comp:=t[bas] SINON t[haut]:=comp; haut:=haut-1 ; comp:=t[haut] FINSI FAIT; t[bas]:=pivot FIN POSTCOND Okbascjln => t[i]ct[bas]<tu], bas=haut, t[bas]=pivot. Programme 3.8. Partition dun vecteur On crde deux trous dans le vecteur, en extrayant t[l], le pivot, et t[n], un lment de comparaison. Si IClment de comparaison est plus petit que le pivot, il est plac dans le trou gauche, sinon dans le trou droit. Un nouveau trou est cr@ ct de celui que lon vient de remplir, en extrayant comme lCment de comparaison le voisin du trou rempli. Le processus continue jusqua la rencontre des deux trous. On inskre le pivot dans le trou (unique) qui rsulte de cette rencontre, sachant que tous les lments plus petits que le pivot sont 21 sa gauche, les autres tant & sa droite. Notons que cette formulation permet lexistence de valeurs Cgales, une valeur gale au pivot tant mise a sa droite. La division du vecteur en deux parties a rduit le probkme de hi en deux sousproblmes. Il nous reste frier les l&ments a gauche du pivot, puis ceux sa droite, le pivot Ctant a sa place (il ne bougera plus). Lalgorithme est applique rkursivement, cest--dire que chaque moiti du tableau est de nouveau divise en deux par comparaison avec un pivot lui, et ainsi de suite. Apr&s avoir divis par deux un certain nombre de fois, il reste au plus un seul l6ment dans chaque classe (certaines sont vides), qui est automatiquement sa place sil existe. 55

Dans le cas dune distribution alatoire de valeurs, surtout si n est grand, lalgorithme de diviser pour rgner avec partition est le meilleur de ceux prsent&
54

hOORITHMIQUE ET PROGRA~ION

hGORITHhfIQUE

ET

PROGRAMMATION

Pour mener bien cette opration r&rsive, nous avons besoin de paramtrer le programme ci-dessus, qui va devenir une procdure dans le programme 3.9. PROC M(i, j); GLOBAL n: entier, t: TABLEAU [l ..n] DE entier; SPEC i, j: entier; trier t[i..j] par la mthode de diviser pour rgner; PRECOND 04,jln; DEBUT VAR pivot, camp, bas, haut: entier; SI j>i ALORS pivot:=t[i]; comp:=tjj]; bas:+ haut:=j; TANTQUE bas-zhaut FAIRE SI compepivot ALORS t[bas]:=comp; bas:=bas+l; comp:=t[bas] SINON t[haut]:=comp; haut:=haut-1 ; comp:=t[haut] FINSI FAIT; t[bas]:=pivot; M(i, bas-l); tri(bas+l, j) FINSI FINPROC Programme 3.9. Insertion dans une procdure rcursive Le terme GLOBAL indique que la variable n est dclare lextrieur de la procdure (dans le programme englobant). Ainsi, la valeur de n est la mme pour chaque appel de la procdure, tandis quil existe un nouvel exemplaire de i et de j pour chaque appel (chacun possde le sien). Le tableau t existe galement en un seul exemplaire, manipule par chacun des appels de la procdure. Cette procdure mne a bien le tri complet. Elle comporte le programme crit precdemment, avec les changements ncessaires pour trier t[i..j] au lieu de t[l..n]. Une fois que la division en deux zones a eu lieu, il reste les trier, lune apres lautre, par deux nouveaux appels de tri avec des pammtres appropris : M(i, bas-l) ET tti(bas+l , j)

M(l, n) La spcification dune procdure (SPEC) prend la place de la clause DONNEES dun programme. On y trouve la dfinition du jeu de parambtres, avec une indication de ce que la procdure doit faire. Cette indication est la post-condition de la procdureLalgorithme a une complexit thorique dordre o(n*log2(n)), montre le raisonnement suivant : comme le

Le cas parfait de cet algorithme arrive quand n=2i-l et le pivot divise toujours la zone en deux parties de longueurs gales. A la fin du premier tour, on a un pivot place et deux zones de taille (2-l-1)/2 = 2- -1. On voit que le nombre de comparaisons dans chaque zone est sa taille moins 1 (le pivot). En remultipliant par le nombre de zones, on dduit que le nombre total de comparaisons par tour est successivement : n-l, n-3, n-7, n-15, . . . Le nombre de tours est i, cest--dire log2(n+1). On obtient, pour des donnes bien conditionnes, la complexit thorique annonce. Ce calcul suppose que le pivot divise chaque fois son monde en deux parties de tailles comparables. Considrons maintenant un vecteur dj ordonne. Comme nous prenons le premier lment pour pivot, un tour va reduire le problme en n dans un problme en 0 et un autre en n-l. Le processus est de nouveau dordre o(n2). Ce rt%ultat, surprenant, montre que diviser pour rgner, tout en tant le meilleur algorithme dans le cas dune distribution alatoire avec une grande valeur de n, est le plus mauvais pour un vecteur dj tri (ou presque tri). On peut toujours prendre le pivot au centre du vecteur pour amliorer quelque peu lalgorithme dans ce cas. 3.42. Solution sans appel rbcursif Le dernier programme ci-dessus comporte deux appels rcursifs de la proc&lure. Si, pour une raison quelconque, on voulait enlever la r&ursivit, on utiliserait une pile (stack ou LIFO - Last In, First Out). Cette dernire sert mmoriser, pendant le tri dune zone, les limites des zones restant trier. Ainsi, on trie t[i..j], avec, au dbut : i=l ET j=n Apres un tour, avec p lindex de la position finale du pivot, on a la situation suivante :
57

La procdure teste par i<j quil y a au moins deux lments a trier. Ce test confiie que les appels rcursifs ne peuvent pas constituer un ensemble infini, car le nombre dlments a trier diminue avec chaque appel. Evidemment, on trie le vecteur complet par un appel de la procdure de la forme suivante :

56

&OORIIHMIQUE

ET PROGRAMMXION

fiLGO-QUEmPROGRAMMPIIION

t[l ..p-1] est trier t[p] est place t[p+l ..n] est trier. Les bornes 1 et p-l sont mises sur la pile et le prochain tour redmarre avec :
i-p+1 ET j = n

On reviendra sur les piles au cours du chapitre 4, en les utilisant souvent par la suite. 3.4.3. Quelques commentaires sur la rcursivit Dans le programme de recherche linaire, ou dans les tris primitifs, la base des algorithmes a t la rduction dun problbme en n vers un problme en (n- 1). Cette tiduction est triviale a mettre en uvre; chaque tour dune boucle, on excute laffectation : n:=n-1 et le tour est jou. En passant la recherche dichotomique, il sagit de rduire un problme en n vers un probl8me en n/2. On cherche un objet dans le domaine dindices [g..d]. Par rapport un ClCment mdiane dindex c on rduit le domaine soit [g..c], soit [c..d]. La mise en uvre est galement triviale, sagissant de lune des deux affectations suivantes : d:=c OU g:=c Notons nanmoins que ces utilisations de laffectation nexistent que pour des raisons de mise en uvre dans un ordinateur. Le raisonnement sous-jacent est de type rkurrent. Par exemple, pour la recherche linaire, on aurait pu crire la fonction suivante : chercher(objet, t[l ..n]): SI t[n] = objet ALORS trouve SINON chercher(objet, FINSI

Aprs chaque partition, une paire de bornes est mise sur la pile, lautre tant traite de suite. Aprs un certain nombre de partitions, la taille de la zone traiter est 1 (ou 0), cest--dire quelle est termine. Dans ce cas, on recommence avec la premikre paire de bornes sur la pile, et ainsi de suite. Cette technique donne lieu au programme 3.10. DONNEES n: entier, t: TABLEAU [ 1 ..n] DE entier, PRECOND O<i,j% DEBUT VAR i, j, pivot, camp, bas, haut, pl: entier, fini: bool, pile: TABLEAU [l ..taille] DE entier; i:=l ; j:=n; fini:=FAUX; pl:=l ; MONDE fini: t[l ..n] est tri6 pl: index de la premire case libre dans la pile t[i..j] est trier les zones indiques dans la pile sont trier TANTQUE NON fini FAIRE MONDE comme dhabitude TANTQUE j>i FAIRE pivot:=t[i]; comp:=t[]; bas:=i; haut:=j; \ TANTQUE basehaut FAIRE SI compcpivot ALORS t[bas]:=comp; bas:=bas+l ; comp:=t[bas] SINON t[haut]:=comp; haut:=haut-1; comp:=t[haut] FINSI FAIT; t[bas]:=pivot; pile[pl]:=i; pile[pl+l]:=bas-1 ; PI:=PI+~; i:=bas+l FAIT; fini:= Pl=i; SI NON fini ALORS PI:=PI-2; i:=pile[pl]; j:=pile[pl+l] FINSI FAIT FIN Programme 3.10. Version avec pile
58

t[l ..n-11)

Cette criture fonctionnelle peut sexprimer par lutilisation dune procdure rkcursive ou par une boucle avec affectation. Toute boucle peut se rcrire en forme de procdure rikursive de manikre directe. Considrons maintenant le tri par diviser pour rkgner avec partition. Ici, on r6duit un problbme en n vers deux problmes en n/2. Une forme rduite de la fonction de tri est la suivante :

59

kCiORITHMIQUE

ET PROGRAMMMION

hOORKHMIQUE JiT PROGRAMMMION

tri(t[g..d]): SI d>g ALORS partition(t[g..d], FINSI

c); tri(t[g..c-11);

tri(t[c+l ..d])

Une simple affectation ? chaque tour dune boucle ne suffit plus, car, si laffectation relance le premier des appels de tri rsultant, les param&es du deuxibme appel doivent tre stocks quelque part afin de revenir dessus la fin du premier appel. Notons que chaque appel provoque, , son tour, deux nouveaux appels, jusqu larriv& de vecteurs de longueur 1 (ou 0). On peut envisager la cascade dappels dans la forme dun arbre (figure 3.1).

contient, a chaque instant, les paires dindices correspondant aux appels laisss en suspens. Notons que le programme du paragraphe pr6cdent trie la partie droite du vecteur, en mettant les indices de la partie gauche en suspens sur la pile. Dans la figure 3.1, lordre est invers. En fait, lordre est indiffrent; a la rigueur, on pourrait lancer les deux appels 16cursifs en parallle sur deux processeurs ind6pendants dans le cadre dun matt5riel multiprocesseur. Lutilisation dune pile est un moyen gnral pour mettre en uvre la rcursivit. Si la pile est programme explicitement, comme dans le paragraphe prcdent, on y stocke les informations permettant de savoir o lon en est dans la cascade dappels. On verra dautres exemples dans le chapitre sur la marche arrire. En fait, lors de lutilisation dune procdure rcursive, un compilateur engendre des instructions qui g&rent une pile. Y sont gardes les valeurs des paramtres de lappel en cours et des variables locales chaque niveau dappel. Ce sujet est couvert avec plus de d&ails dans des cours de compilation tels que [Cunin 19801. On peut se demander quels sont les schmas rcurrents qui permettent une traduction facile vers une boucle avec affectation directe de variables. Un problme qui se rduit de manire rcurrente en deux sous-problmes, ou plus, ne permet pas une telle traduction, car il faut toujours garder la trace des appels en suspens. Dans un schma un seul appel, la traduction vers une boucle est directe dans le cas dune rcursivit terminale. Dans ce cas, aucun calcul ne reste excuter aprs la fin de lexcution de lappel rcursive, comme dans le cas de la recherche linaire. Le problbme sera reconsidr dans le chapitre 7, qui traite de la transformation de programmes. 3.4.4. Deux pivots Comme la division dun problbme en deux sous-problmes - chacun de la moiti du cot du premier - reprt%ente un grand gain defficacit, on peut se demander si lide de diviser un probl&me en trois reprsente encore une amlioration. Cest purement au titre dune spculation intellectuelle que la question se pose pour le tri par diviser pour rgner avec partition. Lauteur ne connat pas de publication ni dapplication de I?d&. La mise en uvre dun tel algorithme (programme 3.11) ncessite donc deux pivots, avec un lment de comparqson. Avec les deux pivots, on partage les &ments en trois ensembles : ceux qui sont plus petits que le plus petit des pivots, ceux qui sont plus grands que le plus grand des pivots et ceux qui sont entre les deux. On trie par la suite les trois ensembles par trois appels r6cursifs.

tri(g..d)

tri(g..cl)

tri(cl..d)

tri(g..c3)

A A
tri(g..c2) tri(c3..c2)

tri(c2..cl)

Figure

3.1. Appels aprspartition

Cette figure montre les appels en cours aprs trois partitions, les indices ayant t6 simplifis pour allger le dessin (suppression des +l et -1). Apr&s chaque partition, on reprend la branche gauche, en laissant la branche droite en suspens. Le successeur gauche est de nouveau partitionn, et ainsi de suite. La modlisation par procdure n5cursive est donc une mise en uvre directe et simple de lalgorithme. Par la suite, nous avons montr une mise en uvre avec une pile. La pile a servi se rappeler ce qui reste faire un moment donn. Elle
60

61

Auio-QUE

IT PROGRAMMtWION

hOORITHMlQUE I?I PROGRAmION

DEBUT DONNEES n: entier; t: TAB [1 ..n] DE entier; PROC tri(min, max: entier): \ VAR tg, td, pl, p2, tl, t2, t3: entier; SI max>min ALORS pl :=t[min]; t2:=min+l; p2:=t[t2]; tl :=min; test:=t[max]; t3:=max; TANTQUE t243 FAIRE SI tesbt2 ALORS t[t3]:=test; t3:=t3-1; test:=t[t3] SINON SI test.4 ALORS t[tl]:=test; tl :=tl+l; t[t2]:=t[tl] SINON t[t2]:=test \ FINSI; t2:=t2+1; test:=t[t2] FINSI FAIT; t[t l]:=pl ; t[t2]:=p2; tri(min, tg-1); tri(tg+l, td-1); tri(td+l, max) FINSI FINPROC; tri(1, n) FIN Programme 3.ll. Diviser pour rgner avec deux pivots

Dans cette figure, Pl est la valeur du plus petit des deux pivots, P2 celle du plus grand. Les lments les plus petits occupent la premire zone dans le vecteur, les plus grands occupant la demi?% (quatrime) zone. Les Mments ayant des valeurs interm&liaires occupent la deuxime zone, laissant la troisime pour les lments nayant pas encore t consid&. Introduire un lment dans la quatribme zone ne pose aucun probl&me; il pousse le trou droit un cran a gauche, le dernier lment non trait tant pris comme lment de comparaison. De mme, on peut introduire un lment a la fin de la deuxi&me zone, repoussant le trou central un cran droite et en considrant le premier des lments non traits. Pour introduire un lment dans la premire zone, il faut mordre sur la deuxibme. Le premier l6ment de la deuxime zone passe la fin de cette zone, en poussant le trou central. Le premier trou peut ainsi avancer dun cran, laissant de la place pour llment insrer. On considre le premier lment non trait, ject par lavance du trou central. Cette mthode apporte-t-elle des amliorations ? En thorie, oui; la complexit est dordre o(n*log3(n)) au lieu de o(n*logz(n)). En pratique, il faut des vecteurs de trs grande taille pour sen apercevoir.
3.4.5. Tri par fusion

La seule difficult dans ce programme rside dans le maniement des trous et les ensembles pendant la partition. Il y a trois trous : deux pivots et un lement de comparaison. Les trois trous dfinissent quatre zones dans le vecteur : trois ensembles B trier et une quatribme zone qui contient les lments en attente de partition. La figure 3.2 montre la distribution de trous et de zones pendant lopration de partition.

Le tri par fusion date dune poque o les mmoires centrales taient petites par rapport aux fichiers trier, ces derniers tant stocks sur des bandes magntiques. Le principe est simple : on coupe le fichier en deux moitis, on trie chaque moiti, puis on fusionne les deux mdiitis tries en intercalant les valeurs de lune et de lautre dans le bon ordre. Lopration est rpte rcursivement autant de fois que ncessaire. Lopration de fusion ncessite une deuxime copie du fichier, ce qui double lespace mmoire occup. En pratique, la fusion se faisait avec deux bandes magntiques en entre, chacune avec une moiti trie du vecteur, et une troisime bande en sortie pour recevoir le tout. La rcursivit ne va pas jusquau bout; on divise le vecteur par deux jusqu ce que le rt%ultat de la division puisse tenir dans la mmoire principale. On peut ds lors trier cette zone du vecteur en mmoire avec un algorithme du type dj vu. Tout lart de la programmation de ce type de situation comportant des bandes magntiques consistait bien organiser les informations sur les bandes afin dviter de coteux allers et retours pour rechercher le prochain bloc. En ignorant lexistence des bandes magntiques, lalgorithme peut travailler de la mani&e suivante : - On divise le vecteur tl autant de fois que ncessaire pour que ls zones soient
63

Pl et[i]<P2
Figure 3.2. Vecteur en cours de partition

non traits

62

.kGORlTHh4lQUFiETPROGRAMMtUlON

fiLGORlWMIQUE ET PROGRAMMAITON

de longueur au plus 1. - On fusionne des zones de longueur 1, par paires, pour crer des zones de longueur 2 dans une nouvelle copie t2 du vecteur. - Les zones de longueur 2 sont fusiondes pour crer des zones de longueur 4, de nouveau en tl. - On continue de la sorte jusquh la fin du processus. Prenons comme exemple un vecteur de 8 lments, tri ?I lenvers. Les tats successifs de fusion sont les suivants : t1: t2: t1: t2: (87654321) (78563412) (56781234) (12343678) Vecteur Fusion Fusion Fusion de dpart dlments crant des paires par paires par groupes de quatre l

Le programme 3.12 montre une mise en uvre correspondante.

DONNEES n: entier, t: TAB [l ..n] DE entier; DEBUTVAR 1, pl, p2, p3, il, i2, i: entier; MONDE on fusionne des paires de zones de longueur I en zones de longueur 21; pl est lindex du premier Mment da la premire zone, p2 lindex du debut de la seconde zone, p3 ds la zone suivante (n+l sil ny en a pas); i &ments ont 15th recopi& vers le nouvel exemplaire du vecteur, il est lindex du premier lment non recopi8 da la premire zone, i2 de la deuxime; I:=l; WNTQUE la-~ FAIRE i:=O; pl :=l; ANTQUE i<n FAIRE p2:=pl+l; p3:=min(p2+l, n+l); il :=pl; i2:=p2; ANTQUE i-+3-1 FAIRE i:=i+l; SI il=p2 ALORS t2[i]:=tl[i2]; i2:=i2+1 SINON SI i2=p3 OUALORS tl[il]<tl[i2] ALORS GZ[i]:=tl[il]; il :=il +l SINON t2[i]:=tl[i2]; i2:=i2+1 FINSI FINSI FAIT; \ pl :=p3 FAlT; 1:=2*1; SI ktl ALORS pl :=l ; i:=O; 7ANTQUE i<n FAIRE p2:=l+l; p3:=min(p2+1, n+l); il :=pl ; i2:=p2; TANTQUE icp3-1 FAIRE i:=i+l ; SI il=p2 ALORS tl[i]:=t[i2]; i2:=i2+1 SINON SI i2=p3 OUALORS t2[il]t2[i2] ALORS tl[i]:=t2[il]; il:=il+l SINON tl[i]:=t2[i2]; i2:=i2+1 FINSI FINSI FAIT; pl :=p3 FINSI; 1:=2*1 FAlT FIN

Programme 3.12. Triparfusion

64

65

AI.,OOmQUE ET PROC3RAMMKTION

3.5. Rsum de la complexit des algorithmes En fonction des discussions prcdentes, on peut dgager des raisons menant un choix sens dalgorithme de tri pour une situation donne. On aboutit aux conclusions suivantes : - La mthode de recherche du plus petit lment nest jamais bonne. On ne Iutilisera pas. - Pour de grands vecteurs dont les lments sont distribus alatoirement, la mthode de diviser pour rgner avec partition est la meilleure. Elle est mauvaise dans le cas dun vecteur dej tri, ou presque tri. Pour limiter les dgts dans ce cas, il vaut mieux prendre le pivot au milieu de la zone, ce qui complique lgerement le programme (voir exercice). - Pour des vecteurs presque tris, les deux methodes dinsertion ou par bulles sont de bonnes candidates, condition dutiliser la version optimise pour la deuxitme. Lune ou lautre peut tre la meilleure, en fonction des propriets particuli&es de lordonnancement approximatif dj existant.

Chapitre 4

Des structures de donnes

3.6.

Exercices

Les langages de programmation classiques permettent lutilisation de variables simples (entiers, rels, . . .) et de tableaux, voire, ventuellement dautres types de donnes composes (records, . ..). D ans le dernier exemple du chapitre 3 (tri par diviser pour rgner), nous avons eu besoin dune structure de donnes particulire, la pile, qui nexiste pas directement dans ces langages. En effet, dans la plupart des langages de programmation, on ne dispose pas dun type pile. Un certain nombre de structures de donnes de ce type reviennent constamment w dans les programmes. Dans ce chapitre, nous allons dcrire les principales structures, avec les moyens de les reprsenter dans des langages existants. Avec le dveloppement de nouveaux langages, on peut sattendre voir ces objets devenir des types standards. En premier lieu, nous allons considrer les piles, les jles (queue ou FIFO - First In, First Out), les arbres (tree), les treillis (lattice) et les graphes (graph). 4.1. Les piles Une pile est un ensemble ordonn dobjets de mme type (entiers, rels, . ..). Cest comme si lon gardait une pile de livres sur son bureau. On peut poser un nouveau livre sur la pile, ou reprendre le livre qui est en haut de la pile. Extraire un livre du milieu est tellement difficile que nous renonons a cet exercice. On dispose dun vecteur dont les lments sont du type appropri. Il existe deux oprations fondamentales : poser un objet (empiler, ou push) et en retirer un (dpiler, ou pull). Le vecteur sert a stocker les objets poss. Pour savoir combien dobjets sont dans la pile a un moment donn, on utilise un pointeur de niveau. Le pointeur sert aussi retrouver, dans le vecteur, le dernier objet dpos.

1. Dans le tri par insertion, lutilisation de ETPUIS permet de supprimer la variable arrt. Dans un langage sans ETPUIS, comment arriver au mme rsultat par lintroduction dune bute ? 2. Donner une rgle permettant de calculer le nombre de comparaisons ncessaires danslecas: - dun tri par insertion, - dun tri par bulles. 3. Dans la dernire version du tri par diviser pour rgner, on fera la modification consistant a prendre comme pivot lelment au milieu de la zone trier. 4. Un bon exercice au niveau dune classe est de comparer les diffrents tris, mis en machine par diffrents lves. On mesurera, par lhorloge et en comptant les oprations, le cot de lexcution de chaque tri, en essayant une srie de vecteurs diffrents. On variera la distribution dlments (tris, presque tris, alatoire, tris Ienvers , . ..) et la longueur du vecteur. Ltablissement de courbes pour chaque mthode permet den confirmer la complexit thorique.

l l

66

ALGORITHMIQUE ET PROGRAhShfMION

fiLOORIlTIMIQUE ET PROGRAMMHION

Le programme 4.1 utilise lindex de la premire case libre (pl) dans une pile dentiers.
DONNEES taille: entier, pile: TABLEAU [l ..taille] DE entier; VAR pl: entier INIT 1; PROCEDURE empiler(x: entier); PRECOND pIstaille; pile[pl]:=x; pl:=pl+l FINPROC; PROCEDURE dp\iler(x:entier); PRECONQ pl>l ; pl:=pl-1 ; x:=pile[pl] FINPROC

PROGRAMME DISCUTABLE DONNEES taille: entier; file: TABLEAU [l ..taille] VAR ancien: entier INIT 1; libre: entier INIT 1; PROCEDURE mettre(x: entier); PRECOND librestaille; file[libre]:=x; libre:=libre+l FINPROC; PROCEDURE enlever(x: entier); PRECOND anciewlibre; x:=file[ancien]; ancien:=ancien+l FINPROC

DE entier;

Programme 4.2. Unefile discutable Pour mettre un objet dans la file, il faut quil y ait une place libre (PRECOND libreltaille). Pour en enlever, il faut quil y ait au moins un objet dans la file (PRECOND ancienclibre). Les deux indices ancien et libre cernent les objets restants dans la file, qui se trouvent en file[ancien..libre-11. Pourquoi avoir dit que le programme ci-dessus est discutable ? Tout simplement parce quil ne rutilise pas Iespace dans la file. Une fois arriv au bout (libre=taille+l), on ne peut plus y mettre de nouveaux lments, mme si le retrait dautres lments a libr de la place. Il faut rendre la file circulaire, en testant autrement la prsence dau moins un lment (programme 4.3). DONNEES taille: entier, file: TABLEAU [Maille] DE entier; VAR n: entier INIT 0; ancien: entier INIT 1; libre: entier INIT 1; PROCEDURE mettre(x: entier); PRECOND netaille; file[libre]:=x; SI libre=taille ALORS libre:=1 SINON libre:=libre+l FINS1 FINPROC; PROCEDURE enlever(x: entier); PRECOND n>O; x:=file[ancien]; SI ancien=taille ALORS ancien:=1 SINON ancien:=ancien+l FINS1 FINPROC Programme 4.3. Unefile circulaire

Programme 4.1. Mise en uvre dune pile On voit que le nombre dobjets dans la pile un moment donn est ~1-1. Les procdures supposent que la pile ne dborde pas (PRECOND plltaille) et que lappel de dpiler na pas lieu avec une pile vide (PRECOND pl>l). En pratique, on teste ces conditions par des SI ALORS spcifiques en dbut de procdure. Dans le chapitre prcdent, nous avons programm directement la pile sans utiliser les procdures empiler et dpiler. La taille de la pile tait suppose suffisante. Une pile vide tait le signe de terminaison de lalgorithme. -i La pile donne ici est une pile dentiers. Le mme schma de programme peut servir pour la construction dune pile de rels, de boolens, . . . (il suffit de changer le type des lements du tableau pile). c
4.2. Les files

Une file est une structure qui ressemble une pile, a la diffrence pres que lon retire llment le plus ancien au lieu du plus rcent. Une telle structure est particulirement utile, par exemple, dans un systme dexploitation, pour la gestion des files dattente. On pourrait imaginer la paire de procdures du programme 4.2.

68

69

&OORITHMlQUE ET PROGRAMMKMON

hOORllXhUQUE ET PROGRAMhCWION

La nouvelle variable n indique le nombre dlments actuellement p&ents dans la fe. Les pr&conditions de non-pltkitude et de non-vide dpendent donc de la valeur de n. Les variables libre et ancien avancent chaque fois de 1, en recommenant au dbut de la file une fois arrives la fin. Dans un syst&me dexploitation, on parle souvent dun producteur (producer) et dun consommateur (consumer) a la place de mettre et enlever (voir [Griffiths 19881). 4.3. Les arbres Larbre est une structure fondamentale dans lalgorithmique. Nous en avons dj vu un dans lalgorithme de tri par diviser pour rgner. Le pivot sert diviser le vecteur en trois zones : - une partie gauche, - le pivot, - une partie droite. Les parties gauche et droite sont de nouveau divises, chacune en trois nouvelles zones, et ainsi de suite. La figure 4.1 prsente ces divisions en forme darbre.

Formellement, un arbre est une structure compose de nuds (node) et de bronches (arc, branch). Une branche menant du noeud nl au noeud n2 fait que n2 est un successeur (successor) de nl, nl tant le prkdkcesseur (predecessor) de n2. Chaque nud, lexception de la racine, possde un et un seul prdcesseur. Un nud peut avoir un nombre quelconque de successeurs. Un nud sans successeur est une feuille. Suivre un chemin dans larbre se fait en suivant des branches successives. Ainsi, si n2 est un successeur de nl et n3 est un successeur de n2, alors il existe un chemin de nl n3 (par lintermdiaire de n2). Nous limitons nos arbres des arbres connexes, cest-Mire que tout nud n est accessible partir dune unique racine. Accessible veut dire quil existe un chemin de la racine de larbre jusquau nud n. Notons que lunicit des prdcesseurs fait que ce chemin est unique.

4.3.1. Arbres binaires et arbres n-aires La dfinition donne ci-dessus permet un nud dun arbre davoir un nombre quelconque de successeurs. En pratique, les programmeurs se limitent souvent lutilisation darbres binaires, dans lesquels un nud a au plus deux successeurs. A priori, cela pourrait constituer une limitation du pouvoir dexpression, mais en fait ce nest pas le cas. On dmontre que-tout arbre n-aire peut tre reprsent sous la forme dun arbre binaire, sans perte dinformation. La forme binaire dun arbre n-aire sappelle un diagramme en forme de vigne (vine diagram). Au lieu de garder, pour chaque nud, des pointeurs vers ses successeurs immdiats, on garde un pointeur vers son premier successeur (fils an) et un deuxime vers le prochain successeur de son pr&Icesseur (frre cadet). La figure 4.2 en donne un exemple.

t[l ..n]

t[p+l ..n]

t[l ..pl-1]

t[pl]

t[pl +l ..p-1]

t[p+l ..p2-l]

UP21

t[p2+1 ..n]

Figure 4.1. Arbre du tri diviserpour rgner

On voit sur la figure 4.2 les couples de pointeurs reprsents dans des botes deux cases. Une case ban& indique labsence de successeur. Cette faon de dcrire un arbre correspond dassez prs sa reprsentation physique dans la mmoire de Iordinateur.

Notons que les informatidiens ont pris lhabitude dinverser les arbres : la situation initiale, la racine (root), est en haut, et les lkments terminaux, les feuilles (leaf, leaves), en bas. Des arbres australiens, en quelque sorte . . . On parle donc dalgorithmes qui descendent de la mcine jusquaux feuilles.

70

71

~ORITHMIQUE

ET PROGRAhSMbXION

.iLOORITHMIQUFi

If2 PROGRAMMB-ION

Les deux premires colonnes de cette figure ne sont donnes que pour faciliter la lecture. Dans la ralit, seuls sont conservs les vecteurs succg (successeur gauche) et succd (successeur droit). Etant donn la possibilit de transformer un arbre n-aire en arbre binaire, cette representation est toujours suffisante. La structure avec deux botes par nud date de LISP [McCarthy 19601. Si lon voulait conserver la forme originale, avec n successeurs possibles, la table de la figure 4.3 comporterait autant de colonnes que le nombre maximum de successeurs. 4.3.3. Parcours darbres Lalgorithme classique de parcours dun arbre utilise un double appel rcursif (programme 4.4).
DONNEES succg, succd: TABLEAU [l ..taille] DE entier;

Figure 4.2. Tran@mnation

darbre n-aire en arbre binaire

4.3.2. Reprsentation darbres Pour illustrer la reprsentation physique des arbres binaires, reprenons les botes de la figure 4.2, en les mettant dans une table (en pratique on utilise deux vecteurs). Les noms des noeuds deviennent des entiers qui servent dindex dans ses vecteurs. En reprenant larbre de la figure 4.2, avec a=l, b=2, et ainsi de suite, on obtient les vecteurs de la figure 4.3. nom A B c D E F G H 1 J
iIl&X

PROC parcours(n: entier); SI succg(n)zO ALORS parcours(succg[n]) FINSI; SI succd(n)+O ALORS parcours(succd[nJ) FINSI FINPROC Programme 4.4. Parcours dun arbre binaire Le parcours complet dun arbre commence par un appel de

succg 2 5 0 8 0 0 0 0 0 0
4.2

1 2 3 4 5 6 7 8 9 10

0 3 4 0 6 7 0 9 10 0

parcours(racine) Le parcours dun noeud implique le parcours de tous ses successeurs. Les tests sur les successeurs tablissent leur existence. Pour viter de tester successivement lexistence dun successeur gauche, puis celle dun successeur droit, on peut avoir recours la notion de bute. Un successeur inexistant a lindex 0. Il suffit de crer un noeud dindex 0 comme bute (programme 4.5).

Figure 4.3. Reprsentation de larbre de lafigure


12

73

fiLGORITHMQUFiJ3TPROGR4MMATION

fiLGORlTHMIQUElTPROGR4MWTION

DONNEES succg, succd: TABLEAU [O..taille] PROC parcours(n: entier); SI nd) ALORS parcours(succg[n]); parcours(succd[n]) FINSI FINPROC Programme 4.5. Utilisation dune butke

DE entier;

Chaque fois quun nud na pas de successeur (gauche ou droit), le programme appelle parcours(O). Le test au dbut de la procdure fait que lexcution ne va pas plus loin. Pour liminer la rcursivit, on peut utiliser une pile, dans laquelle on garde le souvenir des nuds qui restent a parcouru (programme 4.6). DONNEES succg, succd: TABLEAU [O..taille] DE entier; racine: entier; DEBUT VAR n: entier INIT racine, pl: entier INIT 1, fini: bool INIT FAUX, pile: TABLEAU [l ..tpile] DE entier; MONDE pl: premire case libre dans pile; TANTQUE NON fini FAIRE SI n=O ALORS fini:= pl=l ; SI NON fini ALORS dpiler(n) FINSI SINON empiler(succd[n]); n:=succg[n] FINSI FAIT FIN 1 Programme 4.6. Parcours avec pile 43.4. Parcours prfix et post-fix

A5

Figure 4.4. Parcours en profondeur dabord (ordrepr@xt?) La num&otation des nuds de cet arbre correspond lordre de visite des nuds
dans un parcours dit deprojhdeur dabord (depth first seatch). Nous verrons par la suite que cet ordre est celui de lvaluation dite prt?jxt?e (ptefixed). Il est possible dimaginer un parcours de larbre en largeur dabord (breadth first search, figure

4.3, mais le programme est plus difficile Ccrire (voir exercice en fin de chapitre).

10

AA
11 l2
75

13

14

15

Considrons larbre binaire complet trois niveaux de profondeur qui se trouve en figure 4.4. Dans un arbre binaire complet, tous les noeuds autres que les feuilles ont exactement deux successeurs. De plus, tout chemin de la racine une feuille est de la mme longueur (trois dans notre cas).
74

Figure 4.5. Parcours en largey dabord

&OORITHMIQUE

ET PROGRAMMATION

iLGORITHMlQuE ET PROGRAMM.WION

Revenons sur le parcours en profondeur dabord, en dcorant le programme avec une mention pour chaque visite dun nud (programme 4.7). PROCEDURE parcours(n); SI n>o ALORS visite-l ; parcours(sg[n]); visite-2; parcours(sd[n]); visite3 FINSI / FINPROC Programme 4.7. Triple visite dun nud Ces numros de visite correspondent au fait que, dans un parcours darbre, on visite chaque nud trois fois (figure 4.6), une fois en descendant gauche, une fois ayant termin le parcours des successeurs gauche et avant de parcourir les successeurs droits, et une dernire fois en remontant droite la fin.

numt5ros des noeuds la place de visite-2 donnerait lordre infix (hfbxd! (figure 4.7), celui donn par visite-3 tant post-jxt (postfuted) (figure 4.8). Ces drff&ents ordres ont leur importance lors du traitement des expressions dans un compilateur. 8

AA 0,
7 9 11 13

10

14

Figure 4.7. Parcours en ordre infix

1
1

0
2

3 J\O

10

13

3
Figure 4.6. Triple visite des nuds dun arbre Supposons quau cours du parcours en profondeur dabord de larbre dans le programme 4.7, on imprimait le numro de nud a la place de visite-l. Alors lordre dimpression des nuds serait celui de la figure 4.4, ordre dit prfixe. Imprimer les
76

11

12

Figure 4.8. Parcours en ordrepost-fixk

77

h3ORITHhtIQUE I?I- PROGRAMWWION

fiLOORI?TIhUQUE

ET PROGRAMWXION

4.4. Les treillis Un treillis ressemble un arbre, mais en permettant un nud davoir plus dun pr&l&esseur. Les branches peuvent donc se rejoindre. Nanmoins, il nest pas possible de boucler, cest--dire que tous les chemins vont vers lavant, en terminant sur une feuille. Nous retrouverons le probl&me des boucles dans le paragraphe sur les graphes. La figure 4.9 donne un exemple de treillis.

4.5. Les graphes Un graphe est toujours une collection de nuds et de branches, mais sans limitations. Ceci permet des boucles, cest--dire quil peut exister un chemin qui m&ne du nud n jusqu lui-mme. Le rseau du mtro parisien est un exemple de graphe, o lon peut revenir au point de dpart dun voyage. On voit quun treillis est un cas particulier du graphe, un arbre tant un cas particulier du treillis. Pour des raisons pragmatiques nayant rien faire avec la thorie, les informaticiens limitent souvent les graphes quils manipulent. Ainsi, on prendra souvent la forme de vigne, afin de ne parler que de graphes binaires. De mme, certains programmes ne travaillent que sur des graphes possdant une racine, cest-dire que seront considrs comme tant nuds du graphe, les nuds qui sont accessibles B partir de la racine. La notion de racine dans un graphe na pas de justification thorique; elle correspond une volont de simplifier les programmes de traitement. Dans le cas gnral, parcourir un graphe binaire partir de sa racine avec lalgorithme donn pour les arbres mnerait une boucle, car lalgorithme suit tous les chemins possibles. Pour visiter chaque nud une fois, sans boucler, on a recours & la technique de marquage. Le programme garde un drapeau boolen pour chaque nud, indiquant si le nud a djh t visitk. On ne visite pas deux fois un noeud (programme 4.8).

Racine

Figure 4.9. Un treillis Notons, dans cette figure, que tous les chemins qui commencent la racine terminent sur lune des feuilles Fl ou F2 dans un nombre fini de pas. On peut parcourir un treillis comme un arbre, par exemple avec un des programmes du paragraphe prcdent. Ce parcours visitera certains noeuds plusieurs fois, car ils ont plus dun prdcesseur. En fait, chaque nud sera visit autant de fois quil y a de chemins qui mnent de la racine jusqu lui. Il se peut que lexistence de visites multiples soit gnante, pour des raisons defficacit ou de rkpptition intempestive doprations. Pour visiter chaque nud une et une seule fois, on applique lalgorithme de parcours dun graphe (voir ci-dessous). En fonction des besoins immdiats, on traite donc un treillis comme un arbre ou, le plus souvent, comme un graphe.

DONNEES taille: entier; succg, succd: TABLEAU [O..taille] DE entier; racine: entier; DEBUT VAR marque: TABLEAU [O..taille] DE bool INIT FAUX; PROCEDURE visiter(n: entier); SI NON marque[n] ALORS marque[n]:=VRAl; visiter(succg[n]); visiter(succd[n]) FINSI FINPROC; visiter(racine) FIN Programme 4.8. Visite dun graphe

Ce programme suppose lexistence dun nud de bute 0, qui est marqu. Ainsi, le test de marquage et le test dexistence ne font quun. Notons que la 79

78

reprsentation dun graphe sur un couple de vecteurs succg et succd est la mme que celle dun arbre. On peut enlever la rcursivit de cet algorithme en utilisant une pile, exactement comme dans le cas dun arbre. Par la suite, nous montrerons une mthode de parcours dun graphe sans rcursivit mais aussi sans pile. En gnkal, on parcourt un treillis comme un graphe, cest--dire en marquant les nuds visites. Cela vite les visites rptr5es aux nuds qui sont sur plusieurs chemins. 4.51. Algorithme de Schorr-Waite

Avec cette representation programme 4.9. PROC parcours(n); SI m[sg[n]]=O ALORS m[n]:=l ; parcours(sg[n]) FINSI; SI m[sd[n]]=O ALORS m[n]:=2; parcours(sd[n]) FINSI; m[n]:=3 FINPROC

de la marque, lalgorithme classique prend la forme du

Lalgorithme classique de parcours dun graphe, comme celui dun arbre, met en jeu, soit un systme dappels rcursifs, soit une pile. Une application frquente de lalgorithme se trouve dans des interprteurs pour des langages tels que LISP, o, quand la mmoire affecte est pleine, il faut examiner les donnes en cours afin de recuprer de lespace dans la mmoire principale. Pendant lexecution dun programme, on cree des nuds et des arcs, on en supprime et on les modifie. Aprs un certain temps, la partie de la mmoire affecte au graphe est pleine. Mais, suite aux diffrentes manipulations, certains nuds ne sont plus utiles. Lutilit dun nud est dfinie par son accessibilit. Tout noeud accessible partir de la racine est utile (il peut toujours servir). Un nud qui nest pas accessible ne peut plus servir. Pour rcuprer lespace occup par les nuds inaccessibles, on commence par le marquage de tous les noeuds accessibles. Par la suite, dans une deuxime phase, on examine tout lespace utilisable, en rcuprant les cases non marques. Ce processus sappelle le ramasse miettes (garbage collection). Le problme vient du fait que le ramassage des miettes intervient au moment o lon constate que la mmoire est pleine. On ne souhaite pas alors lancer une procdure rcursive, tant donn que son excution ncessite louverture dune pile de taille non prvisible. Une solution de ce problme existe [Schorr 19671. Il sagit de rutiliser les pointeurs pour mettre en uvre la notion de prdcesseur. Considrons le parcours classique dun graphe. La figure 4.6 a dj montr ce parcours sur trois nuds dun arbre. Chaque noeud est visit trois fois. Pour se rappeler o il en est, lalgorithme de Schorr-Waite garde dans la marque le nombre de visites au noeud dj effectues: marque-0 marque=1 marque=2 marque=3 noeud non visit en train de visiter la descendance gauche descendance gauche termine, visite de la descendance droite en cours le marquage de tous les successeurs du nud a t effectu
80

Programme 4.9. Marquage avec troisvaleurs En enlevant la rcursivit de cette procdure, on peut dduire le programme 4.10. DEBUT n:=racine; TANTQUE NON fini FAIRE CAS m[n] DANS m[n]:=l; 0: SI m[sg[n]]=O ALORS n:=sg[n] FINSI, m[n]:=2; 1: SI m[sd[n]]=O ALORS n:=sd[n] FINSI, m[n]:=3; n:=pred[n] 2: FINCAS FAIT FIN Programme 4.10. Version sans rtcursivit

81

,dLGORITHMIQ~

ET

PROGRAMMATION

Dans ce programme, la condition de terminaison est que tous les successeurs de la racine ont t marquks. Dans ce cas, lalgorithme remonte la racine pour la demi& fois, cest--dire que m[racine]=3. Le programme utilise toujours la notion de bute (un successeur absent pointe vers le nud fictif 0, qui est marqu), mais il teste si le successeur ventuel (droite ou gauche) est marquk avant dy aller. La difficult de cette version du parcours vient du fait que lon ne sait pas remonter vers le pticesseur (n:=pred[n]) aprs avoir pass la valeur de la marque 3. La structure des donnes ne comporte que des pointeurs vers les successeurs de chaque noeud. Pour n5soudre le problme, Schorr et Waite ont propos de renverser le pointeur vers le $uccesseur suivi afin dindiquer, de manire temporaire, le pr&l&esseur du nud. Pour ce faire, on garde, tout moment, dans une variable p, la valeur prcdente de n, le nud courant. Considrons la situation quand lalgorithme arrive pour la premire fois sur le nud 1 de la figure 5.6 : n=l, marque[l]=O, p=prdcesseur(l), sg[l]=2, sd[l]=3.

n=prdcesseur(l),

marque[l]=3,

p=l, sg[l]=2, sg[l]=3.

On retrouvera un rsum de ces tats dans la figure 5.6. Avec cette introduction, nous arrivons au programme 4.11. DONNEES Un graphe cod en forme de vecteurs comme avant DEBUT VAR n, p: entier; nkracine; p:=O; TANTQUE m[racine]<3 FAIRE CAS m[n] DANS 0 : m[n]:=l ; SI m[sg[n]]=O ALORS % La voie est libre gauche % cycleh sg[nl, p) SINON % La voie nest pas libre, mais on fait comme si lon y tait all et comme si Ion en revenait %

Appelons les cases qui indiquent les successeurs dun nud sg[n] et sd[n]. Lalgorithme va procder au marquage de la descendance gauche. n va donc prendre la valeur de sg[n], p prenant la valeur de n (il suit toujours n avec un temps de retard). Comme n a pris la valeur du successeur gauche, la case qui contient lindex de ce successeur fait maintenant double emploi. On le rkutilise temporairement pour se rappeler du prdcesseur (lancienne valeur de p). On arrive la situation suivante : n=2, marque[l]=l , p=l, sg[l]=prdcesseur(l), sd[l]=3. A la fin du marquage de la descendance gauche, cest-Mire quand marque[2] passe 3, lalgorithme remonte sur le nud 1 dans ltat suivant, avec p toujours juste derrire n : n=l, marque[l]=l, p=2, sg[l]=prdcesseur(l), sd[l]=3.

wWg[nl, PI
FINSI, 1 : m[n]:=2; SI m[sd[n]]=O ALORS % La voie est libre droite % cycle(n, sd[n], sg[nl, P) SINON % Comme si lon revenait de droite % cycWg[nl, p, sd[nl) FINSI, 2: m[n]:=3; cycle(n, sd[n], p) FINCAS FAIT FIN Programme 43 . Mise en uvre du prdcesseur Le programme comporte deux points particuliers : linstruction cycle et ce qui se fait quand le chemin nest pas libre, droite ou gauche. Linstruction cycle correspond une affectation multiple. Ainsi, considrons le cycle suivant :

On va maintenant repartir droite, ayant restaur le successeur gauche et en conservant le prdcesseur du nud 1 dans sd[ l] pendant la visite : n=3, marque[l]=2, p=l, sg[l]=2, sd[l]=prdcesseur(l). En remontant de la droite, on aura : n=l, marque[l]=2, p=3, sg[l]=2, sd[l]=prdcesseur(l). n va remonter vers le prdcesseur, en laissant le nud 1 dans son tat de dp-t:
82

cyWn, ss[nl,

P)

83

ALGORITHMIQUE

m PROGRAMMMION

z!LGORITHMQUE

ET

PROGRAMMATION

11 correspond a laffectation multiple :

m=O m=3

h w[nl, PI := (sg[nl,
Pour excuter une les affecte, en parallble, &Vite de considrer des affectations individuelles. un rdsultat incorrect : n:=sg[n]; sg[n]:=p; j p:=n

P, n)

affectation multiple, on calcule les valeurs droite, puis on dans les variables indiques gauche. Cette faon dcrire effets de bord qui pourraient rsulter de lordonnancement des Par exemple, la squence suivante daffectations donnerait

m=l

m=2 La premire affectation n fait que les deux autres affectations reprent la nouvelle valeur de n quand cest lancienne qui est recherche. Il faudrait sauvegarder lancienne valeur de n : xn:=n; n:=sg[xn]; sg[xn]:=p; p:=xn Dautres squences de code donnent le mme rsultat. Quand le successeur prvu est marqu, soit parce quil nexiste pas, soit parce quil est en cours de traitement sur un autre chemin, le programme ne doit pas le prendre en compte. Mais les pointeurs doivent tre mis dans un tat qui correspond laugmentation de la valeur de m[n]. Lalgorithme met jour les pointeurs comme si n revenait du chemin qui mne au successeur marqu, bien quil ny soit jamais all. m=O m=3 Figure 4.10. Etats dans Schorr-Wtite La nouvelle id& est de garder le pointeur vers le pticesseur dans la case du successeur que lon ne prend pas. La figure 4.11 montre les tats rsultants, dans lesquels le pointeur vers le descendant restant doit obligatoirement changer de place. P

m=l 4.5.2. Amlioration de lalgorithme de Schorr-Waite Cette prsentation vient de [Griffiths 19791, en rponse celle de [Gries 19791. Cest ce dernier qui a transmis lide de lastuce qui permet de raccourcir le programme ci-dessus. Nous ne connaissons pas lidentit de linventeur de lastuce. Considrons les tats par lesquels passent les noeuds au cours du traitement (figure 4.10). m=2

Figure 4.ll . Etats dans Schorr-Waite amtlior


84 85

t&GORITHhllQUE

IS PROGRAMION

Cette idee, priori un peu bizarre, donne lieu au programme 4.12. DONNEES Un graphe cod en forme de vecteurs comme avant DEBUT VAR n, p: entier; nkracine; p:=O; TANTQUE m[racine]<3 FAIRE CAS m[n] DANS 0: m[n]:=l ; SI m[sg[n]]=O ALQRS cycle(n, sg[n], sd[n], p) SINON cycle(sg[n], sd[n], p) FINSI, 1 : m[n]:=2; SI m[sg[n]]=O ALORS cycle(n, sg[n], sd[n], p) SINON cycle(sg[n], sd[n], p) FINSI, 2: m[n]:=3; cycle(n, sg[n], sd[n], p) FINCAS FAIT FIN Programme 4.12. Schorr-Waite amlior On voit que, dans cette version du programme, les mmes cycles se rptent. En regroupant, dune part, les cycles identiques et, dautre part, les augmentations des valeurs de m[n], on obtient le programme 4.13. DEBUT VAR n, p: entier; n:=racine; p:=O; TANTQUE m[racine]<3 FAIRE m[n]:=m[n]+l ; SI m[n]=3 OUALORS m[sg[n]]=O ALORS cycle(n, sg[n], sd[n], p) SINON cycle(sg[n], sd[n], p) FINSI FAIT FIN Programme 4.13. Version condenske 86

Cette version finale du programme, tres condense, est assez agrable du point de vue de lesthtique, mais elle a demand un travail considerable. Cest un bon exercice de style. En plus, le programme rsultant peut tre repris tel quel et installe dans un ramasse-miettes performant. 4.5.3. Reprsentation de graphes sur une matrice binaire On peut aussi reprsenter un graphe sur une matrice binaire, ce qui facilite le traitement de graphes n-aires. Un 1 B lintersection de la ligne Y et de la colonne X veut dire que le nud X est un successeur du nud Y. Considrons le graphe de la figure 4.12.

A C E

Figure 4.12. Un graphe

Sa reprsentation en forme de matrice binaire se trouve sur la figure 4.13. A 0 0 0 0 0 0 B 1 0 0 1 0 0 C 1 1 0 0 0 0 D 0 0 1 0 0 0 E 0 1 0 0 0 0 F 0 0 0 0 1 0

A B c D E F

Figure 4.13. Reprsentation sur une matrice binaire

87

Un 1 reprsente un arc qui mne du nud indiqu par la ligne celui de la colonne, un 0 indiquant labsence dun tel arc. Cette representation est plus compacte que la table utilide auparavant dans le cas dun graphe permettant de multiples successeuts (graphes n-aires). Elle se prte aussi certaines classes de manipulations, comme dans le paragraphe suivant.

Les nuds B, C, D presentent la particularit dtre leurs propres successeurs (un 1 sur la diagonale principale). Cest une indication de la prsence dune boucle (on peut aller de B jusqu B). Le graphe nest donc pas un treillis et, fortiori, nest pas un arbre. Lalgorithme direct de calcul dune fermeture transitive suit la dfinition : SI B est un successeur de A, ALORS tous les successeurs de B sont galement successeurs de A. Considrons la ligne A de la figure 4.13. Elle comporte des 1 dans les positions B et C. Ainsi, les successeurs de B sont des successeurs de A, comme le sont les successeurs de C. Pour les successeurs de B, on prend la ligne B en lajoutant la ligne A par lopration OU inclusive. On fait de mme pour chaque 1 de la ligne A, que le 1 soit dorigine ou quil rsulte dune addition. On ne reprend pas deux fois un 1 dans la mme position. Pour la ligne A, cela donne les oprations de la figure 4.15.

4.5.4.

Fermeture

transitive

La fermeture dune relation est une opration bien connue des mathmaticiens. Elle &ulte de la transitivit de lopration considere. Par exemple, cette proprit nous permet de dduite: SI aeb ET bec ALORS a<c FINSI Intuitivement, on dit que les amis des amis sont des amis. Considrons la notion de successeur dans un graphe. Dans la matrice du paragraphe prcdent, un 1 dans la position (~,y) indique quil existe un arc entre le nud y et le noeud x (x est successeur de y). Maintenant, si x est un successeur de y, et si y est un successeur de z, on peut dduire que x est un successeur de z (en passant par y), cest--dire quil existe un chemin entre z et x. La fermeture transitive dun graphe donne sa connectivitk Pour chaque nud n, on obtient la liste complte des nuds qui sont accessibles partir de n. La figure 4.14 montre la fermeture transitive du graphe de la figure 4.13. On voit, dans la premire ligne, quil est possible, en partant de A, datteindre les noeuds B, C, D, E, F. En revanche (premiere colonne), A na pas de prdcesseur. On voit bien que A est la racine dun graphe connexe. De la mme faon, dans la dernire ligne, on voit que F na pas de successeur, cest--dire que F est une feuille.

A B C D E F ligne A addition addition addition addition addition Figure au de de de de de dpart : Ia ligne B : la ligne C : la ligne D : la ligne E : la ligne F : 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1.1 1 0 0 0 0 1 1

(sans changement) (sans changement)

4.15. Fermeture transitive, une ligne

Aucun nouvel 1 nayant t introduit, on a termin avec la ligne A. La mme opration a lieu pour chaque ligne du graphe. Cet algorithme nest pas optimal. Il a t amlior plusieurs reprises [Warshall 19621, [Griffiths 19691.

A B c D E F

A 0 0 0 0 0 0

B 1 1 1 1 0 0

C 1 1 1 1 0 0

D 1 1 1 1 0 0

E 1 1 1 1 0 0

F 1 1 1 1 1 0
4.13

4.6. Ordres partiels et totaux Les quatre structures, les files, les arbres, les treillis et les graphes, ont des proprits dordonnancement importantes. Dans une file, il existe un ordre total, cest--dire que pour chaque couple (x, y) dlments, une et une seule des relations suivantes est vraie (pas est la position de llment donn) : Posa() < PWY) ou Pw)o P=(Y)

Figure 4.14. Fermeture transitive du graphe de lafigure

88

89

&OORITHMIQUE ET PROGRAMIUKIXON

tiLGORlIHMQUEETPROGRA~ION

Cette propriete fait quil nexiste quun seul ordre dcriture dans lequel les clments se trouvent chacun leur place. Elle est vidente dans le cas dune file (voir le cas des entiers positifs).
Dans un arbre ou dans un treillis, il nexiste que des ordres partiels. Un ordre doit respecter la rgle suivante : ALORS ~OS(X) FINSI

suite. Le tout constitue donc un arbre binaire dont les nuds sont dcor& (dans le champ val) avec le nom de la personne reprsente. Un nom est une suite de lettres de 8 caracteres au plus. La racine de larbre sappelle adam (ou ve, si lon prlbe). On fbira les proc&hr.res n%trsives suivantes :
- PROC trouver(nom, n) -> (bool, entier).

SI x est successeur de y
> pas(y)

La variable nom contient le nom dune personne. Le boolen retourn en &ultat sera VRAI si et seulement si la personne nomme est prsente dans larbre ou le sous-arbre de racine n. Si le nom est prsent lentier retourn en rsultat contiendra lindex du nud comportant le nom.
- PROC parent(p, enf, n) -> (bool, entier).

Considerons

larbre binaire trivial de la figure 4.16.

A
123 132

Comme pour trouver, mais on cherche un couple parent - enfant de noms p et enf. Lentier rsultant contiendra, le cas ch&nt, lindex du parent.
- PROC cousingermain(a, - PROC aeul(a, b, n) -> (bool, entier, entier).

Recherche de cousins germains de noms a et b.


enf, n) -> (bool, entier, entier).

Figure 4.16. Arbre binaire simple


La rgle dordonnancement est respecte par les deux squences suivantes :

Recherche des liens entre laeul de nom a et son descendant enf. On imprimera (dans le bon ordre) les noms des personnes dans la descendance (a est parent de b, b est parent de c, . . . , i est parent de enf), ou un message appropri si enf nest pas un descendant de a. 2. Par la suite, on peut se poser les mmes questions avec des familles plus naturelles, o chaque enfant a un p&re et une mre, avec des mariages stables et monogames. On garde le contrle des naissances (pas plus de deux enfants par famille). On engendre un treillis. On btira un systme conversationnel qui accepte des informations des types suivants : - a se marie avec b, - naissance de lenfant e du couple m, f. Larbre familial est un fichier sur disquette. Les procdures de recherche peuvent prendre la forme de questions (interface ?) comme : -Quiestlepredex? - Qui est la grand-mre maternelle de y ? - Des questions de lexercice 1. 3. Dans un arbre binaire complet n niveaux, combien y a-t-il de nuds ? Combien de feuilles ? 4. Ecriture du programme de parcours dun arbre binaire en largeur dabord.

La figure 4.4 (prfix ou en profondeur), comme la figure 4.5 (en largeur), prksentent des ordres partiels dans un arbre. Les ordres des figures 4.7 (infix) et 4.8 (post-fixe) ne respectent pas la rgle. Les arbres et les treiilis permettent toujours les ordonnancements partiels des lements les constituant (il existe un seul ordre, cest-adire un ordre total, si et seulement si aucun nud na plus dun successeur direct). Lordonnancement nest plus possible pour un graphe qui comporte une boucle. En effet, comme la boucle indique quil existe au moins un lment qui est son propre successeur, il nest plus possible de respecter la kgle dordonnancement par la relation de succession. 4.7. Exercices
REMARQUE. - La notion de pointeur na pas t introduite. Il faut donc grer

des tables dindex. 1. On considre des familles de type un peu particulier. Il y a un seul parent, avec au plus deux enfants. Un enfant peut lui-mme tre parent dautres enfants, et ainsi de 90

91

Chapitre 5

Rcurrence et rcursivit

Que ce soit pour la rsolution de problmes, pour la spcification dalgorithmes, voire mme pour leur mise en uvre, la rcwrence (induction) est un outil mathmatique essentiel. Tout informaticien se doit de la matriser. Heureusement, la rcurrence est une technique simple, en depit de sa puissance. Dans les chapitres prcdents, nous avons dj vu des programmes bass sur la rkurrence. Il est dailleurs difficile dimaginer des programmes qui ny font pas appel, car la simple boucle en est une forme. Linformaticien parle souvent de rduire un problme en n vers un problme en n-l. Dans dautres cas (diviser pour rgner ou parcours de graphes, par exemple), on parle de la rduction dun problme en n vers deux problmes en n/2. Il sagit maintenant de rendre le lecteur plus conscient du mcanisme et de sa puissance.
traduire par la rcursivit

Au niveau de la programmation, nous avons dj vu que la rcurrence peut se (recursion) ou par des itrations, avec ou sans pile. Le choix de mthode dpend de la complexit du problme considr, de la puissance du langage de programmation utilis et de lefficacit requise. Comme dhabitude, nous abordons les techniques travers des exemples. Ceuxci sont souvent bien connus, car cest un sujet sur lequel beaucoup de collgues ont dj travaih.

5.1. Lexemple type - les tours dHanoi Si ce problme est utilis comme exemple dans tous les cours de ce type, ce nest pas un hasard. Il est mme arriv au niveau des journaux de vulgarisation

hGORIWMIQUE ET PROGRAMMKfION

/KGO~QUE

ET PROGRAh4MkXION

[Bezert 19851. La source la plus ancienne que nous connaissons est de E. Lucas, sous le pseudonyme de M. Claus, en 1883. Les tours dHanoi donnent lieu a une solution lgante par la n?currence, une analyse itkative demandant plus de travail. Le titre du problme vient de lhistoire raconttk habituellement, qui est celle de moines bouddhistes en Asie du sud-est qui grnent le temps en transfrant des disques, tous de tailles diffrentes, sur un jeu de trois piquets (voir figure 5.1). Dans cette figure il ny a que cinq disques, mais la tradition veut que les moines jouent avec 64. Lhistoire est une invention du dix-neuvibme sicle, Lucas la plaant B&u&s (en Inde). On ne sait pas comment elle sest resitue Hanoi . . .

PROCEDURE hanoi(n, a, b); SI n=l ALORS dplacement(a, b); SINON hanoi(n-1, a, 6-a-b); dplacement(a, b); hanoi(n-1,6-a-b, b) FINSI FINPROC Programme 5.1. Toursdtianoi,
version de base

Dans cette proc&re, nous avons suppos que les piquets sont numrots 1.2, 3. Deplacer un disque se fait par la procdure dplacement. Pour n=l le deplacement est immediat, autrement on applique lalgorithme dcrit ci-dessus, 6-a-b tant le numro du troisime piquet (6=1+2+3, donc en soustrayant deux des trois possibilits de 6 on obtient la troisime). La figure 5.2 montre les appels en cascade de la procdure hanoi (note h) pour le transfert de trois disques. Le dplacement dun disque est not d. A B

C h(3,12)

Figure 5.1. Position de dpart des tours dtianoi

Le jeu consiste transfrer la pile de disques du piquet A vers le piquet B, en utilisant C comme piquet de manuvre, tout en respectant les deux rgles suivantes : - un disque ne peut pas tre pos sur plus petit que lui, - on ne dplace quun disque la fois. La solution la plus simple vient de la rponse la question suivante : si je savais transfrer n-l disques, saurais-je transfrer n ?. La rponse est oui, car autrement nous naurions pas pos la question. Ainsi, pour transfrer n disques de A B, on commence par le transfert de n-l disques de A C, suivi du dplacement du dernier disque (le plus gros) de A B, suivi du transfert des n-l disques de C B. Cela donne la proc&rre du programme 5.1.

h(2,1,3)

W 2)

~(2,3,2)

h(l ,112)

A
dU,3)

ht1 ,231

h(l,3,1)

A
d(3,2) W

,112)

Figure 5.2. Arbre dappels pour trois disques

Larbre se lit de la maniere suivante : - Pour transferer 3 disques du piquet 1 au piquet 2 (racine de larbre), on
94 95

ALGORITHMIQUE

m PROGRAM~WION

fdLGORlTHMIQUE

IX PROGRAMMtWION

transfre deux disques de 1 a 3, un disque de 1 2, puis deux disques de 3 2 (successeurs de la racine). - Les successeurs de type h sont dcomposs de la mme manire, en appliquant rcursivement la procdure. - Les feuilles de larbre sont des dplacements dun seul disque. En reprenant les feuilles de larbre dans lordre, de gauche a droite, on dduit les dplacements simples, dans lordre, pour le cas de trois disques : d(lS) d(l3) d(Z3) d(l2) W,l) d(32) d(1,2)

51.2. Une analyse plus pousse La solution rcursive au problbme des tours dHanoi est satisfaisante pour lesprit et marche dans lordinateur. Mais elle comporte linconvnient de ne pas tre tres pratique pour le joueur humain. Mettons nous un instant la place des moines, qui dplacent leurs disques la main. Quel disque faut-il dplacer un moment donn ? Lalgorithme rcursif necessite la mise a jour constante dune pile pour prendre en compte les appels en cours, mais il ne serait pas trs pratique pour les moines de grer une telle pile. En fait, un peu de rflexion nous permet de faire mieux, au moins pour jouer la main. Pour obtenir des rgles simples pour le joueur humain, nous avons besoin de quelques thtkmes.

Les sceptiques peuvent toujours essayer avec trois pices de monnaie ! 5.1.1. Cotit de lalgorithme Se pose maintenant la question de savoir combien de dplacements individuels sont ncessaires pour transfrer n disques. En considrant le programme du paragraphe prcdent, on voit que le transfert de n disques cote deux fois le prix du transfert de n-l disques plus 1 dplacement, par le fait que :
h(n)= h(n-1)+ 1 + h(n-1)

Thorme 1. Dans une position quelconque, il y a au plus trois dplacements possibles.

Preuve. Supposons quaucun piquet nest vide. Comparons les tailles des trois disques en haut des piquets. Le plus gros des trois ne peut pas bouger, car il faudrait le poser sur plus petit que lui. Le moyen peut se poser sur le plus gros (une possibilit), mais pas sur le plus petit (qui est plus petit que lui). Le petit peut passer sur Iun ou lautre des deux autres piquets, ce qui donne trois possibilits en tout. Maintenant, si un des piquets est vide, il joue le rle du plus gros disque, qui ne peut pas bouger de toute faon, et largument ne change pas. Si tous les disques sont sur le mme piquet (deux piquets vides), cest-a-dire dans la position de dpart, il ny a que deux possibilits : le plus petit peut aller sur lun ou lautre des deux piquets restants. Thorme 2. Le disque moyen ne doit pas bouger deux fois de suite. Preuve. Sur les piquets de dpart et darrive, les autres disques sont tous plus gros que lui. Sur le troisime piquet, il y a le plus petit. Aprs un dplacement, le moyen reste donc le moyen. Son seul dplacement possible serait de retourner do il vient, ce qui serait parfaitement inutile. 97

On obtient les quations de rtkurrence suivantes, avec c reprsentant le cot de lopration, mesur en nombre de dplacements individuels :
Cl = 1 c,=2*c,,+1 pourrbl

Pour voir la solution de ces quations de rcurrence, considrons quelques cas :


n 1 2 3 4 cn 1 3 7 15

On voit intuitivement que


C,=2"-1

En substituant cette valeur dans lquation, la solution est confirme. 96

hOORlTHMIQUE ET PRCGRAMMATION

Thorme 3. Le plus petit ne doit pas bouger deux fois de suite. Preuve. Ce nest pas la peine, car il aurait pu y aller en un coup. Tborme 4. Tous les dplacements dindex impair sont des dplacements du plus petit disque, les dplacements pairs tant du moyen. Preuve. Cest le petit qui commence dans la position de dpart, dplacement 1. Par la suite, le moyen et le plus petit alternent, daprks les thoremes 1-3. Nous avons dmontre que pour savoir qui bouge, il suffit de savoir si le dernier transfert tait du petit disque ou du moyen. Le prochain dplacement concerne donc lautre disque mobile. Comme les dplacements du disque moyen sont imposs (une seule possibilit), ils ne posent pas de problbme. Reste trouver une regle pour les dplacements du plus petit. Thhorme 5.

pour n (le tour continue). Comme la propriete est vraie au niveau de trois disques (voir les dkplacements r&sultants de la figure 5.2), elle est vraie pour n disques, -3. Cette preuve est juste, mais elle pose souvent des probltmes aux lves en raison du changement apparent de la direction du cercle entre le niveau n et le niveau n-l. Cest un faux problme, mais il indique une autre proprit intressante. Supposons que le transfert complet souhaite est de A B. Alors, si le nombre total de disques est impair, le premier dplacement (ncessairement du plus petit) est galement de A B. Mais, si le nombre total de disques est pair, le premier dplacement est de A C (lautre possibilit). Avec un nombre impair de disques, le cercle est abcabc... Avec un nombre pair, il est acbacb... Si n est pair, n-l est impair, et inversement. Mais, en regardant les param&res des appels de n et de n-l dans le formule F ci-dessus, on se rend compte de Iinversion de la direction. Nous pouvons donc dduire la regle pour les moines, qui doivent transfrer, rappelons le, 64 disques. Pour effectuer un transfert de A B, le premier deplacement est de A C (64 est un nombre pair, pour un nombre impair de disques le deplacement aurait t de A B). Par la suite, il suffit de se rappeler si le coup qui vient davoir lieu tait un dplacement du plus petit disque, ou non : - Si le dplacement prcdent tait du plus petit disque, on dplace le moyen (une seule possibilit). - Si le dplacement prcdent tait du disque moyen, on dplace le plus petit vers le prochain piquet dans le cycle A C B A C B . . .

Le plus petit disque parcourt les piquets dans un ordre fixe, cest--dire quil fait des cercles. Preuve. Par rcurrence sur n. Supposons que la proprit soit vraie pour n- 1, cest--dire que le petit disque tourne, par exemple, dans lordre a b c a b c . . . On sait que (formule F) : n>l => h(n, a, b) = h(n-1, a, c); d(a,b); h(n-1, c, b) Comme le d(a,b) ne concerne pas le plus petit disque, si les deux appels de h(n-1, . ..) font tourner le petit disque dans le mme sens, alors la proprit est vraie

5.2. Des permutations La gnration des permutations de n objets est un exercice dalgorithmique et de programmation qui prsente un intrt pdagogique certain tout en menant a des programmes utiles. La litt&ature scientifique comporte de nombreux papiers sur le sujet. Nous abordons ces algorithmes dun point de vue personnel, en fonction de dcouvertes alatoires. Le premier programme date dune des coles de FL. Bauer.

98

99

ALOORIlTIMIQUE

ET PROGRAMMATION

h3ORlWMIQUE

ET PROGRAMMATION

5.2.1. Permutations par Ichanges

de voisins

La spcification de ce probleme nous a t donne dans les annes 70, par un collegue. Malheureusement, nous ne nous rappelons plus de qui il sagissait. Si jamais il lit ces lignes, ce serait avec plaisir que nous lui attribuerons son bien . . . Le probleme est dengendrer toutes les permutations des n lments dun vecteur, en appliquant les seules rgles suivantes. - Entre deux permutations successives, la seule diffrence r6sulte de lchange de deux voisins. Notons quen gnral V[il a deux voisins, V[i-l] et V[i+l], mais v[l] et v[n] nont chacun quun seul voisin, ce qui veut dire que le vecteur nest pas circulaire et, en particulier, v[l] et v[nl ne sont pas des voisins. - En numrotant chacun des objets par lindex de sa position de dpart (l..n), lchange qui a lieu entre deux permutations successives concerne llment le plus grand possible, sans reproduire une permutation dj produite. Ces deux rgles impose un ordre unique de production des permutations, mme si cet ordre nest pas vident a premire vue. Afin de bien cerner le problme, considrons lordre impos par cet algorithme pour les permutations de quatre entiers (figure 5.3).
Numm 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1234 1243 1423 4123 4132 1432 1342 1324 3124 3142 3412 4312 4321 3421 3241 3214 2314 2341 2431 4231 4213 2413 2143 2134 Pivot 4 4 4 3 4 4 4 3 4 4 4 2 4 4 4 3 4 4 4 3 4 4 4 Monde des trois 123

Sur cette figure, on voit que le 4 traverse les autres objets de droite a gauche, puis retourne au point de dpart par une traverse de gauche a droite. Au moment de son changement de direction, le 4 ne peut pas revenir immdiatement, car lchange reproduirait la permutation pnkdente. Chaque fois que le 4 termine une traverse, et donc va changer de direction, lalgorithme effectue un change dans le monde des trois. Cela veut dire que les trois objets restants mettent leur vie au niveau infrieur. Le 4 traverse chaque permutation du monde des trois avant que lalgorithme en produise la prochaine. Dans le monde des trois, le trois traverse chaque permutation du monde des deux. Ainsi, on justifie lalgorithme par la formule de rcurrence suivante : lobjet n traverse completement chaque permutation des n-l objets restants. On engendre ainsi toutes les permutations de n objets. La demibre colonne de la figure 5.3 indique les permutations des trois objets qui sont engendn$es chaque changement de direction du quatrime. Le sens du mot pivot deviendra clair par la suite.
52.2. Le programme

Dans ce programme, nous travaillons sur lentier i, qui est lindex de lchange considr. LCchange i transforme la permutation i en la permutation i+l. La valeur de i varie donc de 1 factoriel(n)-1, o n est le nombre dobjets, lchange dindex factoriel(n) reproduisant la position de dpart. Les objets sont reprsents par les entiers (l..n). Le vecteur v contient, chaque instant, la dernire permutation produite. Avec les conventions ci-dessus, nous allons considrer les questions suivantes : - A lchange dindex i, quel est lobjet qui doit schanger avec un voisin plus petit que lui ? On appelle cet objet le pivot. - Dans quelle direction le pivot doit-il bouger ? - O se trouve le pivot dans v ? La figure 5.3 indique le pivot dans le cas des permutations de quatre lments. Reprenons cette sequence (figure 5.4).
123456789101112131415161718192021222324 444344434 4 4 2 4 4 4 3 4 4 4 3 4 4 4fin

132

312

321

231

213

Figure 5.4. Valeurs de i et du pivot dans permutations(4) Dans cette figure, la Premiere ligne indique les index des changes successifs, la deuxime le pivot pour lchange. On voit que le quatre prend trois changes pour
101

Figure 5.3. Permutations de quatre entiers


100

hOOR111IMIQuE

E rPROGRAMM4TION

fiLOORIIHMIQUE

EI PROGRAMhfH-ION

traverser la permutation courante du monde des trois, suivi dun change dans ce monde des trois. On voit que chaque fois que 4 nest pas un diviseur de i, cest le 4 qui est le pivot. Considrons maintenant le monde des trois. Les changes le concernant sont les 4, 8, 12, 16 et 20, tous les i multiples de 4. Il suffit de diviser ces valeurs par 4 pour retrouver la squence 1,2,3,4,5. De manire rcurrente, dans le monde des trois, le pivot est le 3, sauf si 3 divise le nouvel i (lancien i/4). Pour le cas gnral, on obtient le programme 5.2 pour le calcul du pivot. DEBUT nn:=n; ni:=i; TANTQUE nn DIVISE ni FAIRE ni:=ni/nn; nn:=nn-1 FAIT % nn est le pivot pour lchange i % FIN Programme 5.2. Calcul dupivot Le fait de recopier n et i dans les variables temporaires nn et ni vite de les d&ruire. Loprateur DIVISE donne un rsultat VRAI si son deuxime oprande est un multiple exact du premier, FAUX autrement. Dans la plupart de langages de programmation, il faudrait kcrire un test du type suivant : SI nn*entier(ni/nn)=ni ALORS . . .

positions a partir de son point de dpart. - Le point de dpart du pivot est complttement gauche ou complbtement droite de lensemble des lments du monde des nn, qui se trouvent dans des positions successives (tout lment dordre suprieur est en fin de traverse). Le point de dpart naturel dune traverse est la position 1 (traverse de gauche &oite) ou nn (de droite a gauche). Mais considrons, dans la figure 5.3, lchange numro 4, qui est le premier du monde des 3. Le pivot, dans ce cas le 3, nest pas en position 3, mais en position 4. La raison est que le 4 a termin une traverse de droite gauche sans revenir. On voit quil faut dplacer le point de dpart naturel par le nombre dlments dordre suprieur qui ont accompli un nombre impair de traverses. Tout cela nous donne le programme 5.3. DEBUT DONNEES n: entier; VAR i, ni, nn, p, q, d, pos: entier; VAR perm: TABLEAU [ 1 ..n] DE entier; POUR i:= 1 JUSQUA n FAIRE perm[i]:=i FAIT; POUR i:= 1 JUSQUA fact(n)-1 FAIRE ni:=i; nn:=n; d:=O; TANTQUE nn DIVISE ni FAIRE ni:=ni/nn; nn:=nn-1 ; SI impair(ni) ALORS d:=d+l FINSI FAIT; p:=entier(ni/nn); q:=reste(ni/nn); SI pair(p) ALORS pos:=nn+d-q+l ; changer(perm[pos], perm[pos-11) SINON pos:=q+d; changer(perm[pos], perm[pos+l J) FINSI FAIT FIN Programme 5.3. Permutationspar changes Dans ce programme, d est le dplacement cr par les traverses dlments dordre suprieur. Aprs la division ni/nn, la nouvelle valeur de ni reprsente le nombre de traverses dj effectues par lancien M. Si ce nombre est impair, nn est reste a gauche, provoquant une augmentation de la valeur de d. Le calcul de pos, la position du pivot, prend en compte les faits dduits ci-dessus. Le calcul de pos, avec le cumul de d, peut tre vit en gardant en mbmoire le vecteur inverse du vecteur perm. Llment perm[i] indique lobjet qui se trouve en
103

Examinons maintenant le problme de savoir o en est le pivot dans ses traverses du monde infrieur. A la fin de la boucle ci-dessus, nn est le pivot et ni lindex de lchange dans le monde des nn. Considrons les valeurs de p et q, Calcul&s comme suit : p:=entier(ni/nn); q:=reste(ni/nn);

Notons que q>O, car la division laisse ncessairement un reste (condition de terminaison de la boucle). On dmontre que p est le nombre de traverses dj effectues par nn, q tant lindex de lchange dans la traverse actuelle. Cela rsulte du fait quune traverse ncessite tut-1 6changes avec nn comme pivot. Avec cette information, on peut dduire les faits suivants. - Si p est pair, le pivot procde de droite gauche, et si p est impair, de gauche droite. Effectivement, la premire traverse, avec p=O, a lieu de droite a gauche, la deuxime, avec p=l, dc gauche droite et ainsi de suite. - Le pivot a effectu q-l changes dans cette traverse. Il a donc avanc de q-l 102

ALG~RITHMICKE

m PROGRAMMMION

&GORIIHMIQUE

I7r PROGRAMMtWON

position i. Llment inv[i] va indiquer la position de lobjet i en perm. Cest donc une reprsentation duale de la relation biunivoque position <-> objet On obtient le programme 5.4.

fichier. On voit que la reprsentation choisie doit tre une fonction de lutilisation que lon va en faire. La double reprsentation est souvent ncessaire, cest--dire que h pr6fecture garderait deux fichiers, lun tri en fonction des noms des propritaires, lautre par des numros dimmatriculation. 5.2.3. Relation avec les tours dHanoi

DEBUT DONNEES n: entier; VAR i, ni, nn, p, q, pos, voisin: entier; VAR perm: TABLEAU [l ..n] DE entier; POUR i:= 1 JUSQUA n FAIRE perm[i]:=i; inv[i]:=i FAIT; POUR i:= 1 JUSQUA fact(n)-1 FAIRE ni:=i; nn:=n; d:=O; TANTQUE nn DIVISE ni FAIRE ni:=nihn; nn:=nn-1 FAIT; p:=entier(ni/nn); q:=reste(ni/nn); pos:=inv(nn); SI pair(p) ALORS voisin:=perm[pos-11; perm[pos-l]:=nn; inv[nn]:=pos-1 ; inv[voisin]:=inv[voisin]+l SINON voisin:=perm[pos+l]; perm[pos+l]:=nn; inv[nn]:=pos+l; inv[voisin]:=inv[voisin]-1 FINSI; perm[pos]:=voisin FAIT FIN Programme 5.4. Permutations par changes, version 2

La technique dutiliser lindex du coup jouer marche aussi dans les tours dHanoi. Supposons que les disques sont numrots de 1 (le plus grand) n (le plus petit). Pour savoir quel est lindex du disque qui doit bouger, on peut utiliser un programme semblable celui que nous avons dduit ci-dessus pour les permutations (programme 5.5). DEBUT nn:=n; ni:=i; TANTQUE 2 DIVISE ni FAIRE ni:=ni/2; nn:=nn-1 FAIT; % Cest le disque nn qui bouge % FIN Programme 5.5. Encore les tours dtianoi

Ce calcul dpend du fait que le plus petit disque bouge une fois sur deux, que le second bouge une fois sur deux des coups restants, et ainsi de suite. Lide de travailler sur les proprits numriques de lindex du coup sav&re intressante dans un ensemble dapplications. 5.2.4. Une variante En relaxant la r&gle concernant lchange de voisins, on peut produire un deuxihme programme, similaire au premier, qui engendre toutes les permutations de n objets dans un autre ordre. Nous allons travailler sur la rcurrence suivante : le pivot n traverse de droite gauche chaque permutation du monde des n-l. Pour quatre objets, les permutations sont engendres dans lordre de la figure 5.5.

Dans cette version, pos est la position du pivot, voisin lobjet avec lequel il va schanger. Les deux vecteurs sont mis jour de manire vidente. Lide de garder la reprsentation inverse dune relation revient souvent dans les programmes, comme dans les bases de donnes. Considrons le fichier de propritaires de voitures tenu jour par une prfecture. On peut, par exemple, le trier dans lordre alphabtique des noms des propritaires. Dans ce cas, connatre le numro dimmatriculation de DUPONT est une opration rapide (avec un algorithme de recherche dichotomique, par exemple). Mais dcouvrir le nom du propritaire du vhicule 9999Xx44 devient difficile, car il faut inspecter chaque entre dans le
104

105

Index

Les4 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1234 1243 / 1423 4123 1324 1342 1432 4132 3124 3142 3412 4312 2134 2143 2413 4213 2314 2341 2431 423 1 3214 3241 3421 4321

Mondes Les 3 123

Les2 12

132

DEBUT DONNEES n: entier; VAR i, j, nn, p, q: entier; perm: TABLEAU [l ..n] DE entier; i:=O; TANTQUE idact(n) FAIRE nn:=n; P:=i; POUR j:=l JUSQUA n FAIRE perm[j]:=O FAIT; TANTQUE nn>O FAIRE % placer lobjet nn % q:=reste(p/nn); p:=entier(p/nn); j:=n; TANTQUE q>O FAIRE % sauter q positions vides % SI permjj]=O ALORS q:=q-1 FINSI; FA,T j:=j-1 TANTQUE permjj]zO FAIRE % trouver la prochaine position vide %
j:=j-1

312

213

21

231

nn:=nn-1 FAIT; % perm contient la permutation dindex i % i:=i+l FAIT FIN Programme 5.6.
Traverse unidirectionnelle

FAIT; permjj]:=nn;

321 Pour chaque valeur de i, le programme calcule la position de chaque objet nn (llnnln) dans le vecteur perm. Par la mme logique que celle de lalgorithme prcdent, on prend la partie entire (en p) et le reste (en q)de la division de p (une copie de i au dpart) par nn. La partie entire indique combien de traverses compltes ont t effectues par nn. Le reste indique le nombre davances dj effectues par nn dans la traverse actuelle. Les index i vont de 0 fa&(n)-1 pour faciliter les calculs. La valeur de q permet de calculer la position de lobjet nn dans perm. Lobjet nn a dj avanc q fois. En commenant droite, lalgorithme passe sur q positions vides dans perm. Lobjet nn va occuper la prochaine position vide. La boucle est rpte pour chaque objet n, n-l, n-2, . . . . 1. La nouvelle valeur de p est lindex de la permutation dans le monde des nn-1 .
106

Figure 5.5. Nouvelles permutations de quatre objets

Considrons le programme 5.6.

107

ALGORITHMIQUJ~

m PROGRA~~W~TION

Ce nouvel algorithme a un avantage important par rapport au prcdent : il


calcule chaque permutation a partir des valeurs de i et. de n, sans consulter les

permutations prcdentes. 11 est donc capable de calculer la permutation i sans engendrer ses prdcesseurs. Cette possibilit sert souvent, car de nombreuses applications ncessitent de tirer au sort une permutation. Apr&s le tirage au sort dun entier albatoire entre 1 et factoriel(n), lalgorithme peut engendrer la permutation correspondante.
5.2.5

a la dfinition suivante : Les 1Cments dans chaque permutation sont considrs comme des cara&res (numriques) dun mot. Ces mots sont organiss en ordre alphabtique (ordre numrique si lon considre les Clments comme les chiffres dun nombre).

5.3.

Exercices

Une version rcursive

Sur les tours dHanoi 1. En supposant que les moines de Hanoi veuillent transfrer le jeu complet de 64 disques, en prenant une seconde pour le dplacement individuel dun disque, combien leur faudrait-il de temps ? La 1Cgende veut que la terminaison du transfert signale la fin du monde. 2. Etablir une courbe de complexitk de lalgorithme des tours dHanoi en mesurant le temps nkessaire au calcul pour diffrents nombres de disques. Lexercice sert faire prendre conscience des problkmes dexplosion combinatoire. 3. Avec 64 disques, et en numrotant les disques d, (le plus petit), d,, . . . . b (Ie plus gros), dans quel ordre d2 parcourt-il les piquets et, en gCn&al, quelle est la rgle pourq ?
FAIT;

Ce nest pas par got de la difficult que les programmes pour les permutations prsents en premier lieu ncessitent une certaine rflection, mais justement pour apprendre raisonner. Evidemment, on peut travailler directement, en Ccrivant une procdure rkursive de manire naturelle (programme 5.7).

VAR pos: TAB [O..max-11; PROC perm(i, n): VAR p, q, j: entier; SI n=l ALORS pos[l]:=l SINON p:=entier(i/n); q:=reste(i/n); perm(p, n-l); POUR j:=n-1 PAR -1 JUSQUA q+l FAIRE pos~]:=pos[j-1] pos[q]:=n FINSI FINPROC

4. Considrons les index des dplacements simples i 1 (le premier coup), i,, . . . Quel est le numro du disque qui bouge au coup ij ?

Programme 5.7. Version rcursive Dans cette procdure, i est lindex de la permutation engendrer, Oli<factoriel(n-1). n est le nombre dlments placer dans pos[O..n-11. La numrotation partir de 0 facilite lutilisation du systme de partie entire et reste, d@ tudi. Ainsi, sil existe plusieurs lments placer (n>l), on place n-l Clments par lappel rcursif, puis on insre le n-ibme sa place en dcalant les q derniers lments chacun dune case vers la droite. Dans le monde des n-l, lindex de la permutation concerne est p (entier(i/n)). Cet algorithme, appel rptition pour les valeurs de i de 0 factoriel(n)-1, engendre les permutations dans lordre alphabtique. Cet ordre, classique, correspond
108

Sur les permutations 5. La possibilit de tirer au sort une permutation est utile dans de nombreuses applications. On reprendra la mthode de gnration des permutations par change de voisins (&5.2.2), en la modifiant afin de pouvoir calculer la permutation numro i sans en engendrer les i-l prkdentes. 6. Ecrire un programme qui engendre les permutations dans lordre alphabtique (la valeur numrique de lentier produit en concatenant, dans lordre, les objets de la permutation i, est plus petite que celle de la permutation i+l) sans utiliser une procdure rcursive. Le programme possdera la proprit de la question prcdente (engendrer une permutation sans engendrer ses pklcesseurs). a 7. On considre les nombres pouvant tre forms partir des chiffres 1, 2, . . . . 9, chaque nombre comportant une et une seule occurrence de chaque chiffre. En
109

ALOORITHMIQUJ3

ET PROGRAMMPLTION

organisant ces nombres dans lordre numerique, quelle est la valeur du 1OO.OOO&me nombre ? (Concours de jeux mathmatiques de France, 1986). 8. Considrons lensemble de valeurs duales des permutations du 85.2.2. (vecteur inv). Nous lavons utilis pour faciliter la recherche de la position courante de chaque objet. Est ce que cet ensemble forme un nouvel ordonnancement des permutations de n objets ? 9. Un thorme bien connu des mathmaticiens est que toute permutation de n objets est un produit unique de cycles. On dmontrera le thorme en laccompagnant dun programme qui identifie les cycles concerns pour une permutation dom&. 10. Le code de Gray est bien connu dans le monde de la transmission dinformations. Il est dfini de la manire suivante : On considre lensemble de caractres forms dun nombre fixe de bits. Comme dhabitude, n bits permettent de disposer de 2 caracti?res. Dans le code de Gray, deux caract&w qui se suivent ne diffrent que par la valeur dun seul bit. On crira un programme qui engendre les caractres successifs du code de Gray sur n bits, sachant que le bit qui change de valeur chaque fois est celui du poids le plus faible possible. Par exemple, pour n=3, on aura lordre suivant : 000
001 011 010 110 111 101 100

Chapitre 6

La marche arrire

Dans ce chapitre nous abordons une nouvelle catgorie dalgorithmes : ceux qui mettent en jeu la rrwrcAe arrire (backtracking). Il sagit de resoudre des problmes non dterministes. Un problbme qui nest pas dterministe ne peut pas tre rsolu de manire directe. Considrons la situation de quelquun qui arrive un carrefour dans un labyrinthe. Il ne sait pas si la bonne dcision est de tourner gauche ou droite. La solution est dessayer une des possibilits ouvertes, en la poursuivant aussi longtemps que possible. Si la dcision mne un blocage, il faut revenir sur ses pas afin dessayer une autre possibilit. Cest cette ide de revenir sur ses pas qui donne lieu lexpression marche arrire.

6.1. La souris et le fromage Nous considrons comme application de la marche arrire un problme de labyrinthe artificiel. Sur un chiquier classique (8 cases par 8), il y a un morceau de fromage que veut manger une souris. Les coordonnes des cases quils occupent sont [xf, yfl et [xs, ys]. Le problme est dtablir un chemin que peut prendre la souris pour aller jusquau fromage, sachant quelle ne peut avancer que dune case la fois, soit horizontalement, soit verticalement, et quil existe des barrires infranchissables entre certaines cases voisines. Le problme est ancien, mais la premire utilisation ayant un rapport avec linformatique que nous connaissons est [Zemanek 19711. Il a t utilis par un des auteurs comme exercice de programmation depuis une cole dt en 1972 et a servi comme support dans un cours dintelligence artificielle [Griffiths 1986, 19871.

110

hGOlUTHMIQUEiETPROGRAhfMKllON

Nous allons donc crire un programme mettant en uvre la marche arriere pour la recherche dun chemin, sans essayer de trouver le chemin le plus court. Le programme prsente les camctkistiques suivantes : - A chaque tour de la boucle principale, il considere, avant, sinon il en fait un en an-Se. sil en existe, un pas en

- Le pas en avant est dans la premire direction disponible (qui na pas encore t essaye) partir de la case courante. - Lordre de disponibilit est : nord, est, sud, ouest, qui sont reprsentes par les entiers 1,2, 3,4. - En arrivant sur une case, le programme la marque. Les marques ne sont jamais enleves. La souris ne repasse jamais en marche avant dans une case dj marque, car lalgorithme essaie toutes les possibilits partir de chaque case visite, et il ne faut pas quil boucle. - Faire marche arrire consiste en un retour vers le prdcesseur de la case courante, suivi dun essai dans la prochaine direction disponible partir de cette case (sil en existe). - La marche arrire rsulte du constat que lon a essay les quatre directions partir dune case sans trouver un chemin. - Pour faire un pas en arriere, il faut savoir do on est venu. Ainsi, a chaque dcision, la direction prise est sauvegarde sur une pile. A chaque retour en arrire, on revient sur la dcision la plus rcente, qui est en tte de la pile. Si la situation est toujours bloque, on prends encore le prkdcesseur, et ainsi de suite. Le programme 6.1 est donn en entier pour faciliter la prsentation, les commentaires tant regroups a la fin.

1 DEBUT DONNEES barrire: TAB [1..8, 1..8, 1..4] DE bool; xs, ys, xf, yf: entier; 2 VAR marque: TAB [1..8, 1..8] DE bool INIT FAUX; 3 pile: TAB [1..64] DE entier; 4 x, y, xn, yn, dir, pl: entier; 5 X:=X~; y:=ys; marque[x, y]:=VRAI; dir:=O; pl:=l; 6 TANTQUE NON (x=xf ET y=yf) ET pl>O 7 FAIRE SI dire4 8 ALORS dir:= dir+l ; 9 SI NON barrire[x, y, dir] 10 ALORS xn:= CAS dir DANS (x, x+1, x, x-l) FINCAS; 11 yn:= CAS dir DANS (y+1 , y, y-l, y) FINCAS; 12 SI NON marque[xn, yn] 13 ALORS x:=xn; y:=yn; marque[x, y]:=VRAI; 14 pile[pl]:=dir; pl:=pl+l ; dir:=O 15 FINSI FINSI SINON PI:=PI-1 ; 16 SI PI>0 17 ALORS dir:=pile[pl]; 18 x:= CAS dir DANS (x, x-l, x, x+1) FINCAS; 19 y:= CAS dir DANS (y-l, y, y+1 , y) FINCAS 20 FINSI FINSI FAIT FIN Programme 6.1. La souris et le fromage Les numeros de ligne servent aux commentaires. 1. barrire[i, j, dir] est VRAI si et seulement sil y a une barriere entre la case [i, j] et sa voisine en direction dir. Le tableau est initialis ailleurs. Le bord de lchiquier est entoure de barrieres pour viter de faire rfrence a des cases inexistantes. 2. [xs, ys] est la position de dpart de la souris, [xf, yfl celle du fromage. 3. marque[i, j] est VRAI si et seulement si la souris a dj visit la case [i, j]. Ce tableau est initialis FAUX. 4. Une pile de 64 Clments suffit. 5. [x, y] est la position courante de la souris. [xn, yn] est une position vise par la souris. dir reprsente le nombre de directions que la souris a dj essayes partir de la case [x, y].

112

113

6. Linitialisation met [x, y] la position de dpart [xs, ys], qui est marque. Aucune direction na t essaytk La pile est vide. 7. On boucle tant que le fromage nest pas atteint et quil existe des chemins essayer. 8. Existe-t-il encore une direction essayer ? 9. Alors on prend la prochaine. 10. Si la route nest pas barrke, on calcule les coordonnes [xn, yn] de la case voisine en direction dit 11,12. dir=l veut dire vers le nord, cest-a-dire augmenter y de 1, et ainsi de suite. 13. Dans labsence de marque sur [xn, yn], on peut y aller. Si la case est marquee, on essaiera la ptochaine valeur de dir au prochain tour de la boucle. 14. Le mouvement a lieu, avec marquage. 15. La dcision est enregistre sur la pile. Pour la nouvelle case le nombre de directions dja essayes est 0. 16. Comme il nexiste plus de directions essayer, il faut faire marche arrire. 17. Si la pile nest pas vide, on prend la direction prise pour arriver sur la case courante, qui se trouve en haut de la pile (18). pl=O arrive quand tous les chemins possibles ont t essays partir de [xs, ys] et le fromage na pas t atteint. Il ny a donc pas de chemin possible. 19,20. On calcule, en inversant les lignes 11 et 12, les coordonnes du prdecesseur, et le programme fait un nouveau tour de la boucle. A la fin du programme, si un chemin existe, la pile contient toutes les dcisions prises sur lesquelles lalgorithme nest pas revenu. Ainsi la pile contient une solution du probleme. 11 nest pas intressant de programmer ici une boucle dimpression, mais le principe de la disponibilit des rsultats acquis est valable pour les algorithmes de marche arrire en gnral. Pendant lexcution du programme, la pile contient a chaque moment les dcisions en cours dessai. 6.1.1. Version rcursive Dans le cas dune recherche complte sur toutes les possibilits, la marche arrire est lquivalent dun parcours darbre ou de graphe (voir $6.1.2). Dans le probleme considr ici, nous sommes en prsence dun graphe, car il serait possible de revenir sur la case que lalgorithme vient de quitter, et lon pourrait boucler. Cela est une autre explication du besoin de marquage, pour empcher le programme de suivre les boucles. Comme dans tous les algorithmes de ce type, on peut crire le programme dans la forme dune procdure rcursive (programme 6.2).

DEBUT DONNEES xs, ys, xf, yf: entier; barr: TAB [1..8, 1..8, 1..4] DE bool; VAR marqu: TAB [1..8, 1..8] DE bool INIT FAUX; existe chemin: bool; PROC jr%dre(x, y) -> bool; VAR trouve: bool; dir: entier; trouv:= x=xf ET y=yf; SI NON marque[x, y] ALORS marqu[x, y]:=VRAI; dir:=O; TANTQUE NON trouv ET dir<4 FAIRE dir:=dir+l ; SI NON barr[x, y, dir] ALORS trouv:=joindre(CAS dir DANS x, x+1, x, x-l FINCAS, CAS dir DANS y+1 , y, y-l, y FINCAS) FINSI FAIT; St trouv ALORS imprimer(x, y) FINSI FINSI; RETOURNER trouv FINPROC; existechemin:=joindre(xs, ys) FIN Programme 6.2. Forme rtkmive Le lecteur ayant tout suivi jusquici naura pas de probleme pour comprendre ce programme.

j I

6.1.2. Marche arrire, arbres et graphes La marche arriere est un moyen dexplorer systmatiquement un arbre de possibilits. Evidemment, lexploration systmatique ne termine que si le nombre de possibilitt% est fini. Il faut aussi noter lexistence de lexplosion combinatoire, o le nombre de possibilits, bien que fini, est tellement grand que lexploration est en dehors des capacits des ordinateurs. La figure 6.1 montre quelques branches de larbre de possibilites pour la souris. Commenant sur la case [xs, ys], elle a quatre directions disponibles (en ignorant des barrires et les limites de lchiquier). Considrons la direction est, menant la case [xs+l, ys]. A partir de cette nouvelle case, quatre directions sont encore imaginables, dresses sur la figure.

114

hOORITHhUQUE FiT PROGRAMMAITON

kGORlTHMlQUE ET PROGRAMMHION

Dans le programme 6.3, np est un entier qui indique le nombre de reines actuellement places. En marche avant, le programme considre la prochaine case [x, y]. Le test 08 au dbut de la boucle sert a passer a la ligne suivante (y:=y+l) en cas de besoin. Si la case [x, y] nest pas couverte par une dame dj place, on peut y placer une nouvelle dame. Si la case nest pas libre, la prochaine case sera considre au prochain tour de la boucle (x:=x+1 la dernire ligne).

[xs+l , ys+l]

4
[xs+2, ys]

l l >

[xs+l , p-11

[xs, ys]
1

Figure 6.1. Arbre de possibilits pour la souris Or, une de ces directions mbne la souris de retour sa case de dpart. Si lon continuait dresser un arbre, il serait de taille infinie, car on recommence indfiniment avec la mme case. Pour reprsenter le labyrinthe par une structure formelle, il faut passer un graphe, avec un nud par case. Les arcs sont toujours bidirectionnels, reliant des cases voisines qui ne sont pas spares par des barrires. Notons que ce nest pas seulement le fait de pouvoir retourner directement vers la case prcdente qui impose un graphe; on peut aussi arriver, par exemple, la case [xs+l, ys+l] en deux pas de deux faons diffrentes (par [xs, ys+l] et par [xs+l, YSI). Le marquage des cases du labyrinthe correspond donc exactement au marquage des nuds dans le parcours dun graphe. Dans le cas du labyrinthe, le graphe est le graphe de connectivit des cases, reprsentes par des nuds. Le programme 6.2 a la forme dun parcours de graphe n-aire.

DEBUT VAR np: entier INIT 0; % Le nombre de reines places % x, y: entier; % coordonnes dune case essayer % x:=1; y:=l; TANTQUE np c 8 FAIRE SI x > 8 ALORS x:=1 ; y:=~+1 FINSI; SIY< ALORS SI libre(x, y) ALORS np:=np+l ; empiler(x, y); FINSI SINON dpiler(x, y); np:=np-1 FINSI; x:=x+1 FAIT FIN Programme 6.3. Les huit reines

Lindication de la ncessit de faire marche arrire est quil ne reste plus de case essayer (y>8). Dans ce cas, on dpile x et y pour revenir dans ltat prcdent, en diminuant le compteur np. Le prochain tour de la boucle considrera la prochaine case. Le processus sar&e quand np arrive a 8, cest-Mire quand solution est trouve. Comme nous savons quune telle solution existe, il nest pas ncessaire de considrer le cas o la marche arrire ne peut pas avoir lieu. Dans ce programme, la pile contient les coordonnes des reines places. Comme il y a deux valeurs a empiler (x et y), il peut devenir intressant de dfinir deux piles, px et py. Dans ce cas, notons que np peut servir pour indiquer la hauteur de chaque pile. Par ailleurs, il nous faut remplacer lappel de la procdure libre par une boucle qui decide si la case (~,y) est couverte par une reine dj place (mme ligne, colonne ou diagonale). On obtient le programme 6.4.

6.2. Les huit reines Il sagit encore dun exercice archi-classique, mais sa prsence ici est toujours justifie par des raisons pdagogiques. Il faut placer huit reines sur un chiquier de telle faon quaucune reine ne soit en prise par rapport une autre. Deux reines sont mutuellement en prise si elles se situent sur la mme ligne, la mme colonne ou la mme diagonale. Le probleme est ancien [Bezzel 18481 [Gauss 18501, mais il a t remis au got du jour avec larrive des ordinateurs. De nombreux articles modernes lui sont consacrs, jusqu dans des revues populaires [Paclet 19841. Il y a 92 solutions, dont videmment un grand nombre dues la symtrie.
116

117

kGORlTHB5QIJEiETPROOlUMMATION

.kOORIIHMIQUE

ET PROGlL4hfMMlON

DEBUT VAR px, py: TAB [1..8] DE entier; np: entier INIT 0; x, y: entier INIT 1; libre: bool; i: entier; TANTQUE np < 8 FAIRE SI x > 8 ALORS x:=1 ; y:=~+1 FINSI; SIY< ALORS libre:=VRAI; i:=O; TANTQUE libre ET ienp FAIRE i:=i+l ; libre:=NON(x=px[ij OU y=py(i] OU abs(x-px[i])=abs(y-py[i])) FAIT; SI libre ;;sS np:=np+l ; px[np]:=x; py[np]:=y SINON x:=px[np]; FINSI; x:=x+1 FAlT FIN y:=py[np]; np:=np-1

DEBUT VAR py: TAB [1..8] DE entier; x, y: entier INIT 1; libre: bool; i: entier; TANTQUE x < 9 FAIRE SI y < 9 ALORS libre:=VRAI; i:=l ; TANTQUE libre ET i<x F;!RIE+;bre:= NON( y=py[i] OU abs(x-i)=abs(y-py[ij)) .= FAIT; SI libre ALORS py[x]:=y; x:=x+1 ; y:=0 FINSI SINON x:=x-l ; y:=py[x]; FINSI; y:=y+l FAIT FIN Programme 6.5. Suppression dune pile Dans cette version du programme, la variable np disparat (sa valeur est la mme que celle de x-l). La variable x est la coordonnke en x de la nouvelle reine a placer, y tant lautre coordonne de la prochaine case essayer. La marche arrire devient ncessaire si y arrive 9, cest--dire sil ny a plus de cases essayer pour la valeur courante de x. Le test libre ne comporte plus de test sur x, car les x sont tous diffrents par construction, x servant galement de pointeur dans la pile des y (il indique la premire place libre dans cette pile). 6.2.2. Une deuxime approche La demiere version ci-dessus ressemble un peu un compteur octal, qui grene tous les nombres en octal de 0 77777777. Pour faciliter la programmation, nous avons numrot les cases de lchiquier de 0 a 7 au lieu de 1 8. Considrons le programme 6.6.

Programme 6.4. Les huit reines, version 2 6.2.1. Une version ambliore La solution ci-dessus marche, mais on peut lamliorer. Profitons du fait que nous savons quil y a une et une seule reine par colonne. Il nest plus ncessaire de gader une pile des x (programme 6.5).

118

119

ALGORITHMIQUE

ET P R O G R A M M A T I O N

DEBUT VAR camp: TAB [0..7] DE entier INIT 0; VAR couvert: bool INIT VRAI; i, j: entier; TANTQUE couvert FAIRE % avancer le compteur % i:=7; TANTQUE comp[i]=7 FAIRE comp[i]:=O; i:=i-1 FAIT; comp[i]:=comp[i]+l ; % tester la position cre % i:=l ; couvert:=FAUX; TANTQUE NON couvert ET k8 FAIRE j:=O; TANTQUE NON couvert ET j-4 FAIRE couvert:=comp[i]=comp[jl OU abs(i-comp[i])=abs(j-comp[jl); j:=j+l FAIT; i:=i+l FAIT FAIT FIN Programme 6.6. Avec un compteur octal Dans ce programme, le compteur (camp) est initialis la valeur zro. Le boolen couvert indique que deux reines se voient. Son initialisation VRAI est une vidence : dans la position de dpart, toutes les reines se voient. La boucle externe tourne tant que la dernikre position examine nest pas la bOMe (aucune paire de reines en prise mutuelle). Le contenu de la boucle est en deux parties : lavancement du compteur et lexamen de la nouvelle position cre. Les 7 droite du compteur deviennent des 0, et le prochain chiffre gauche augmente de 1. Puis, pour chaque reine i (1...7), on teste si la reine j (O...i-1) est compatible. Les tests sarrtent la premi&re incompatibilit. Le nombre de positions essayer avant de dcouvrir toutes les solutions est de 8*, cest--dire 224, ou 16 Mga dans le sens de linformatique (1 Mga = 1024*1024). Considrer un peu plus de 16 millions de cas prend un certain temps. Notons que la marche arrire diminue considrablement ce chiffre. En effet, les premires positions essayes dans lalgorithme du compteur sont les suivantes :

00000000
0 0 0 0 0 0 0 1

00000002 et ainsi de suite. Avec la marche arrire, on dcouvre immdiatement que tout nombre commenant par 0 0 . . . est liminer. La marche arrire donne un algorithme qui considre, dans lordre arithmtique : 00000000 01000000 02000000 02100000 et ainsi de suite. Compter le nombre de cas essays est la& comme exercice. Cette ligne de rflexion pourrait nous amener B considrer, la place du compteur octal, les permutations sur 8 objets. On sait en effet que tous les x, comme tous les y, doivent tre diffrents. Le nombre de cas : considkrer au total est de factoriel(8), cest--dire 40320 ( comparer avec les plus de 16 millions pour le compteur octal). En faisant de la marche arri&re sur les permutations, il deviendrait possible de considrer encore moins de cas. Le programme de dpart dnombre les permutations successives, en utilisant un des programmes du chapitre 5 (programme 6.7). DEBUT VAR i: entier INIT 1, trouv: bool INIT FAUX; TANTQUE NON trouv FAIRE trouv&=libre(perm(i)) i:=i+l FAIT FIN Programme 6.7. Avec despermutations En y ajoutant de la marche arrire, on pourrait arriver au programme 6.8.

120

121

fb3ORITHMIQuE m PROGRAMMKMON

fiLOORITUMIQUJ3

JTI- PROGRAMhMION

DEBUT VAR perm: TAB [1..8] DE entier INIT 0; disp: TAB [1..8] DE bool INIT VRAI; n, i: entier INIT 0; TANTQUE ne8 FAIRE i:=i+l ; TANTQUE i<9 ETPUIS NON disp[i] FAIRE i:=i+l FAIT; SI i<9 ALORS couvert:=FAUX; j:=O; TANTQUE j<n ETPUIS NON couvert FAIRE j:=j+l ; couvert:= n+l-j = abs(i-perm]) FAIT; SI NON couvert ALORS n:=n+l; perm[n]:=i; disp[i]:=FAUX; i:=O FINSI SINON i:=perm[n]; disp[i]:=VRAI; perm[n]:=O; n:=n-1 FINSI FAIT FIN Programme 6.8. Avec permutations et marche arrire Dans ce programme, les permutations essayer sont engendrees en perm. Llement disp[i] du tableau booleen disp indique si lobjet i est disponible, cest-dire sil ne figure pas dj dans la permutation : disp[i] est VRAI si i ne figure pas dans perm[l..n]. n indique le nombre de reines dj placees. A chaque moment, la valeur de i est lindex du dernier objet essay comme candidat la place perm[n+l], qui est la prochaine reine placer.
6.3.

Sur les huit reines 4. On modifiera le programme donn au $6.2.1 afin de trouver toutes les solutions. 5. Ecrire la version rcursive du programme du $6.2.1. 6. Ecrire un programme qui compte le nombre de cas essays en marche arriere sur le compteur octal (voir $6.2.2). 7. Faire la mme chose pour le programme bas sur les permutations,

Dautres applications de la marche arrire 8. Extrait partiel du concours de jeux mathmatiques, 1987. On considre un triangle de cinq lignes :
X xx xxx xxxx xxxxx

Dans chacune des 15 positions, il faut placer un des 15 premiers entiers en respectant la condition suivante : lentier dans chaque position, sauf dans la demiere ligne, a comme valeur la diffrence entre les valeurs de ses successeurs immdiats (on considere le triangle comme un treillis binaire). Un dbut de solution pourrait tre :
7 103

Exercices

Sur la souris et le fromage 1. Ecrire le programme qui ajoute lheuristique, cest--dire qui prend comme direction pn5fMe celle du fromage. 2. Ecrire le programme de numerotation longueur minimale. successive qui trouve un chemin de

11 14 ... Chaque entier ne figure quune seule fois dans le triangle. On crira un programme utilisant la marche arrire pour trouver une solution au problme. Combien y a-t-il de solutions en tout ?

3. Il existe combien de chemins diffrents allant de la case 11, 11 a la case [8,81 sur un chiquier vide ? Une mme case ne peut pas figurer deux fois sur un mme chemin. On &ira un programme qui compte le nombre de chemins.
122 123

Chapitre 7

Transformation de programmes

La transformation de programmes, par des techniques automatiques ou manuelles, devrait intresser linformaticien professionnel pour plusieurs raisons : - La transformation automatique de spcifications en programmes excutables peut diminuer le cot de la production de programmes, tout en amliorant leur fiabilitk - On peut d6couvri.r des versions plus efficaces de certains programmes. - En restant proche de la spcification dun programme, on en facilite la portabilit. - Le programmeur amCliore sa matrise des outils, ce qui nous intresse pour la pdagogie. Le besoin dindustrialiser la production des logiciels a provoqu lintroduction de nombreux mots-cls nouveaux. Ces nologismes, tels que programmation structuree, gnie logiciel, . . . . ne reprsentent pas seulement un phnomene de mode, mais recouvrent aussi une ralit technique. Afin de sassurer de la qualit des produits industriels, on a souvent recours la spcification formelle. Celle-ci sert a la demonstration ventuelle dun programme, sa validation, voire mme sa production. Cest pour ce dernier aspect que les techniques de transformation deviennent utiles. Une spcification est une formulation mathmatique, surtout algbrique, dun problme. Dans les mthodes de dmonstration de programmes, on dispose dune spcification et dun programme, puis on dmontre que le programme est une mise en uvre de la spkcification. Plutt que dmontrer lquivalence de ces deux textes, on est arriv naturellement se poser la question de savoir sil est possible de driver le programme partir de la spcification.

fdLOORIllU5QUE GT P R O G R A M M A T I O N

fiLGORlTHhffQUEETPROGRAM.bMCION

Dans certains cas, la reformulation dun programme peut savrer plus efficace que la version dorigine. Nous avons vu leffet de certaines transformations dans le cadre de programmes r&rsifs dans un chapitre prtkkdent. La transformation dun programme nest possible que si le programme est crit dans une forme propre. Lutilisation dastuces dpendant dun compilateur particulier est proscrire. Notons que cela revient dire les mmes choses que lon retrouve dans les ouvrages sur la portabilit des logiciels [Brown 19771. Enfin, mme si les techniques de transformation navaient aucune application pratique directe, nous les enseignerions quand mme nos tudiants. Effectivement, en examinant diffrentes versions dun mme programme, ils &Parent mieuX ce qui est intrins&que au problme de ce qui dpend du langage, du compilateur, voire de lordinateur particulier quils utilisent un instant-donn. Les paragraphes suivants donnent quelques exemples de transformations. Pour aller plus loin, on peut consulter utilement des ouvrages tels que [Burstall 19751, [Darlington 19731.

! >F. I

Cest en essayant de rdiger la spkification formelle de lalgorithme que nous avons trouv une amlioration. Essayons de dnombrer les cas qui se ptisentent. On consid&re que le mode dun vecteur est un doublet : la longueur de la chane la plus longue et la valeur qui parat dans cette chane. On obtient les cas suivants : 1. n-l: mode(v[l..n]) = (1, V[I]) 2. n>l , v[n]zv[n-11: mode(v[l ..n]) = mode(v[l ..n-11) 3. n>l, v[n]=v[n-11, long(mode(v[l ..n]))>long(mode(v[l ..n-11)): mode(v[l ..n]) = (long(mode(v[l ..n-l]))+l, v[n]) 4. n>l, v[n]=v[n-11, long(mode(v[l ..n]))llong(mode(v[l ..n-11)): mode(v[l..n]) = mode(v[l..n-1]) Lamlioration est venu en essayant de formaliser le test qui dcide si la chane courante est plus longue que le mode trouv jusqualors, cest--dire le test suivant : long(mode(v[l ..n]))>long(mode(v[l ..n-11))

7.1. Revenons sur le mode dun vecteur Ce probl&me a form le sujet du premier vrai programme de cet ouvrage ($2.1). Il a t le sujet de plusieurs tudes [Griffiths 19761, [Arsac 19841, car on peut lamliorer trs simplement, cependant lamlioration reste difficile trouver. Nous avons dcouvert la modification en explorant les techniques de transformation. La version de dpart de cet algorithme tait celle du programme 7.1. DEBUT DONNEES n: entier; v: TABLEAU [l ..n] DE entier; VAR i, lcour, Imax, indm: entier INIT 1; TANTQUE kn FAIRE i:=i+l ; SI v[i]=v[i-1] ALORS Icour:=lcour+l ; SI lcouolmax ALORS Imax:=lcour; indmc-i FINSI SINON Icour:=l FINSI FAIT FIN Programme 7.1. Encore le mode dun vecteur
126

Evidemment, on peut laisser le test sous cette forme, mais on cherche naturellement un test qui dcide si la nouvelle chane est plus longue que lancienne. Cest en posant la question de cette manire que nous avons trouv lide (pourtant simple) de considerer le test suivant : soit lm la longueur du mode de v[l..n-11. Alors, la chane dont v[n] fait partie est plus longue que lm si et seulement si v[n] = v[n-lm]

Il
I

Dans la transformation de la spcification ci-dessus, on remplacera le test sur les longueurs par ce nouveau test. l%r ailleurs, on note que les cas 2 et 4 donne la mme solution (le mode est inchang). Mais, si v[n]#v(n-1), il est certain que v[n]#v[n-hn]. On en dduit que la comparaison de v[n] avec son prdcesseur nest pas ncessaire. La spcification devient : 1. n=l: mode(v[l..n])=(l, V[I]) 3. n>l, v[n]=v[n-long(mode(v[i ..n-l]))]: mode(v[l ..n]) = (long(mode(v[l ..n-l]))+i, v[n]) 4. n>l , v[n]#v[n-long(mode(v[l ..n-l]))]: mode(v(1 ..n)) = mode(v(1 ..n-1))

Le test v[n]=v[n-1] a disparu. En transformant cette spcification vers un programme itratif, on obtient le programme 7.2.

127

hCiO=QUE

ET PROGRAWION

fiLOOWTHMIQUEETPROGRAMh4MION

DEBUT DONNEES n: entier, v: TAB [l ..n] DE entier; VAR i, indm, Im: entier INIT 1; TANTQUE ien FAIRE i:=i+l ; SI $j=@-lm] % regard en arrire ALORS indm:=i; Im:=lm+l % nouveau max FINSI FAIT FIN Programme 7.2. Le mode amlior

parce que 3 est un nombre impair, 3 * 320 = 1 * 640 + 1 * 320. Donc il faut garder les valeurs en face des nombres impairs. En Ccrivant ceci dans la forme dune procdure, on obtient le programme 7.3. mult(a,b):SI a=0 ALORS 0 SINON SI impair(a) ALORS b + mult(a-1, b) SINON mult(a/2, lf2) FINSI FINSI
7.3. Multiplication avec rcursivit

Programme On a gagn une variable et un test par lment du tableau. Cette optimisation, bien que simple, nest pas vidente au dpart

Pour enlever la rcursivit, on introduit une variable res pour le cumul des valeurs retenues (programme 7.4). 7.2. La multiplication des gitans Lide de ce programme nous est venu de P.L. Bauer, qui la incorpor dans son cours de programmation [Bauer 19761. Elle vient de lobservation de la mthode de multiplication utilise par des gens sur des marchs de lEurope centrale. Montrons cette mthode sur un exemple, 25 * 40 : 25 40 12 80 6 160 3 320 1 640 On a divis les nombres de la colonne gauche par deux, en multipliant par deux ceux de la colonne de droite en mme temps. Lopration est rptee autant de fois que possible, en laissant tomber les moitis rsultant de la division par deux dun nombre impair. Par la suite, on supprime, dans la colonne de droite, les valeurs correspondant un nombre pair dans la colonne de gauche. Les nombres restant droite sont additionns : 40+320+640=1000 Ce qui est le rkwltat escompt. Les oprations ci-dessus ne sont pas le tour de passe-passe que lon pourrait imaginer. En fait, 6 * 160 (deuxieme ligne) est strictement gal 3 * 320. Mais,
128

multp(a,b,res):TANTQUE a>0 FAIRE SI impair(a) ALORS res:=res+b; a:=a-1 SINON a:=a/2; b:=b*2 FINSI FAIT Programme 7.4. Suppression de la rtcursivitk
I !

I ,

Comme il sagit dentiers, reprsentes sous forme binaire dans lordinateur, on peut simplifier en faisant appel des dcalages (decg pour dcaler dune place vers la gauche, decd droite). Par ailleurs, si a est impair, a-l est pair, donc le SINON peut disparatre (programme 7.5). multp(a,b,res):TANTQUE a>0 FAIRE SI impair(a) ALORS res:=res+b; a:=a-1 FINSI; decd(a); decg(b) FAIT Programme 7.5. Version avec dcalages
129

akOORIIHMIQUE

ET PROGRAIdMWION

Etant donn que a est dcal droite, a:=a-1 nest pas n6cessaire, car le 1 que lon enBve est le dernier bit droite, supprime de toute faon par le dcalage qui va suivre. Cela nous permet de dduire lalgorithme de multiplication rellement mis en uvre par les circuits dans les ordinateurs (figure 7.1).

On utilise des connaissances intuitives des proprits telles que la commutativit, la distributivit, . . . des oprateurs. Une tude des conditions &essaires et suffisantes pour permettre des transformations ne peut quamliorer notre matrise de loutil de programmation. Bauer signale que le programme de multiplication peut se gnraliser. Le mme schma de programme permet la minimalisation du nombre de multiplications t&essaires pour le calcul dune puissance entire (voir exercice en fin de chapitre).

7.3.

Exercices

1. Utiliser le programme du 57.2 comme schma pour le calcul de ab. 2. Enlever la rcursivit des procdures divfact et div2 du $7.2.

res
Figure 7.1. Multiplication de deux entiers On decale a vers la droite jusqu lanMe dun 1 dans le bit de droite. On dcale dautant b vers la gauche. On ajoute la nouvelle valeur de b dans le registre rsultat. La triple opration est rpte pour chaque bit de a ayant la valeur 1. Lintroduction dune variable permettant de supprimer une rcursivit sans recourir une pile est une technique habituelle. On le fait sans rflchir dans beaucoup de cas. Considrons le programme bateau de calcul dun factoriel : fact(n): SI n=l ALORS 1 SINON n fact(n-1) FINSI
l

Aucun programmeur nhsite crire la boucle suivante fact:=l ; TANTQUE ml FAIRE fact:=fact * n; n:=n-1 FAIT

Mais il hsiterait beaucoup plus sur les fonctions suivantes : divfact(n): SI n=l ALORS 1 SINON n / divfact(n-1) FINSI div2(n): SI n=l ALORS 1 SINON div2(n-1) / n FINSI

130
-

131

Chapitre 8

Quelques structures de donnes particulires

Dans ce chapitre, nous introduisons plusieurs formes nouvelles darbres : les arbres ordonns (ordered trees), les arbres quilibrs (balanced trees) et les Barbm (B-trees)

8.1. Les arbres ordonns Pour quun arbre soit ordonn, il faut attacher une valeur chaque nud. Dans la pratique, comme par exemple dans des systmes de gestion de fichiers ou de bases de donnes, cette valeur est souvent une cl, du type dcrit pour ladressage dispers (voir chapitre 2) ou dun style approchant. Ordonner un arbre revient a faire en sorte que, si la valeur attache au noeud n est val[n], chaque nud dans le sous-arbre dont la racine est le successeur gauche de n a une valeur associe vg tel que vgcval[nl, les nuds droite possdant des valeurs suprieures. Cette dfinition implique quune valeur ne peut figurer quune seule fois dans larbre. La figure 8.1 montre un exemple darbre binaire ordonn (voir page suivante). Larbre de la figure aurait pu tre produit par lalgorithme naturel de construction si les lments considrs staient prsents dans lun des ordres suivants :
71042831 74211083

hOORIIHMIQUE EI- PROGRAMMWION


Yi,a; *,- . ~ _-+ii

>f$s
4 10

-,

Larbre defini par les trois vecteurs succg, succd et val est fourni en entre, avec sa racine, la valeur v trouver ou a placer et lindex pl de la premik case libre dans chaque tableau. Si v est dj dans larbre, n se positionne sur le nud qui porte la valeur v, sinon on cn5e un nouveau nud lendroit adquat de larbre. Avec un arbre ordonn6, la recherche dun objet se fait plus vite quavec les algorithmes prcdents (4.3.3), qui examinaient tous les nuds de larbre. Lordonnancement permet de suivre toujours le bon chemin, cest--dire celui sur lequel est, ou sera, le nud possdant la valeur recherche. Le nombre de tests dpend ainsi de la profondeur de larbre.
8.2. Les arbres quilibrs

x 1 3

Figure 8.1. Un arbre binaire ordonn


/

Ces ordres sont des ordres partiels de parcours de larbre, o lordre partiel doit respecter la rgle suivante : un nud devance, dans lordonnancement, ses successeurs. Lalgorithme naturel de construction de larbre est aussi celui qui sert rechercher la prsence dun objet dans larbre (programme 8.1). DEBUT DONNEES succg, succd, val: TABLEAU [l ..taille] DE entier; v, racine, pi: entier; VAR n: entier; n:=racine; TANTQUE wval[n] FAIRE SI v<val[n] ALORS SI succg[n]>O ALORS n:=succg[n] SINON succg[n]:=pl; n:=pl; PI:=PI+~; succg[n]:=O; succd[n]:=O; val[n]:=v FINSI SINON SI succd[n]>O ALORS n:=succd[n] SINON succd[n]:=pl; n:=pl; pl:=pl+l ; succg[n]:=O; succd[n]:=O; val[n]:=v FINSI FINSI FAIT FIN Programme 8.1. Arbre ordonnk
134

/ /

Lalgorithme du paragraphe prcdent amliore la vitesse de recherche dun objet, mais le nombre de nuds examiner nest pas encore minimis. En effet, considrons larbre (figure 8.2) qui rsulterait dune arrive dobjets dans lordre suivant : 12347810

Figure 8.2. Arbre ordonn inejkace

135

fiLGO-QUEETPROGRAhtMATION

Les recherches sont minimises seulement si larbre est quilibr, ou du moins si la profondeur maximale de larbre est minimise. La figure 8.3 montre larbre de lexemple dans sa forme quilibn5e.

Cet arbre nest plus quilibr, car le nud de valeur 4 a 3 noeuds dans son sousarbre gauche et 5 droite. Pour le requilibrer, il y a plusieurs possibilits, dont celle de la figure 8.5.
6

5
1 3 7 10

Figure 8.3. Equilibrage de larbre de la figure 8.1 Soient ng le nombre de nuds dans le sous-arbre gauche dun nud donn, nd le nombre de nuds dans le sous-arbre droit. Alors un arbre est quilibr si et seulement si abs(ng-nd)<2. Si, pour tout nud, ng=nd, larbre est strictement quilibr, comme celui de la figure 8.3. Cela ne peut arriver que si le nombre de nuds le permet (nombre total de noeuds = 2 - 1). On voit que lalgorithme de recherche dans un arbre ordonne et quilibr a une efficacit dordre o(log2(n)), tandis que lalgorithme classique est dordre o(n), avec n le nombre de noeuds. Ajoutons maintenant deux nuds, de valeurs 6 et 11, larbre prcdent. On obtient larbre de la figure 8.4.
4

Figure 8.5. Rquilibrage de larbre de la figure 8.4 Notons que larbre de la figure 8.4 est en fait aussi bon que celui de la figure 8.5 en ce qui concerne lefficacit des recherches, car leurs profondeurs sont les mmes. On peut dfinir la profondeur dun arbre comme tant la longueur du plus long chemin menant de la racine une feuille. Lefficacit des recherches se mesure en calculant le nombre de tests ncessaires pour atteindre chaque objet de larbre, ou pour en placer un nouveau. Cest dans ce sens que les arbres des figures 8.4 et 8.5 sont defficacite Cgale (voir paragraphe ci-dessous sur les B-arbres).

8.2.1. Algorithmes de manipulation darbres Cquilibrs La cration dun arbre quilibr partir dun ensemble dobjets ordonns nest pas difficile. Commenons avec un vecteur v qui contient les valeurs, dans lordre numrique. On peut considrer le programme 8.2.

11

Figure 8.4. Avec deux nuds en plus


136 137

hGORITHMIQUE ET PROGRAMMtWION

DEBUT DONNEES n: entier; v: TABLEAU (1 ..n] DE entier; VAR val, sg, sd: TABLEAU [l ..n] DE entier; pl: entier lNIT 1; racine: entier; PROCEDURE arb(i, j: entier) RETOURNER entier; VAR x, k: entier; SI i=j ALORS val[pl]:=v[i]; sg[pl]:=O; sd[pl]:=O; pl:=pl+l ; RETOURNER (~1-1) SINON SI j>i ALORS k:=(i+j)/2; X:=~I; pl:=pl+l ; val[x]:=v[k]; sg[x]:=arb(i,k-1); sd[x]:=arb(k+l,j); RETOURNER x SINON RETOURNER 0 FINSI FINSI FINPROC; racine:=arb(l ,n) FIN Programme 8.2. Arbre quilibr La procCdure arb procbde par dichotomie, en retournant lindex de la racine de larbre quelle cre. Elle cn5e un arbre quilibr avec les valeurs v[i]..vu]. Si i=j, lunique valeur est insr6e dans un nouveau nud, qui est en fait une feuille. Si icj, lindex mdian k est calcul. Un nud est cr pour VF]. Les successeurs de ce nud sont calcul& par des appels de arb avec les valeurs a gauche de k, puis les valeurs B droite. On retourne lindex du nud cr&. Enfin, si i>j, aucun lment nest insrer, donc la procda retourne la valeur 0 (successeur vide). Le problme se corse quand on dispose dun arbre quilibr6 et que lon veut y insrer un nouvel objet. Lopration est intrinsquement coteuse, les algorithmes directs &ant particuli&rement lourds.

une lecture sur support externe (en gnral un disque). La multiplication des enMessorties est un facteur dterminant pour lefficacit du syst&me global, surtout dans un contexte conversationnel, o les temps de rr5ponse ne doivent pas dpasser certaines limites. Ce paragraphe reprend des notions exposes dans [Miranda 19871, qui traite de tous les aspects des bases de donnes. Les B-arbres [Bayer 19711, [Bayer 19721 apportent une solution efficace au probEme, sans pour autant prtendre une efficacit parfaite. Ils reprsentent un point dquilibre, vitant les algorithmes coteux de restructuration que ncessitent les arbres binaires quilibtis. Un B-arbre nest jamais binaire. Un nud possde au plus 2*d+l successeurs et 2*d tiquettes, o d est la densit du B-arbre. Ce sont des arbres ordonns et quilibrs. Dans le vocabulaire des B-arbres, le terme quilibr& se dfinit de la manire suivante : la longueur dun chemin de la racine jusqu une feuille est la mme, quelque soit la feuille. Nous prsentons ici les B-arbres les plus simples, avec d=l, cest--dire que chaque nud comporte au plus deux tiquettes (les valeurs du paragraphe pr&dent, appeXes cls dans le langage des bases de donnes) et trois successeurs. La figure 8.6 montre un tel B-arbre, complet deux niveaux (8 &iquettes pour 4 nuds).

Figure 8.6. Un B-arbre complet avec d=l Par ailleurs, on applique la rbgle quun B-arbre doit tre rempli au moins 50%, ce qui revient dire, pour d=l, que chaque noeud comporte au moins une tiquette. Un nud qui nest pas une feuille a au moins deux successeurs. Pour comprendre la faon de construire un B-arbre, ou de rechercher un objet, prenons lexemple de larrive dobjets dans lordre le plus difficile, soit des objets comportant des tiquettes 1,2,3, . . . Les tiquettes 1 et 2 se logent dans la racine (figure 8.7).

8.3. Les B-arbres La minimisation du nombre de noeuds a examiner pendant le parcours dun arbre est particulirement intressante pour la recherche dun fichier a travers le catalogue tenu par le syst&me dexploitation, ou pour les accs a des objets dans des bases de donnes. En effet, dans ces cas, chaque examen dun nud peut ncessiter
138

Figure 8.7. Aprs lecture des 1 et 2 A la lecture du trois, il faut introduire un nouveau niveau, car la racine est pleine (figure 8.8).
139

fiLOORIlWMIQUE

ET PROGRAMhhWON

&GORlIFMQUE

ET PROGRAMh4tUTON

Le 7 a fait clater le nud qui contenait 5 et 6. Il ny avait pas de place pour le 6 dans le nud au-dessus, qui contenait dj 2 et 4. A son tour, ce nud clate, provoquant larrive dune nouvelle racine. Cela augmente la profondeur de larbre. Figure 8.8. Aprs lecture du 3 Le 4 peut sajouter dans la feuille qui contient dj le 3. Mais larrive du 5 provoque nouveau un bouleversement, car on voudrait rajouter dans ce mme noeud. Comme il ny a pas la place pour une troisime tiquette, le 5 provoque lclatement du nud comportant le 3 et le 4. Lclatement consiste en la remonte de ltiquette intermdiaire (dans ce cas le 4), en crant deux nouveaux noeuds pour les tiquettes dcentralises (ici 3 et 5). Le rsultat se trouve en figure 8.9. Notons que la profondeur augmente toujours par ladjonction dune nouvelle racine. Cette propriet assure la continuite de la dfinition de B-arbre, o chaque feuille est la mme distance de la racine. Les noeuds sont galement remplis 50% dans le plus mauvais des cas, car chaque nud cr reoit toujours une tiquette. La figure 8.10 montre les limites de lalgorithme simple, car larbre comporte trois niveaux pour un ensemble dtiquettes qui nen ncessite que deux. Cela rsulte du choix de donnes de dpart, o nous avons considr lordre le plus mauvais. Le rsultat est nanmoins tout fait acceptable, surtout en sachant quun ordre alatoire darrive des tiquettes mne un bien meilleur taux de remplissage des nuds. Dans les bases de donnes, ou dans le stockage de fichiers en gnral, les Ctiquettes sont des cls dans le sens de ladressage disperse (voir chapitre 2). Sauf accident, ceci assure une distribution correcte de valeurs. Si lefficacit tait vraiment critique, on aurait pu viter lintroduction dun nouveau nud dans la figure 8.10. Rappelons que ltiquette 7 fait clater la case qui contenait auparavant le 5 et le 6. La monte du 6 fait clater la racine, qui comportait le 2 et le 4. La remonte du 4 cre une nouvelle racine. En fait, il y a une place pour le 4 cte du 3, comme dans la figure 8.11.

Figure 8.9. Aprs lecture du 5 On voit que le 4 peut se loger au niveau au-dessus, car le nud comportant ltiquette 2 nest pas plein. A son tour, le six se logera dans la case qui contient dj le 5, car il reste un espace. Larrive du 7 va encore provoquer des clatements (figure 8.10).

/ /

Figure 8.ll. Rkorganisation

pour viter dintroduire un niveau

Il a galement fallu rorganiser le trio 5, 6, 7. On note que retrouver une organisation optimale est loin dtre trivial. En gnral, on accepte lalgorithme de base, qui est raisonnable. Des valuations des cots des diffrentes oprations se trouvent en [Bayer 19721, [Knuth 19731, [Aho 19741. Figure 8.10. Aprs lecture du 7 Le programme 8.3 recherche une valeur dans un B-arbre, en lintroduisant dans larbre si elle ny est pas encore.

140

141

i t

DONNEES sg, SC, sd, valg, vald, pred: TAB [1 ..taille] DE entier; v , racine: entier; DEBUTVAR n. pos. orph, nn: entier; fini, trouve: bool; h:=racine; trouti:= v=valg[n] OU v=vald[n]; TANTQUE sg[r+O ET NON trouve FAIRE SI vsvalg[nl ALORS n:=sg[n] SINON SI v>vald[n] ET vald[n]>O ALORS n:=sd[n] SINON n:=sc[n] FINSI FINSI; trouv:= v=valg[n] OU v=vald[n] FAlT; SI NON trouv ALORS orph:=O; fini:=FAUX; mNTQUE NON fini FAIRE SI vald[n]=O ALORS fini:=VRAl; SI n=O ALORS nnxouveau-nud; sg[nn]:=racine; sc[nn]:=orph; sd[nn]:=O; pred(nn]:=O; valg[nn]:=v; vald[nn]:=O; pred[racine]:=nn; pred(orph]:=nn; racine:=nn SINON SI v>valg[n] ALORS vald(n]:=v; sd[n]:=orph SINON vald[n]:=valg[n]; valg[n]:=v; sd[n]:=sc[n]; sc[n]:=orph FINSI FINSI SINON nn:=nouveau noeud: SI v<valg[n] - ALORS Bchange(v,valg[n]); pos:=l ; valg[nn]:=vald[n] SINON SI v>vald(n] ET vald[n]>o ALORS valg[nn]:=v; v:=vald[n]; pos:=2 SINON valg[nn]:=vald[n]; pos:=3 FINSI FINSI; vald[n]:=O; pred[nn]:=pred[n]; CAS pos DANS 1: sc[nn]:=sd[n]; sg[nn]:=sc[n]; sc[n]:=orph, 2: sg[nn]:=sd[n]; sc[nn]:=orph, 3: sg[nn]:=orph; sc[nn]:=sd[n] FINCAS; sd(n]:=O; sd[nn]:=O; n:=pred[n]; orph:=nn FINSI FAlT FINSI FIN

La complexiti de cet algorithme montre le travail faire pour mettre en route ce genre de technique. Le programme qui restructure un B-arbre pour lui donner une repr&entation optimale est encore plus difficile. Un traitement complet de ces problkmes dpasse la port& de cet ouvrage. 8.4. Exercices

1. Ecrire un programme qui dcide si un arbre binaire donn est ordonn.

2. Ecrire un programme qui dcide si un arbre binaire donn est quilibr.

Programme 8.3. B-urbres

142

143

Bibliographie et rfrences

CACM : Communications of the Association for Computing Machinery JACM : Journal of the Association for Computing Machinery LNCS : Lecture Notes in Computer Science
AHD A.V., HOPCROET J.E., ULLMANN J.D., The design and analysis of computer algorithms, Addison Wesley, 1974. AK) A.V., HOPCROFT J.E., ULLMANN J.D., Data structures and algorithms, Addison

Wesley, 1983.
&SAC J., Premires leons de programmation, CEDIC/Fernand Nathan, 1980. kSAC J., Les bases de la programmation, Dunod, 1983. ARSAC J., La conception des programmes, Pour la science, numro spcial 85,

novembre 1984, (dition franaise de Scientific American). EL., EI~K~. J., (eds.), Advanced course in compiler construction, Springer Verlag, LNCS 21, 1974. BAVER F.L., (ed.), Software engineering, Springer Verlag, LNCS 30, 1975. BAUI!R EL., Samekon K., (eds.), Language hierarchies and interfaces, Springer Verlag, LNCS 46, 1976. BAUFR EL., BROY M., (eds.), Program construction, Springer Verlag, LNCS 69, 1979. BAYER R., Binary B-trees for virtual memory, Proc. ACM-SIGFZDET Workshop, New York, 197 1. BP;YER R., Mc G~GB~ E., Grganisation and maintenance of large ordered indexes, Acta Informatica 1,3, p. 173-189, 1972. Bmoux P., B IZARD Ph., Algorithmique : construction, preuve et &Valuation des programmes, Dunod, 1983. BJZEXT D., MONET P., Les tours dHanoi, ou comment changer de disques, OI, janvier 1985.
BAWR

&GORlTHhtlQUEJZTPROGRAhfhMTON

BEZEL Max, Schach zeitung, 1848. BOUSSARD J.-C., MAHL. R., Programmation avance : algorithmique et structures de donnees, Eyrolles, 1983. BROWN P.J., (ed.), Software portability, Cambridge university press, 1977. B~JRSXU R.M.,DARUNcrroN J., Some transformations for developping recursive programs, SIGPLAN notices 10, 6, juin 1975. CARRoLL L., Symbolic logic, part 1 : elementary, Macmillan 1896, traduit en franais par GHIXNO J. et COUMIZ E., La logique sans peine, Hermann, 1966. COUR~ J., KWARSKI I., Initiation h lalgorithmique et aux structures de donnes, 1 - programmation structure et structures de donnes lmentaires, 2 rcursivit et structures de donnes avances, Dunod, 1987. Cm P.Y., GRIFFITHS M., S~BOU P.C., Construction mthodique et vrification systmatique de programmes : lments dun langage, Congrs AFCET, novembre 1978. CUNIN P.Y., G-S M., VOIRON J., Comprendre la compilation, Springer Ver&, 1980. DARLING-~~N J., B-AU R.D., A system which automatically improves programs, 3rd. conf on arhjkial intelligence, Stanford, aot 1973. DUKSTRA E.W., A discipline of programming, Prentice Hall, 1976. FEIGENBAUM E.A., FE%DMAN J., (eds.), Computers and thought, McGraw Hill, 1963. GAUSS Carl, 1850, (lettre du 2/9). GERBIER A., (nom collectif), Mes premires constructions de programmes, Springer Verlag, LNCS 55, 1977. GMGRASSO V.M., GIRZI T., Projet DU-II, Universite Aix-Marseille III, 1989. GREGOIRE, (nom collectif), Informatique-programmation, tome 1 : la programmation structuree, tome 2 : la spcification rcursive et lanalyse des algorithmes, Masson, 1986, 1988. GRIES D., The Schorr-Waite marking algorithm, dans [Bauer 19791. GRIES D., The science of programming, Springer Verlag, 1981. GRIFFTIHS M., Analyse dterministe et compilateurs, thse, Grenoble, octobre 1969. GFUFRTHS M., Program production by successive transformation, dans [Bauer 19761. GRIFFITHs M., Development of the Schorr-Waite algorithm, dans [Bauer 19791. GRIFFITHs M., Mthodes algorithmiques pour 19ntelligence artificielle, Hem&, 1986. GRIFFUHS M., PALI~~ER C., Algorithmic methods for artificial intelligence, Herrds, 1987. GRIFFITHS M., VAYS!%DE M., Architecture des systmes dexploitation, Herms, 1988. m D., Antiquit binaire : la multiplication gyptienne, OI no 54 bis, dcembre 1983. J&S, Qui est mcanicien ?, Jeux et stratgie 26, p. 84, avril-mai 1984. 146

m D.E., The art of computer programming, vol. 3 : Sorting and searching, Addison Wesley 1973. LEDGARD HF., Programmingproverbs, Hayden, 1975, traduit en franais par ARtSAC J., Proverbes de programmation, Dunod, 1978. LUCAS M., PEYRIN J.-P., SCHO~ P.C., Algorithmique et reprsentation des donnes, 1 - Files, automates dtats finis, Masson, 1983a. LUCAS M., Algorithmique et reprtsentation des donnes, 2 - Evaluations, arbres, graphes, analyse de textes, Masson, 1983b. Mc Cm J., Recursive computations of symbolic expressions and their computation by machine, part 1, CACM 3, 4, avril 1960. MEYHZ B., BAUDOIN C., Mthodes de programmation, Eyrolles, 1978. MIR~NDA S., BUS-~A J.-M., Lart des bases de donnes, Tome 1 : Introduction aux bases de donnes, Eyrolles 1987. OXUSOFF L., RAIXY A., Lvaluation smantique en calcul propositionnel, thses, Luminy, janvier 1989. PACLFT Ph., Aventures en noir et blanc, J&S no 26, p. 28-30, avril-mai 1984. QUINJZ W.V.O., Methods of logic, Holt, Rinehart & Winston, 1950, traduit en franais par CLAVELIN M., Armand Colin, 1972. SAMUEU A.L., Some studies in machine leaming using the game of checkers, IBM jnl. of res. and dev., 3, 1959, rimprim dans [Feigenbaum 19631. SCHOU P.C., Algorithmique et reprsentation des donnes, 3 - rcursivitt et arbres, Masson, 1984. SCHORR H., WABE W.M., An efSicient machine independentprocedure for garbage collection in various list structures, CACM, aot 1967. SIEC~, P., Reprsentation et utilisation de la connaissance en calcul propositionnel, thse, Luminy, 1987. SMUUYAN R., The lady or the tiger, Knopf 1982, (traduit en franais par MARIHON J., Le livre qui rendfou, Dunod, 1984). WARSHALL S., A theorem on Boolean matrices, JACM 9, 1, janvier 1962. Wl~m N., Algotithms + Data Structures = Programs, Prentice Hall, 1976. WIRM N., Systematic programming : an introduction, Prentice-Hall, 1973, (traduit en franais par LECARME O., Introduction la programmation systmatique, Masson 1977).

147

fiLOORlTHh4IQUB

III- PROGRAhfMKl-ION

Graphe Infix Iuvariant Largeur dabord

Graph Infixed Invariant Breadth first search Backtracking

4 4.3.4 2.2.1 4.3.4 6 4.3 2.2.2 3.4.1 2.2.1 4.3.4 2.2.1 4.3 4.3.4 4.2 4.3.4 4.3 4.5.1 5 5 2.4 4.5.1 2.4 4.3 4 3

Marche arrire Nud

Glossaire

OUALORS Pile Post-condition Postfix Pr&condition I , predecessellr Prfix Producteur Profondeur dabord
2.5 4 8 8 2.4 8 4.3 2.2.3 2.5 2.5 4.2 4.1 4.3.1 2.3 4.1 2.1.1 2.2.2 4.5.4 4.3 4 2.2.3

COR Stack ou LIFO Postcondition Postfured Precondition Predecessor Prefixed


Ph.llXI

1. Franais - anglais Adressagedisperse Adne Arbre quilibti


Arbre OdOM

Depth first search Root Garbage collection Induction Recursion Program scheme Specification Successor Lattice Sort

Assertion B-arbre Branche CHOIX ClasSe Cl Consommateur Dpiler Diagramme de vigne Diviser pour rgner Empiler Etat du monde ETPUIS Fermeture transitive Feuille File FINCHOIX

Hash code TiW? Balancedtree ordedtree Assertion B-tree Branch CHOICE Class Key Consumer Pull Vine diagram Divide and conquer Push State of the world CAND Transitive closure Queue ou FIFO ENDCHOICE

Racine Ramasse-miettes Rcurrence Rcursivit Schma de programme Schorr-Waite Spcification Successeur Treillis Tri 2. Anglais - franais Assertion B-Tiee Backtracking Balancedtree CAND CHOICE Class Consumer COR 149

Assertion B-arbre Marche arriere Arbre quilibn5 ETPUIS CHOIX Classe Consommateur OUALORS

2.4 8 6 8 2.2.2 2.2.3 2.5 4.2 2.2.2

Divide and conque1 ENDCHOICE FIFO Garbage collection Graph Hash code Induction Invariant Key Lattice LIFO

Diviser pour rgner FINCHOIX File Ramasse Graphe Adressage Rcurrence Invariant Cl Treillis Pile Arbre ordonn miettes disperse

3.3 2.2.3 4 4.5.1 4 2.5 5 2.2.1 2.5 4 4.4.1 8 2.2.1 2.2.1 4.2 2.4 4.1 4.1 4 5 4.5.1 3 3.4 4.4.1 4.5.4 4 4.3.1

Solutions de quelques exercices

CHAPITRE 2

Question 1 Voir chapitre 7. Question 2 Supposons que nous arrivons dans la situation suivant : objet=15, i bas=centre=7, haut=8, t[T=lO, t[8]=20

Postcondition Precondi tion Pmducer Program scheme Pull Push Queue Recursion Schorr-Waite sort Specification Stack Transitive Tm closure

Post-condition Pr-condition Producteur Schma de programme Dpiler Empiler File Rcursivit Tri Spcification Pile Fermeture transitive Diagramme de vigne

Alors, lobjet nest pas trouv et t[centre]cobjet. La prochaine valeur de centre sera entier((7+8)/2), cest-Mire 7. Lalgorithme boucle.
CHAPITRE 3

Question 1 DONNEES n: entier; t: TABLEAU [O..n] DE entier; PRECOND n>O; DEBUT VAR i, j, temp: entier; MONDE i: t[l ..i] est tri; i:=l ; t[O]:= - maxint TANTQUE i<n FAIRE i:=i+l ; MONDE j est la position du trou, temp:=t[i]; j:=i; TANTQUE j>l ET t[i-l]>temp FAIRE tjjj:=tjj-11; j:=j-1 FAIT; t[j]:=temp FAIT FIN POSTCOND t[ 1 ..n] est tri

Vme diagram

150

fiLGORITHMlQUFifTPROGRAMMATION

Le problme vient du fait que quand j=l, tu-11 nexiste pas. On cre un t[O], fictif, en linitialisant au plus petit entier ngatif. En fait, cette initialisation nest pas ncessaire, car si t[O] est tifrence, il nest plus vrai que j> 1. ,
CHAPITRE 4

PROC cousin_germain(a, b: chaine(8), SI n=O ALORS RETOURNER (FAUX, 0)

n: entier) -> (bool, entier, entier):

SINON

Question 1
PROC trouver(nom: chaine(8), n: entier) -> (bool, entier):

VAR b: bool, x: entier; SI n=O ALORS RETOURNER (FAUX, 0) SINON SI val[n]=nom ALORS RETOURNER (VRAI, n) SINON (b, x):= trouver(nom, sg[n]); SI b ALORS RETOURNER (b, x) SINON RETOURNER trouver(nom, sd[n]) FINSI FINSI FINSI FINPROC PROC parent(p, enf: chaine(8), n: entier) -> (bool, entier): VAR b: bool, x: entier; SI n=O ALORS RETOURNER (FAUX, 0) FINSI: SI val[n]=p ALORS SI val[sg[n]]=enf OU val[sd[n]]=enf ALORS RETOURNER (VRAI, n) FINSI FINSI; (b, x):= trouver(p, enf, sg[n]); SI b ALORS RETOURNER (b,x) SINON RETOURNER parent(p, enf, sd[n]) FINSI FINPROC

((val[sg[sg[n]]=a OU val[sd[sg[n]]=a) ET (val[sg[sd[n]]=b OU val[sd[sd[n]]=b)) OU ((val[sg[sg[n]]=b OU val[sd[sg[n]]=b) ET (val[sg[sd[n]]=a OU val[sd[sd[n]]=a)) ALORS RETOURNER VRAI FINSI; SI cousin_germain(a, b, sg[n]) ALORS RETOURNER VRAI SINON RETOURNER cousin_germain(a, b, sd[n]) FINSI

SI

FINSI FINPROC

1
4

PROC aeul(a, enf, n) -> bool: VAR b:. bool, x, y: entier; (b, x):=trouver(a, n); SI b ALORS (b, y):=trouver(enf, x); RETOURNER b FINSI FINPROC
REMARQUE.

- Cette version suppose quun nom ne peut figurer quune fois dans larbre. Elle nassure pas limpression, qui pose le problme particulier de lordre dimpression demand. Supposons une prockdure trouver-bis, qui comporte un ordre dimpression. Alors les rsultats seraient imprims avec les noms des enfants avant ceux de leurs parents. Le plus simple est dempiler le nom lintrieur de la procdure (dans une pile globale) et dimprimer la pile la fin du programme.

Question 3 Un tel arbre comporte (2-1) noeuds et 2- feuilles.


I

CHAPITRE 5

Question 1 Le nombre de dplacements est 2@ - 1 (voir $5.1.1)

152

153

fiLGONMMIQLJE

JT PROGRAMhfATION

2 - 1 secondes = 18446744073709551615 secondes = 307445734561825860 minutes 15 secondes = 5124095576030431 heures 15 secondes = 213087315667934 jours 15 heures + . . . = 5 838 008 664 843 annes 239 jours + . . . (avec lanne 365 jours, cest-Mire sans prendre en compte les annes bissextiles). En arrondissant a 5 800 milliards dannes, on voit que la fin du monde nest pas pour demain. Le rsultat est un bel exemple de lexplosion combinatoire. Question 3 Lordre est invers a chaque changement de disque, cest--dire que 4 fait des cercles dans le sens oppose celui de d, et, plus grukalement, di tourne dans le oppos a 4-1. Question 4 DEBUT DONNEES i: entier; VAR n: entier; n:=l; TANTQUE 2 DIVISE i FAIRE i:=i/2; n:=n+l FIN

perm: TABLEAU (1 :n) DE integer INIT 1;


nn:=n;

TANTQU E nn> 1 FAIRE (p,q):=i DIVPAR nn; SI q=O ALORS p:=p-1 ; q:=nn FINSI; j:=n+l ; TANTQUE q> 1 FAIRE j:=j-1; TANTQUE perm(j)>l FAIRE j:=j-1 FAIT; FAIT q:=q-l perm(j):=nn; nn:=nn-1; FAIT Question 6 Ordre alphabtique, toujours pour tirer le i-ieme permutation. Cest la mme chose en plaant les objets a lenvers TANTQUE n>O FAIRE n:=n-1 ; (p,q):=i DIVPAR fact(n); SI q=O ALORS p:=p-1; q:=fact(n) FINSI; j:=l; TANTQUE perm(j)>O FAIRE j:=j+l FAIT; TANTQUE j<p FAIRE j:=j+l ; TANTQUE perm(j)>O FAIRE j:=j+l FAIT FAIT; perm(j):=p+l ; i:=q FAIT Question 8 Oui. Chacun des fact(n) permutations est diffrente des autres (la relation avec la permutation dorigine est bi-univoque). Comme elles sont toutes diffrentes et il y a le compte . . .

i:=p+l

FAIT

i est lindex du coup, n lindex du disque qui bouge coup i. Tous les coups impairs sont du disque d,. Pour les impairs, on divise i par deux, en enlevant le disque 1 (passage au monde des 2) . . . Voir le calcul du pivot dans le problme des permutations. Question 5 Le pivot marche toujours de droite gauche et on gnre le i-ime permutation de n objets

154

155

&GORlTHhfIQUEETPROGRAMhUTION

CHAPITRE 6

Question 8 DEBUT tab: TAB [i ..5, 1..5] DE entier INIT 0; occup: TAB [0..16] DE bool INIT FAUX; i: entier INIT 1; j: entier INIT 0; p, q, val: entier; marche: bool; TANTQUE i<6 FAIRE j:=j+l ; TANTQUE occup[j] ET je16 FAIRE j:=j+l FAIT; SI je16 ALORS tab[5,i]:=j; occup[j]:=VRAl; p:=5; q:=i; marche:=VRAI; TANTQUE p>l ET q>l ET marche FAIRE val:=abs(tab[p,q]-tab[p,q-11); SI occup[val] ALORS marche:=FAUX; TANTQUE pc6 FAIRE occup[tab[p,q]]:=FAUX; tab[p,q]:=O; p:=p+l ; q:=q+l FAIT SINON p:=p-1 ; q:=q-1 ; tab[p,q]:=val; occup[val]:=VRAI FINSI FAIT; SI marche ALORS i:=i+l ; j:=O FINSI SINON i:=i-1 ; x:=5; y:=i; j:=tab[5,i]; TANTQUE y>0 FAIRE occup[tab[x,y]]:=FAUX; tab[x,y]:=O; x:=x-l; y:=~-1 FAIT FINSI FAIT FIN
CHAPITRE 7

Question 1 Version avec heuristique :


PROCEDURE prochain(dir) RETOURNER entier;

RETOURNER(9 dir=O ALORS dirpref(x,y,xf,yf) SINON SI dir=4 ALORS 1 SINON dir+l FINSI FINSI) FINPROC; PROCEDURE dirpref(x,y,xf,yf) RETOURNER entier; RETOURNER(SI xcxf ET y<=yf ALORS 1 SINON SI y<yf ET x>=xf ALORS 2 SINON SI x>xf ALORS 3 SINON 4 FINSI FINSI FINSI) FINPROC; X:=X~; y:=ys; marque(x,y):=true; dir:=O; empiler(O); TANTQUE xoxf OU yoyf FAIRE SI prochain(dir)odirpref(x,y,xf,yf) ALORS dir:=prochain(dir); xn:= CAS dir DANS (x,x+1 ,x,x-l) FCAS; yn:= CAS dir DANS (y+1 ,y,~-1 ,y) FCAS; SI NON barrire(x,y,dir) ETPUIS NON marque(xn,yn) ALORS x:=xn; y:=yn; marque(x,y):=vrai; empiler(dir); dir:=O FINSI SINON desempiler(dir); SI dir=O ALORS -- il ny a pas de chemin FINSI; x:= CAS dir DANS (x,x-l ,x,x+1) FINCAS; y:= CAS dir DANS (y-l ,y,~+1 ,y) FINCAS FINSI FAIT

Question 1 puiss(a,b): SI b=O ALORS 1 SINON SI impair(b) ALORS a * puiss(a, b-l) SINON puiss(a*a, b/2) FINSI FINSI
157

156

h3ORITHMIQuE

IS PROGRAhfhCWlON

fiL,GORIT?MQUEJ%TPROGRAhAMKClON

puiss(a,b,res):

res:=l ; TANTQUE b>O FAIRE SI impair(b) ALORS res:=res*a; b:=b-1 FINSI; a:=a*a; b:=b/2 FAIT

CHAPITRE 8

Question 1 DEBUT DONNEES taille, racine: entier; sg, sd, val: TAB (1 ..taille) DE entier; PROCEDURE ord(n, min, max: entier) RETOURNE bool; SI n=O ALORS RETOURNER VRAI SINON SI val(n)>max OU val(n)<min ALORS RETOURNER FAUX SINON RETOURNER(ord(sg(n), min, val(n)-1) ET ord(sd(n), val(n)+l, max)) FINSI FINSI FINPROC; imprimer(ord(racine, - maxint, maxint)) FIN

Question 2 div2(n) = l/fact(n) diiact(n) = n*ln-7!*(n-4).., (n-l) * (n-3) * . . .


=

SI pair(n) ALORS 2 * (fact(n/2))2 / fact(n) SINON fact(n) / 2- / (fact((n-1)/2))2 FINSI

Question 2 DEBUT DONNEES sg, sd, val: TABLEAU (1 ..taille) DE entier; racine: entier; VAR x: entier; PROC qu(n, poids: entier) RETOURNE bool; VAR qg, qd: bool; pg, pd: entier; SI n=O ALORS poids:=O; RETOURNER VRAI I S I N O N qg:=qu(sg(n), p g ) ; eqd:=qu(sd(n), p d ) ; poids:=pg+pd+l ; RETOURNER (qg ET qd ET abs(pg-pd)<2) FINSI FINPROC; imprimer(qu(racine, x)) FIN

DEBUT DONNEES n: entier; VAR div2: entier INITl ; TANTQUE n>l FAIRE div2:=diWn; n:=n-1 FAIT FIN DEBUT DONNEES n: entier; VAR haut, bas, divfact: entier; haut:=1 ; bas:=1 ; TANTQUE n>l FAIRE haut:=haut*n; bas:=bas*(n-1); n:=n-2 FAIT; divfact:=haut/bas FIN

158

159

1II I Il
la Villette

360243 8

Al hmique et pr dun enseignement defiv des tudiants de Ier et 2= cycles universitaires et dans le cadre de la formation permanente. II sadresse donc aussi bien aux tudiants qui dsirent se spcialiser en informatique quaux professionnels de lentreprise souhaiteraient acqurir des eiments de rig en programmation. A partir des exemples ci les tours dHanoi), la descr des diffrentes mtho dalgorithmes sont proposs. Des exercices avec ur solution concluent chaque chapitre et sument lessentiel des points abords.