Programmation et Algorithmique

´ INF 421, Ecole Polytechnique

Philippe Baptiste et Luc Maranget

Avant-propos
Ce polycopi´ est utilis´ pour le cours INF 421 intitul´ Les bases de la programmation et de e e e l’algorithmique. Ce cours fait suite au cours INF 311 Introduction a l’informatique et pr´c`de le ` e e cours INF 431 intitul´ Fondements de l’informatique. L’objectif du cours est triple : (1) programe mer en java, (2) maˆ ıtriser les bases de l’algorithmique sur des structures dynamiques (listes, arbres) et (3) introduire quelques notions fondamentales d’informatique comme les expressions r´guli`res, les automates et l’analyse syntaxique. e e Nous utilisions jusqu’` l’an pass´ le polycopi´ r´dig´ par Jean Berstel et Jean-Eric Pin. Pour a e e e e prendre en compte les r´cents changements dans l’organisation des cours d’informatique de e ´ l’Ecole, nous avons d´cid´ de r´diger une nouvelle version de ce polycopi´. Nous esp´rons qu’elle e e e e e est aussi claire, aussi pr´cise et aussi simple que la pr´c´dente. Nous avons d’ailleurs conserv´ e e e e de nombreux passages de J. Berstel et J.-E. Pin (en particulier pour les chapitres relatifs aux arbres). ´ Nous remercions nos coll`gues de l’Ecole Polytechnique, et plus particuli`rement ceux qui e e ont d’une mani`re ou d’une autre contribu´ au succ`s du cours INF 421 : Philippe Chassignet, e e e Mathieu Cluzeau, Thomas Heide Clausen, Robert Cori, Xavier Dahan, Olivier Devillers, Thomas Houtmann, Philippe Jacquet, Fabien Laguillaumie, Fabrice Le Fessant, Laurent Mauborgne, ´ David Monniaux, Sylvain Pradalier, Alejandro Ribes, Dominique Rossin, Eric Schost, Nicolas Sendrier, Jean-Jacques L´vy, Fran¸ois Morain, Laurent Viennot et Axelle Ziegler. Merci aussi ` e c a Christoph D¨rr du LIX, qui a magnifiquement illustr´ la page de couverture1 de ce polycopi´. u e e Les auteurs peuvent ˆtre contact´s par courrier ´lectronique aux adresses suivantes : e e e Philippe.Baptiste@polytechnique.fr Luc.Maranget@inria.fr On peut aussi consulter la version html de ce polycopi´ ainsi que les pages des travaux dirig´s e e sur le site http ://www.enseignement.polytechnique.fr/informatique/inf421

1

Le 421 est un jeu de bar qui se joue au comptoir avec des d´s. e

3

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¸ 2 Impl´mentation des piles . . . . . . . . . . . . . . . . . . . . . III Associations — Tables de hachage 1 Statistique des mots . . . structure s´quentielle e 2 Listes chaˆ ees. . e 4 Type abstrait. . . . . . . 2 Table de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 2 Arbres binaires de recherche . . . . . . . . . . . . . . . . . . . . . . II Piles et files ` 1 A quoi ca sert ? . . . . . . . . . . V Arbres binaires 113 1 Implantation des arbres binaires . . . ou gestion des partitions 3 Arbres binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 3 Programmation avec les expressions r´guli`res e e 4 Impl´mentation des expressions r´guli`res . . . . . . . . . . . . . . . . . . 5 Compl´ment : listes boucl´es . . . . . . . . . . . . . . . . . . . . . . . . . . . .Table des mati`res e I Listes 1 Structure dynamique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Choix des fonctions de hachage . . . . . . . . . . . . . . . . . e e 7 7 10 23 32 36 45 46 51 57 63 65 65 68 78 81 81 83 87 92 96 102 . . . 4 Programmation objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Files de priorit´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . e e e 5 Une autre approche du filtrage . . . . . . . . . . . . . . . . . . . . . . . 124 e e VI Expressions r´guli`res e e 1 Langages r´guliers . . . . . . . . . . . e 2 Union-Find. . . . . . . . . . . . . . . r´visions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 6 Codage de Huffman . . . . . . e 2 Notations suppl´mentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ın´ e 3 Tri des listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IV Arbres 1 D´finitions . . . 5 135 135 138 140 144 150 . . . . . . . . . . 4 Arbres de syntaxe abstraite . . . . . . . . . . . . . . . . . e 3 Impl´mentation des files . . . . . . . . . . . . . . . . . . . choix de l’impl´mentation e . . . . . . . . . 117 3 Arbres ´quilibr´s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o 2 Obscur objet . . . . . . . . . . . . . . . . . . . u e u e . . . . . . . . . . . . . mots. . . . . e Automates finis et expressions r´guli`res . . . . . . . . . . . . . . . . . . . e R´f´rences ee Index . . . . . . . . . e 6 Quelques classes de biblioth`que e 7 Pi`ges et astuces . . . . . . . . . . . . Quelques exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Constructions de base . 5 Entr´es-sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . coˆ t d’un algorithme u Une d´finition tr`s informelle des algorithmes e e Des algorithmes “efficaces” ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Coˆt estim´ vs. . . . . . e Rappel : alphabets. . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ` TABLE DES MATIERES 155 155 155 155 158 163 163 167 167 167 168 170 173 173 177 180 194 198 207 213 215 215 . . . . . . e Automates finis non-d´terministes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . langages et probl`mes e Automates finis d´terministes . . B Morceaux de Java 1 Un langage plutˆt classe . . e e Un peu de Java . . . . . . . . . . . . . . . . . . . . . . . . .6 VII Les 1 2 3 4 5 6 A Le 1 2 3 4 automates Pourquoi ´tudier les automates . . . . . . . . . . . . . . . . . . . coˆt r´el . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

comme dans beaucoup de langages. end. mais e elle est plus d´licate qu’en Java. la a c e e e m´moire peut ˆtre allou´e dynamiquement lors de l’ex´cution du programme. L’aspect dynamique renvoie aussi ` la fa¸on dont la m´moire n´cessaire est allou´e. Il n’en reste pas moins e e que les tableaux manquent un peu de souplesse. var t : array [1. Ainsi.. le syst`me d’ex´cution de Java est assez malin pour r´aliser cette e e e op´ration tout seul. qui est e e e bien plus souple que l’allocation statique.100] of integer . le proe grammeur doit rendre explicitement les cellules de m´moire dont il n’a plus besoin au syst`me e e d’exploitation. . Il faut d’abord allouer un tableau de points ` une e a taille par d´faut N suppos´e suffisante et ensuite g´rer le cas o` l’utilisateur entre plus de e e e u N points. En Pascal par exemple. En revanche. begin . Il est bien plus commode d’organiser ces points comme une suite de petits ea e blocs de m´moire. au sens o` elles ´voluent en forme et en taille en cours d’ex´cution u e e d’un programme. On alloue alors e la m´moire proportionnellement au nombre de points stock´s sans se poser de questions. mais les chaˆ ınes sont ´galement allou´es dynamiquement. Les listes sont l’exemple le plus simple d’une telle structure dynamique. par exemple en allouant un nouveau tableau plus grand dans lequel on recopie les points d´j` stock´s. En Java toutes les structures de donn´es sont allou´es dynamiquement — g´n´ralement e e e e par new. Nous e e avons red´couvert la liste (simplement chaˆ ee). structure s´quentielle e Les structures de donn´es en algorithmique sont souvent complexes et de taille variable. e e program . e a a e cette taille est ensuite rang´e dans le fichier ex´cutable produit et c’est le syst`me d’exploitation e e e qui alloue la m´moire demand´e au moment de lancer le programme.Chapitre I Listes 1 Structure dynamique. L’allocation statique suppose que le compilateur arrive ` e a connaˆ ıtre la taille de m´moire ` allouer (c’est-a-dire ` demander au syst`me d’exploitation). Une liste L E de E est : e ın´ 7 . la e e taille des tableaux globaux est donn´e par des constantes. de sorte les tableaux globaux peuvent e ˆtre et sont allou´s statiquement. . ce qui permet de privil´gier l’allocation dynamique de la m´moire. car en Pascal. un tableau n’est pas tr`s commode pour stocker une suite de points entr´s ` e e e a la souris et termin´e par un double clic. puisque leur taille est fix´e au moment de la e cr´ation. ou statiquement e e e e avant l’ex´cution du programme. un bloc contenant un point et un lien vers le bloc suivant. L’allocation dynamique de la m´moire existe aussi en Pascal (il existe une fonction new). Elles e sont souvent aussi dynamiques.

1. car la liste de l’informatique est tr`s contrainte. puisque les valeurs des objets sont des r´f´rences. On peut comprendre un ee ee aspect de l’organisation des listes ` l’aide de dessins qui d´crivent une abstraction de l’´tat de a e e la m´moire. PointList next . l’´tat final de la m´moire est sch´matis´ ainsi : e e e e qs p2 ps p1 ´ Etant entendu que.next = next .8 (1) la liste vide. Par exemple la liste des admis ` un concours quelconque. le (( lien )) revient n´cessairement. ee e (2) ou bien une r´f´rence qui pointe vers une cellule contenant un ´l´ment et une liste. PointList next) { this. une liste est une r´f´rence e e ee (voir B. // L’´l´ment e e // La suite PointList (Point val. sans vraiment tout expliquer. Mais m´fions nous un peu. ou la liste de e ee a courses que l’on emporte au supermarch´ pour ne rien oublier. les boˆ ıtes nomm´es les variables. CHAPITRE I. ee ee Autrement dit voici une d´finition possible des cellules de liste de points en Java. sans les d´tails techniques. on peut ee (a) extraire le premier ´l´ment e (ℓ. les fl`ches repr´sentent les r´f´rences. c’est-`-dire une simplification de cet ´tat. la barre e e e ee diagonale null . PointList qs = new PointList (p2 . ee . ps) .1). LISTES (2) ou une paire (une cellule) qui contient un ´l´ment E et la liste des ´l´ments suivants.3. dans ce type de sch´ma. dans le cas 2 le mot (( liste )) apparaˆ ` nouveau.val). null) . } } La cellule de liste est donc un objet de la classe PointList et une valeur de type PointList est bien une r´f´rence. qui est : (1) soit la r´f´rence null qui ne pointe nulle part et repr´sente la liste vide. En effet. u On notera que la d´finition th´orique des listes ne fait plus usage du mot lien.val = val . En Java. et les boˆ e ıtes anonymes les cellules m´moire e allou´es par new (voir B. ee ee Il s’agit d’une d´finition par induction. ℓ′ ). dire que l’ensemble L E est d´crit par l’´quation suivante. (2) et si ℓ est (une r´f´rence vers) une cellule (e. On e ıt a peut. Alors. e Il semble que ce qu’est une liste dans la vie de tous les jours est clair : une liste est une s´quence d’´l´ments. Par exemple. e class PointList { Point val . e a e e on suppose donn´s deux points p1 et p2 .3. Mais d`s que e e e l’on impl´mente les listes. e e les op´rations ´l´mentaires possibles sur une liste ℓ sont peu nombreuses : e ee (1) Tester si une liste est la liste vide ou pas (ℓ == null). et on produit les listes : e PointList ps = new PointList (p1 . e e L E = { ∅ } ∪ (E × L E ) O` ∅ est la liste vide. L’intuition est juste. this. elle fait e en particulier apparaˆ (surtout dans le premier exemple) que l’ordre des ´l´ments d’une liste ıtre ee importe.1).

la liste est une structure de donn´es s´quentielle. L’utilisateur qui entre un texte au clavier n’offre qu’un acc`s s´quentiel ` un proe e a gramme qui lit ce qu’il frappe. e (1) la liste vide existe ( null ). toutes les autres op´rations doivent ˆtre programm´es ` partir des op´rations e e e a e ´l´mentaires. e Exercice 1 Donner deux autres exemples de dispositifs mat´riels offrant acc`s s´quentiel et e e e acc`s direct. parce que la m´moire est allou´e petit ` e e e e a petit directement en fonction des besoins. ee e Et c’est tout. ce qui rend la e e programmation r´cursive assez naturelle (inductif et r´cursif sont pour nous informaticiens des e e synonymes). e Solution. la lecture de leur code en est facilit´e.next ) { // Traiter l’´l´ment qs. Enfin.val e e qs = qs.val e e } Les programmeurs Java emploient normalement une telle boucle pour signaler que leur code r´alise un simple parcours de liste. qs != null . STRUCTURE DYNAMIQUE. ne nous empˆche d’employer un autre type de boucle. ℓ)). (2) on peut ajouter un nouvel ´l´ment e en tˆte d’une liste ℓ (new PointList (e. Notons que e e e rien. par a e exemple PointList qs = ps . e e a e a ee . L’acc`s au contenu e e d’une case se fait simplement en donnant son adresse. et le parcours e e de la structure est privil´gi´ par rapport ` l’acc`s ` un ´l´ment arbitraire. Pour construite les listes. En effet. l’acc`s ` e e a e a un cylindre donn´ se fait par un d´placement radial de la tˆte de lecture. Cette technique d’acc`s s´quentiel s’oppose ` a ee e e e a l’acc`s direct. il n’y a que deux op´rations.next .next). e e e La distinction entre s´quentiel et direct se retrouve dans des dispositifs mat´riels : une bande e e magn´tique n’offre qu’un acc`s s´quentiel puisqu’il faut d´rouler toute la bande pour en lire la e e e e fin . En fait le tableau est une abstraction de la m´moire de l’ordinateur. ici une boucle for . qs = qs. en revanche la m´moire de l’ordinateur offre l’acc`s direct. e // ps est une PointList for (PointList qs = ps . e ee e le dispositif d’adressage des donn´es est juste un peu plus compliqu´ que celui de la m´moire. La liste peut ˆtre d´finie inductivement. e e e Les donn´es sont r´parties en cylindres qui correspondent ` des anneaux sur le disque. on peut voir la m´moire e e e comme un grand tableau (d’octets) dont les indices sont appel´s adresses. D`s lors. Vu du programmeur e e e il existe donc des fichiers ` acc`s uniquement s´quentiel (l’entr´e standard si elle correspond au a e e e clavier) et des fichiers ` acc`s direct (l’entr´e standard si elle correspond ` un fichier. Une structure s´quentielle reın´ e e groupe des ´l´ments en s´quence et l’acc`s ` un ´l´ment donn´ ne peut se faire qu’en parcourant ee e e a ee e la structure. e e e ee Cette op´ration se fait id´alement selon un idiome. STRUCTURE SEQUENTIELLE 9 (b) ou bien extraire la liste qui suit ℓ (ℓ. Un exemple simple de structure de donn´es qui offre l’acc`s direct est le tableau.´ 1. par une a e e a redirection <fichier ). jusqu’` trouver l’´l´ment cherch´. ee Une op´ration fr´quemment programm´e est le parcours de tous les ´l´ments d’une liste. En r´sum´ la liste est une structure dynamique. while (qs != null) { // Traiter l’´l´ment qs. ` part le souci de la coutume. c’est-`-dire selon une tournure fr´quemment e e a e employ´e qui signale l’intention du programmeur. } La liste simplement chaˆ ee est une structure s´quentielle. Un disque dur peut ˆtre consid´r´ comme offrant l’acc`s direct. par nature.

plus exactement.val + ". r´visions ın´ e Les listes chaˆ ees ont d´j` ´t´ vues dans le cours pr´c´dent. // L’´l´ment e e // La suite List (int val. e Poursuivons en affichant les ensembles. ou. Pour la programmation. la variable xs existe toujours et r´f´rence toujours la premi`re cellule de la ee e liste {1.next ne fait que changer la valeur de la variable locale xs (qui appartient en propre ` l’appel de m´thode) et n’a pas d’impact sur la liste elle-mˆme. 4}. Nous commen¸ons donc par ın´ ea ee e e c quelques r´visions.next) r++ . En fait. en particulier aucun ordre des ´l´ments d’une liste e ee n’est impos´. nous profitons de ce que les listes ont (( d´j` ´t´ vues en cours )) e eaee pour syst´matiser les techniques de programmation sur les listes. xs = xs. essayez tout de suite les exercices de la section 2. new List (3. xs != null .1 Op´rations ´l´mentaires sur les listes e ee Comme r´vision nous montrons comment r´aliser les ensembles (d’entiers) ` partir des listes e e a (d’entiers). e e List xs = new List (1. for ( .val = val . for ( . 3. this. en fabriquant une repr´sentation e affichable des ensembles sous forme de chaˆ ıne. a e e Ainsi ´crire le code suivant ne pose pas de probl`me particulier. new List (2. return r . Attention.4.null)))) l int size = card(xs) . un premier point tr`s important ` prendre en compte est e e a que null est un ensemble valable. Il est alors naturel d’´crire des m´thodes statiques. 2. new List (4.next ) r = r + xs. e class List { int val . List next . Employons donc la a boucle idiomatique static int card( List xs) { int r = 0 . // pour r = r+1 . e e ee a Nous ne sp´cifions aucune autre contrainte. Il y a sans doute un vrai profit e a ` lire cette section. e e e Commen¸ons par calculer le cardinal d’en ensemble. } Ce premier code fait apparaˆ qu’il n’est pas toujours utile de r´server une variable locale pour ıtre e ´ la boucle idiomatique. ` A la fin du code. car null e e qui ne poss`de aucune m´thode est une valeur l´gitime pour une variable de type List . xs = xs. } } Un ensemble est repr´sent´ par une liste dont tous les ´l´ments sont deux ` deux distincts. Si vous en doutez. LISTES 2 Listes chaˆ ees. List next) { this. Compte tenu de ce que les ´l´ments des c ee listes sont deux ` deux distincts. Voici un premier essai d’´criture d’une m´thode e e statique toString dans la classe List avec la boucle idiomatique. . Ecrire xs = xs. 2. cette variable xs n’a rien ` voir avec le param`tre homonyme de la a e m´thode card et heureusement. " .next = next .10 CHAPITRE I. xs != null . il suffit de calculer la longueur d’une liste. Voici la d´finition des cellules de liste d’entiers. static String toString( List xs) { String r = "{" .

val) . // Renvoyer la cha^ne contenue dans le StringBuilder ı return r. ´crivons ensuite la m´thode mem qui teste l’appartenance d’un entier e e e e a ` un ensemble. afin de parcourir la liste et de retourner de la m´thode mem e d`s qu’un entier ´gal ` x est trouv´. Le coˆt de production de la chaˆ est quadratique en la longueur de la liste. pour la liste des entiers a 1 et 3. List xs) { i f (xs == null) { return false . Pour une liste xs de longueur n on proc`de donc ` n concat´nations e e e a e de chaˆ ınes. List xs) { for ( .val) return true .append(xs. En particulier. e e ee Pour rem´dier au second probl`me il faut utiliser un objet StringBuilder de la biblioth`que e e e Java (voir B. xs != null . 3. static boolean mem(int x. xs != null . car il faut allouer une nouvelle chaˆ pour y recopier les caract`res des e e ıne e chaˆ ınes concat´n´es. return r . } Apr`s cet ´chauffement. }". // ajouter l’´criture d´cimale du premier entier xs = xs. Les objets StringBuilder sont des chaˆ ınes dynamiques. de tailles suppos´es r´guli`rement croissantes ce qui m`ne au final ` un coˆt e e e e a u quadratique. Comme on le sait certainement d´j` une ´criture r´cursive e e a e ea e e est ´galement possible. } On emploie la boucle idiomatique.val) .append("}") . // Un StringBuilder ` nous r.next . .append(". REVISIONS IN ´ r = r + "}" .4).´ 2. } return false .next ) r. LISTES CHAˆ EES. on obtient "{1. dont on peut changer la taille. Bref on a : static String toString( List xs) { a StringBuilder r = new StringBuilder () .toString() . i f (xs != null) { e e r.1. " + xs. En effet la u ıne concat´nation de deux chaˆ e ınes (par +) coˆte en proportion de la somme des longueurs des u chaˆ ınes concat´n´es. } 11 Le code est critiquable : L’affichage est laid.next) { i f (x == xs. pour un coˆt que l’on peut consid´rer proportionnel ` ıne a u e a la longueur de str. } r. il y a une virgule en trop ` la fin. xs = xs. " e e for ( .append("{") . xs = xs. Par exemple. e static boolean mem(int x.6. n(n + 1) k k + 2 × k + ···n × k = 2 Pour rem´dier au premier probl`me on peut distinguer le cas du premier ´l´ment de la liste. ils poss`dent une m´thode append(String str) qui permet e e de leur ajouter une chaˆ str ` la fin. // et celles des suivants pr´fix´es par ".

dont on sait qu’elle ne peut a ′. Nous e e ee choisissons de suivre le principe des structures de donn´es dites fonctionnelles ou non-mutables e ou encore persistantes. on ne modifie jamais le contenu des cellules de liste. u Pour ´crire remove.next) . pas contenir x Soit en ´quations : e R(x. xs)) { return xs .12 } else { return (x == xs. a e e et il est important d’admettre d`s maintenant que cette approche rend la programmation bien e plus sˆre. et il a bien plus de chances d’ˆtre juste du premier coup. a Sinon. (x′ . ∅) = ∅ R(x. (x′ . elle tient en deux e e ´quations : e M (x. ee e e ´crire add est tr`s facile. avec une boucle) est plus c e e e efficace que le code r´cursif (le second). style dit fonctionnel u Nous envisageons maintenant des m´thodes qui fabriquent de nouveaux ensembles. raisonnons inductivement : dans un ensemble vide. List xs) { i f (mem(x. } } CHAPITRE I. X ′ ) on distingue deux cas : L’´l´ment x ` enlever est ´gal ` x′ et alors X moins x est X ′ . que choisir ? De fa¸on g´n´rale le code it´ratif (le premier. e le code it´ratif est pr´f´rable (en Java qui favorise ce style). } else { return new List (x. xs) . Come men¸ons par la m´thode add qui ajoute un ´l´ment ` un ensemble. Alors. xs. e e static List add(int x. ne serait-ce que parce que les appels de m´thode sont e e assez coˆteux. il faut enlever x de X ′ et ajouter x′ ` la liste obtenue. X ′ )) = (x′ . e a tandis que dans un ensemble (une liste) X = (x′ . Une fois disponible la m´thode mem. ∅) = faux M (x. R(x. Mais le code r´cursif r´sulte d’une analyse syst´matique de la structure de donn´es u e e e e inductive. il n’y a rien ` enlever . dans un cas aussi simple. car X est une liste d’´l´ments ee a e a ee deux ` deux distincts et donc X ′ ne contient pas x. } } Attaquons ensuite la m´thode remove qui enl`ve un ´l´ment x d’un ensemble X. (x. X ′ )) = (x = x′ ) ∨ M (x. X ′ )) = X ′ R(x. . Selon ce principe. X ′ ) Ces deux ´quations sont le support d’une preuve par induction structurelle ´vidente de la core e rection de mem. Le point remarquable est c e ee a qu’un ensemble contient au plus une fois un ´l´ment donn´.val) || mem(x. Cela revient ` consid´rer les listes comme d´finies inductivement par L E = {∅} ∪ (E × L E ). X ′ )) avec x = x′ Et en Java : static List remove(int x. LISTES La version r´cursive provient directement de la d´finition inductive des listes.2 Programmation sˆ re. List xs) { i f (xs == null) { return null . Ici. e ee 2.

xs != null . jusqu’` trouver l’´l´ment x. Pour s’en convaincre. xs. } Et ici apparaˆ une diff´rence. REVISIONS IN ´ } else i f (x == xs.next)) . l’´l´ment xs. List ys = remove(3. r) est construite. new List (3. Ces cellules sont devenues les miettes et le gestionnaire de m´moire e e e de l’environnement d’ex´cution de Java peut les r´cup´rer. xs) . null)))) .xs) renvoie une nouvelle liste (il y a des new). la version it´rative inverse cet ordre. List xs) { List r = null .val. si e e a ee e e nous ´crivons e List xs = new List (1. xs = remove(3. Alors on obtient 1 xs 1 2 2 3 4 C’est-`-dire exactement la mˆme chose. e ee e a static List remove(int x. e e dont le d´but est une copie du d´but de xs. } return r . Plus pr´cis´ment. Alors on a les structures suivantes en m´moire e xs ys 1 1 2 2 3 4 (Les cellules allou´es par remove sont gris´es.val. new List (4. e Au passage.val est . sauf que la variable xs pointe maintenant vers la nouvelle a e liste et qu’il n’y a plus de r´f´rence vers la premi`re cellule de l’ancienne liste. remove(x. ´ Ecrivons maintenant remove avec une boucle.val) { return xs. xs) .val) r = new List (xs. LISTES CHAˆ EES. remove(x. new List (3. for ( . xs = xs. } } 13 En ce qui concerne l’´tat m´moire. new List (4. Dans le style persistant on en vient toujours ` copier les parties e a des structures qui sont chang´es. new List (2. alors que la version r´cursive de remove pr´serve l’ordre des ıt e e e ´l´ments de la liste. et c’est un des points forts du langage. } else { return new List (xs.next ) { i f (x != xs.) On constate que la partie de la liste xs qui e e pr´c`de l’´l´ment supprim´ est recopi´e.´ 2.next .val. Le ramassage des miettes (garbage e e e collection) est automatique en Java. tandis que la partie qui suit l’´l´ment supprim´ est e e ee e e ee e partag´e entre les deux listes. Voici un premier essai : parcourir la liste xs en la recopiant en ´vitant de copier tout ´l´ment ´gal ` x. si on ´crit plutˆt e o List xs = new List (1. Il en r´sulte ee e e que la m´moire occup´e par les trois premi`res cellules de l’ancienne liste ne peut plus ˆtre e e e e acc´d´e par le programme. new List (2. null)))) . on peut remarquer ee e ee que lorsque qu’une nouvelle cellule new List(xs. r) .

Jusqu’ici nous avons toujours appel´ le constructeur des listes e e e a ` deux arguments. le code it´ratif convient. xs 1 1 2 2 3 4 4 ys 2 3 4 L’inversion n’a pas d’importance ici.14 Fig. new List (3. Il n’en y irait pas de mˆme si les e e listes ´taient ordonn´es. la cellule e e e a e nouvelle ´tant gris´e. Avant la boucle e e e ea e on a x 3 xs 1 r La figure 1 sch´matise l’´tat de la m´moire ` la fin de chacune des quatre it´rations. pour le programme complet. 1 – Les quatre it´rations de remove e xs 1 1 2 r 3 4 xs CHAPITRE I. puisque les ensembles sont des listes d’´l´ments deux ` ee a deux distincts sans autre contrainte. new List (2. LISTES ⇒ 1 1 2 2 3 r 4 xs 1 1 2 2 3 4 4 r ⇓ ⇐ 1 1 2 2 xs 3 r 4 ajout´ au d´but de la liste r. Et enfin voici le bilan de l’´tat m´moire. e e Il est possible de proc´der it´rativement et de conserver l’ordre des ´l´ments. tandis qu’il est extrait de la fin de la liste des ´l´ments d´j` e e ee ea parcourus. Nous nous donnons un nouveau constructeur List ( int val) qui initialise . List ys = remove(3. null)))) . new List (4. Mais c’est e e ee un peu plus difficile. On peut aussi sch´matiser l’´tat m´moire sur l’exemple d´j` utilis´. e e e e List xs = new List (1. ce qui nous garantit une bonne initialisation des champs val et next des cellules de liste. On emploie une technique de programmation que faute de mieux nous appellerons initialisation diff´r´e. xs) .

next ` une nouvelle cellule a last. tandis que last pointe sur la derni`re cellule — sauf tr`s bri`vement au niveau e e e e du commentaire /**.next = null .next = new List(xs.val).´ 2. ce qui revient e moralement ` une initialisation. . .next ) { // (( initialiser )) last.next . e e .next = new List (xs.val) . } Pour illustrer l’initialisation diff´r´e. . } // (( initialiser )) le next de la toute derni`re cellule ` une liste vide e a last. **/. xs = xs. // Inutile.3.val) . **/ est e e e la suivante : xs 1 1 r last 2 2 ? 3 4 5 Et apr`s ajout d’une nouvelle cellule par last. LISTES CHAˆ EES. nous commen¸ons par un exemple plus facile. REVISIONS IN ´ 15 seulement le champ val. // last pointe sur la derni`re cellule de la liste r e List last = r . // Ici la copie poss`de au moins une cellule. voir B. Il y a un petit d´calage ` g´rer au d´but et ` la fin a e e a e e a du parcours.next .val = val . o` last pointe sur l’avant-derni`re cellule. ee c Exercice 2 Copier une liste en respectant l’ordre de ses ´l´ments.1 Le champ next sera affect´ une seule fois plus tard. static List copy( List xs) { i f (xs == null) return null . e Solution. que voici e List r = new List (xs.3. } On observe que le code construit une liste sur laquelle il maintient deux r´f´rences. a List (int val) { this. e 1 En fait. . un champ de type objet non-initialis´ explicitement contient la valeur par d´faut null. // Parcourir la suite de xs for ( xs = xs. e e /¶¶ qui devient donc la derni`re cellule du r´sultat ¶¶/ last = last. xs != null . L’id´e revient ` parcourir la liste xs en copiant ses cellules et en d´l´gant l’initialisae a ee tion du champ next ` l’it´ration suivante. sans ´crire de m´thode ee e e r´cursive. Plus pr´cis´ment. r pointe sur la ee premi`re cellule. la u e e e situation en (( r´gime permanent )) avant la ligne qui pr´c`de le commentaire /**. mais c’est plus propre return r .

16

CHAPITRE I. LISTES

xs 1 1 r last 2 2 3 3 ? 4 5

(La cellule qui vient d’ˆtre allou´e est gris´e). Et enfin apr`s ex´cution de l’affectation last == e e e e e last.next. xs 1 1 r 2 2 last 3 3 ? 4 5

On con¸oit qu’avec un tel dispositif il est facile d’ajouter de nouvelles cellules de liste ` la fin du c a r´sultat et possible de rendre le r´sultat (avec r qui pointe sur la premi`re cellule). On observe e e e le cas particulier trait´ au d´but, il r´sulte de la n´cessit´ d’identifier le cas o` il ne faut pas e e e e e u construire la premi`re cellule du r´sultat. e e Enfin, la m´thode it´rative construit exactement la mˆme liste que la m´thode r´cursive, e e e e e mˆme si les cellules de listes sont allou´es en ordre inverse. En effet la m´thode r´cursive ale e e e loue la cellule apr`s que la valeur du champ next est connue, tandis que la m´thode it´rative e e e l’alloue avant. La figure 2 donne la nouvelle version it´rative de remove, ´crite selon le principe de l’inie e tialisation diff´r´e. Par rapport ` la copie de liste, le principal changement est la possibilit´ ee a e d’arrˆt de la copie ` l’int´rieur de la boucle. En effet, lorsque l’´l´ment ` supprimer est identifi´ e a e ee a e (x == xs.val), nous connaissons la valeur ` ranger dans last.next, puisque par l’hypoth`se a e (( xs est un ensemble )), xs moins l’´l´ment x est exactement xs.next, on peut donc arrˆter ee e la copie. On observe ´galement les deux cas particuliers trait´s au d´but, ils r´sultent, comme e e e e dans la copie de liste, de la n´cessit´ d’identifier les cas o` il ne faut pas construire la premi`re e e u e cellule du r´sultat. Au final le code it´ratif est quand mˆme plus complexe que le code r´cursif. e e e e Par cons´quent, si l’efficacit´ importe peu, par exemple si les listes sont de petite taille, la e e programmation r´cursive est pr´f´rable. e ee Nous pouvons maintenant ´tendre les op´rations ´l´mentaires impliquant un ´l´ment et un e e ee ee ensemble et programmer les op´rations ensemblistes du test d’inclusion, de l’union et de la e diff´rence. Pour programmer le test d’inclusion xs ⊆ ys, il suffit de tester l’appartenance de e tous les ´l´ments de xs ` ys. ee a static boolean included( List xs, List ys) { for ( ; xs != null ; xs = xs.next ) i f (!mem(xs.val, ys)) return false ; return true ; }

´ 2. LISTES CHAˆ EES, REVISIONS IN ´ Fig. 2 – Enlever un ´l´ment selon la technique de l’initialisation diff´r´e ee ee static List remove(int x, List xs) { /* Deux cas particuliers */ i f (xs == null) return null ; /* Ici xs != null, donc xs.val existe */ i f (x == xs.val) return xs.next ; /* Ici la premi`re cellule du r´sultat existe et contient xs.val */ e e List r = new List (xs.val) ; List last = r ; /* Cas g´n´ral : copier xs (dans r), jusqu’` trouver x */ e e a for ( xs = xs.next ; xs != null ; xs = xs.next ) { i f (x == xs.val) { // (( initialiser )) last.next ` xs moins xs.val (c-a-d. xs.next) a last.next = xs.next ; return r ; } // (( initialiser )) last.next ` une nouvelle cellule a last.next = new List (xs.val) ; last = last.next ; } // (( initialiser )) last.next a une liste vide ` last.next = null ; // Inutile mais c’est plus propre. return r ; }

17

Exercice 3 Programmer l’union et la diff´rence ensembliste. e Solution. Pour l’union, il suffit d’ajouter tous les ´l´ment d’un ensemble ` l’autre. ee a static List union( List xs, List ys) { List r = ys ; for ( ; xs != null ; xs = xs.next ) r = add(xs.val, r) ; return r ; } On aurait mˆme pu se passer de la variable r pour ´crire ys = add(xs.val, ys) et puis e e return ys. Mais la clart´ en aurait souffert. e Pour la diff´rence des ensembles, l´g`rement plus d´licate en raison de la non-commutativit´, e e e e e on enl`ve de xs tous les ´l´ments de ys. e ee // Renvoie xs \ ys static List diff( List xs, List ys) { List r = xs ; for ( ; ys != null ; ys = ys.next ) r = remove(ys.val, r) ; return r ; }

18

CHAPITRE I. LISTES

2.3

Programmation dangereuse, structures mutables

Les m´thodes remove de la section pr´c´dente copient tout ou partie des cellules de la liste e e e pass´e en argument. On peut, par exemple dans un souci (souvent mal venu, disons le tout de e suite) d’´conomie de la m´moire, chercher ` ´viter ces copies. C’est parfaitement possible, ` e e a e a condition de s’autoriser les affectations des champs des cellules de liste. On parle alors de structure de donn´es mutable ou imp´rative. Ainsi, dans le remove r´cursif on remplace l’allocation e e e d’une nouvelle cellule par la modification de l’ancienne cellule, et on obtient la m´thode nremove e destructive ou en place suivante static List nremove(int x, List xs) { i f (xs == null) { return null ; } else i f (x == xs.val) { return xs.next ; } else { xs.next = nremove(x, xs.next) ; return xs ; } } On peut adapter la seconde version it´rative de remove (figure 2) selon le mˆme esprit, les e e r´f´rences r et last pointant maintenant, non plus sur une copie de la liste pass´e en argument, ee e mais sur cette liste elle-mˆme. e static List nremove(int x, List xs) { /* Deux cas particuliers */ i f (xs == null) return null ; i f (x == xs.val) return xs.next ; // Ici la premi`re cellule du r´sultat existe et contient xs.val, e e List r = xs ; List last = r ; /* Cas g´n´ral : chercher x dans xs pour enlever sa cellule */ e e for ( xs = xs.next ; xs != null ; xs = xs.next ) { /¶¶ Ici, on a last.next == xs ¶¶/ i f (x == xs.val) { // Enlever la cellule point´e par xs e last.next = xs.next ; return r ; } last.next = xs ; // Affectation inutile last = last.next ; /¶¶ Exceptionnellement, on a xs == last ¶¶/ } last.next = null ; // Affectation inutile return r ; } On note que la version it´rative comporte des affectations de last.next inutiles. En effet, ` e a l’int´rieur de la boucle les r´f´rences xs et last.next sont toujours ´gales, car last pointe e ee e toujours sur la cellule qui pr´c`de xs dans la liste pass´e en argument, sauf tr`s bri`vement au e e e e e niveau du second commentaire /**. . . **/. En r´gime permanent, la situation au d´but d’une e e it´ration de boucle (avant le premier commentaire /**. . . **/) est la suivante e

´ 2. LISTES CHAˆ EES, REVISIONS IN ´

19

x 3 r

last 1

xs 2 3 4

Et en fin d’it´ration, apr`s le second commentaire /**. . . **/, on a e e x 3 r last 1 xs 2 3 4

Au d´but de l’it´ration suivante on a e e x 3 r 1 last 2 xs 3 4

La recherche de xs.val == x est termin´e, et le champ next de la cellule last qui pr´c`de la e e e cellule xs est affect´. e x 3 r 1 last 2 xs 3 4

Puis la liste r est renvoy´e. e a Au final, l’effet de nremove( int x, List xs) s’apparente ` un court-circuit de la cellule qui contient x. Ainsi ex´cuter e List xs = new List (1, new List (2, new List (3, new List (4, null)))) ; List ys = nremove(3, xs) ; produit l’´tat m´moire e e xs 1 ys Un probl`me majeur survient donc : l’ensemble xs est modifi´ ! On comprend donc pourquoi e e nremove est dite destructive. On peut se dire que cela n’est pas si grave et que l’on saura en tenir compte. Mais c’est assez vain, car si on supprime le premier ´l´ment de l’ensemble xs, le ee comportement est diff´rent e List xs = new List (1, new List (2, new List (3, new List (4, null)))) ; List ys = nremove(1, xs) ; Alors on obtient xs 1 ys 2 3 4 2 3 4

val. ys)) . Par cons´quent. X). static List nappend( List xs. le champ next est affect´ une seule e e e fois. List ys) { i f (xs == null) { return ys . ajouter les ´l´ments d’une liste ` la fin de l’autre. L’´criture r´cursive de nappend ` e e e a partir de la version fonctionnelle est m´canique.next = ys . la programmation des structures mutables est tellement d´licate qu’il est e souvent plus raisonnable de s’abstenir de l’employer. Concat´ner deux listes signifie. return r .next. append(xs. List ys) { i f (xs == null) return ys . // A(∅. e e e Afin d’ˆtre bien sˆr d’avoir tout compris. return xs . quand on est absolument sˆr que les e e u structures modifi´es ne sont connues d’aucune autre partie du programme. la maxime ci-dessus souffre des exceptions.4 Contrˆle de la r´vision o e Nous avons divis´ les techniques de programmation sur les listes selon deux fois deux e cat´gories. List r = new List (x.next = nappend(xs.next ) { last. Y )) } } Nommons nappend la version mutable de la concat´nation. LISTES Et on observe qu’alors l’ensemble xs n’a pas chang´. comme pour les chaˆ c e ınes. et en particulier. static List append( List xs. ee a e e La programmation r´cursive non-mutable r´sulte d’une analyse simple de la structure inductive e e de liste.next . dans les quelques cas o` l’on poss`de une maˆ u e ıtrise compl`te des structures de donn´es. on a recours a l’initialisation diff´r´e.next . A(X. programmons une op´ration classique sur les listes e u e des quatre fa¸ons possibles. Y ) = (x. List ys) { i f (xs == null) { return ys .val) . et qui ne e e e sont accessible qu’` partir des variables r et last elles-mˆmes locales ` remove. xs = xs. allou´es par la m´thode remove elle-mˆme.20 CHAPITRE I.val) . e a e 2. Il suffit de remplacer les allocations de cellules e par des modifications. xs != null .next. . } } Pour la programmation it´rative non-mutable. Comme toujours en programmation. Y ) = Y } else { return new List (xs. En effet. e En conclusion. // A((x. Les listes peuvent ˆtre fonctionnelles (persistantes) ou mutables (imp´ratives). for ( xs = xs. Nommons append la m´thode qui concat`ne deux listes. List last = r . et e e e la programmation peut ˆtre r´cursive ou it´rative. e ` ee static List append( List xs. } last. last = last. } else { xs. ys)) . et seulement sur des cellules fraˆ ıches.next = new List (xs. e Un bon exemple de ce type d’exception est la technique d’initialisation diff´r´e employ´e dans ee e la seconde m´thode remove it´rative de la figure 2. a e a e les modifications apport´es ` certaines cellules sont invisibles de l’ext´rieur de remove.

LISTES CHAˆ EES. Il en r´sulte une liste (( boucl´e )). nous conseillons encore un exercice. la version it´rative est de loin la plus naturelle. Une fois n’est pas coutume. last.next e e qui est la seule qui change quelque chose.´ 2. Le programme affecte la r´f´rence xs au champ next de la derni`re cellule de la ee e liste xs. List ys) { i f (xs == null) return ys . de reverse la e e m´thode qui inverse une liste. for ( .next = ys . last = last. e e xs 1 ys 2 3 Et pour ˆtre vraiment sˆr d’avoir compris.next . null))) . Solution. REVISIONS IN ´ } 21 Pour la programmation mutable et it´rative. on supprime les allocations de la version none mutable et on remplace les initialisations diff´r´es par des affectations des champs next de la ee liste pass´e comme premier argument.next = ys . qui met en valeur la recherche de la e e derni`re cellule de la liste xs. List r = xs . On n’explicite que la derni`re affectation de last. xs) . List ys) { i f (xs == null) return ys . u e Exercice 4 Que fait le programme suivant ? List xs = new List (1. u e e ee o` n est la longueur de la premi`re liste. } last.next != null . // idiome : boucle qui ne fait rien last. xs != null . } Enfin on constate que le coˆt de la concat´nation est de l’ordre de n op´rations ´l´mentaires. new List (3.next ) { last = last. } Voici un autre codage plus concis de la mˆme m´thode.next ) . e . e e Exercice 5 Programmer les deux versions fonctionnelles. return xs . xs = xs. List ys = nappend(xs. e static List nappend( List xs. new List (2. return r . List last = r . e Solution.next . d’ailleurs un e u peu pi´g´. List last = xs . it´rative et r´cursive. static List nappend( List xs. for ( xs = xs.

car pour inverser une liste de longueur n. Y ) = A(R(X). G´n´ralisons l’´quation pr´c´dente. Soit R la fonction qui inverse une liste et A la concat´nation des listes. on a A(A(X. Y ) = R′ (X. return r . null)) . } } Ce n’est pas une tr`s bonne id´e. for ( .22 static List reverse( List xs) { List r = null . mais ici c’est utile. Y ) Or. X)). X)). X)). e static List reverse( List xs) { i f (xs == null) { return null . A(Y.val. autrement dit R′ renvoie la concat´nation de X invers´e et de Y . l’´quation pr´c´dente s’´crit : e e e e R′ ((x. (x. Y et Z. puisque de toute ´vidence R′ (∅. xs != null . pour toute liste Y on a c e e e e e e A(R((x. Y ). e e C’est souvent embˆtant. ∅)) On est donc tent´ d’´crire : e e // Mauvais code. en lisant des entiers e entr´s au clavier) pour construire une liste. Y ) = A(R(X). (x. e e Avec cette nouvelle notation. 0. Le truc revient ` consid´rer une a a e m´thode auxiliaire r´cursive qui prend deux arguments. Z)) (la concat´nation e est associative). mais pr´f´rons faire semblant de le d´duire de d´marches plus g´n´rales. montre ce qu’il faut ´viter. e ´ Ecrire la version r´cursive est un peu gratuit en Java.next). quelles que soient les listes X. . Y )) ´ Equation qui permet de calculer R′ r´cursivement. les ´l´ments sont ajout´s au d´but de la liste construite alors qu’il ee e e sont extraits de la fin de la s´quence lue — voir aussi le premier remove it´ratif de la section 2. Nous pourrions donner le truc directee e ment. e e . il en coˆtera au e e u moins de l’ordre de n2 op´rations ´l´mentaires. Y ) = A(A(R(X). l’une th´orique ee e e e e e et l’autre pratique. new List (xs. le premier ee e ´l´ment de la liste que l’on souhaite inverser doit aller a la fin de la liste invers´e.val. r) . Commen¸ons par la th´orie. Soit l’´quation : ee ` e e R((x.2. LISTES En effet. Un truc des plus classiques vient alors ` notre secours. Z) = A(X. Y ) = Y . Y ) = A(R(X). Il vient : A(R((x. Y ). xs = xs. en parcourant (lisant) une s´quence quelconque d’entiers (par ex. ∅)). correspondant aux n append effectu´s sur des e ee e listes de taille n − 1. . } else { return append(reverse(xs. De fait. (x. A((x.next ) r = new List (xs. Mais la d´marche ne manque pas e e d’int´rˆt. . (x. X)). X)) = A(R(X). la liste est naturellement construite dans l’ordre e inverse de la lecture. ∅). Y )) Autrement dit : A(R((x. Y )) Posons R′ (X. } CHAPITRE I.

List r) { i f (xs == null) { return r . } else { return loop(xs. apr`s nettoyage. u e e Consid´rons maintenant une it´ration de la boucle comme une m´thode loop. e a static List reverse( List xs) { List r = null . Enfin. dans le corps de reverse it´ratif. } On retrouve. return r . il faut appeler l’it´ration e a e suivante en lui passant r modifi´ par l’appel ` body. Si xs est null . et notamment de consid´rer des ale gorithmes. } else { return reverseAux(xs. e On r´´crit donc la boucle ainsi ee for ( . Cette m´thode e e e e a besoin des param`tres xs et r. les tris interviennent e e e . xs != null . ∅) = R(X) et donc : static List reverse( List xs) { return reverseAux(xs.next. il faut surtout retenir l’´quivalence entre boucle et r´cursion terminale. Dans ce cas. } 23 Pour l’approche pratique du truc. De tous ces e e e e discours. xs = xs. On op`re ensuite un petit saut s´mantique en comprenant e e e (( passer ` la suite du code )) comme (( retourner en tant que r´sultat de la m´thode loop )). Le tri est une op´ration fr´quemment effectu´e. Commen¸ons par n´gliger les d´tails du corps e c e e de la boucle de reverse it´ratif. il faut savoir que l’on peut toujours et de fa¸on assez c simple remplacer une boucle par une r´cursion. r)) .next. r) . new List (xs. Ou plus exactement l’unique appel r´cursif est en position dite e e terminale. e e 3 Tri des listes Nous disposons d´sormais de techniques de programmation ´l´mentaires sur les listes. } } Par ailleurs on a R′ (X. r) . } } La m´thode loop est dite r´cursive terminale car la derni`re op´ration effectu´e avant de ree e e e e tourner est un appel r´cursif. null) . TRI DES LISTES private static reverseAux( List xs. En particulier. r = loop(xs. O` la m´thode body est ´vidente. Le corps de la boucle lit les variables xs et r puis modifie r. le mˆme code qu’en suivant la d´marche th´orique. ys)) . List ys) { i f (xs == null) { return ys . body(xs.val. Sinon. Soit : a e e private static List loop( List xs. le r´sultat ` passer ` la suite du e a e a a code est celui de l’it´ration suivante.3.next ) r = body(xs. Il est e ee temps d’aborder des programmes un peu plus audacieux. ∅) = A(R(X). on remplace la boucle par un appel ` loop. alors il n’y a pas d’it´ration (pas d’appel de e e body) et il faut passer le r´sultat r courant ` la suite du code.

xs = xs. et que le second uniq effectue au pire de l’ordre de n2 op´rations ´l´mentaires. e e e static List insertionSort( List xs) { List r = null .next) .next != null else i f (xs. il suffit donc de parcourir la liste en la copiant et d’´viter de copier les ´l´ments ´gaux e e ee e a ` leur successeur dans la liste. return uniqSorted(xs. private static List uniqSorted( List xs) { e e e i f (xs == null || xs.next. Dans une liste tri´e les ´l´ments ´gaux se suivent.next ) r = insert(xs. on ´crit la m´thode uniq qui accepte une liste e e e e quelconque. uniqSorted(xs. e ee il est certain que le premier uniq est asymptotiquement plus efficace.val. } En revanche.3).next == null) { // xs a z´ro ou un ´l´ment return xs .val. } . static List uniq( List xs) { return uniqSorted(sort(xs)) .val.next)) . } // desormais xs != null && xs. return r . for ( . return r . Sur ce point [6] est une r´f´rence fondamentale.1 Tri par insertion Ce tri est un des tris de listes les plus naturels. r) . } } La s´mantique s´quentielle du ou logique || permet la concision (voir B. en raison e e de leur int´rˆt et de leur relative simplicit´.7. comme on peut trier une liste au prix de de l’ordre de n log n op´rations ´l´mentaires e ee (n longueur de xs). r) . e ee a e e C’est la technique g´n´ralement employ´e pour trier une main dans les jeux de cartes.24 CHAPITRE I. Les algorithmes de tri sont nombreux et ils ont fait l’objet d’analyses tr`s fouill´es. xs)) r = new List (xs. Pour enlever les doublons d’une e ee e liste tri´e.val) .val == xs. for ( . ee e ee 3. LISTES souvent dans d’autres algorithmes. dans le cas fr´quent o` il plus simple ou plus efficace d’effece u tuer des traitements sur des donn´es prises dans l’ordre plutˆt que dans le d´sordre. e o e Exercice 6 Programmer la m´thode static e d’une liste.val. List uniq( List xs) qui ´limine les doublons e Solution.next ) i f (!mem(x. L’algorithme est simple : pour trier la liste xs. } On peut discuter pour savoir si uniq ci-dessus est plus simple que la version sans tri ci-dessous ou pas. on la parcourt en ins´rant ses ´l´ments ` leur place dans une liste r´sultat qui est donc tri´e. xs != null . xs != null . xs = xs. En supposant e e donn´e une m´thode sort de tri des listes. } else { return new List (xs. static List uniq( List xs) { List r = null .

Par ailleurs. Soit ee e e finalement (y. Y ′ ) est tri´e. Y ) est tri´e. Y ) Soit I fonction d’insertion. d’un test contre null . On montre d’abord (par induction structurelle) le lemme ´vident que I(x. Y ee que tous les ´l´ments de I(x. Par ailleurs nous n´gligeons le coˆt du garbage collector. insert(x. I(0) ≤ k0 2 I(n + 1) ≤ I(n) + k1 Attention. e private static List insert(int x. si x ≤ y. Y ′ ) et on est tent´ de poser e e e I(x. List ys) { i f (here(x. a e u . List ys) { return ys == null || x <= ys. Y ) e regroupe les ´l´ments de Y plus x. a e private static boolean here(int x. Y ) invalide). Y ) est invalide. Y = (y. e Induction Sinon. (y. Y ) est vrai (ce qui comprend le cas Y = ∅). alors tous les ´l´ments de Y tri´e sont plus grands (au sens large) que x. ys. quand la liste (x. e a e e Il n’en va pas de mˆme de la m´thode insert qui est r´cursive. e e ′ ) sont ceux de Y ′ plus x. Les constructions ´l´mentaires de Java employ´es2 e ee e s’ex´cutent en temps constant c’est ` dire que leur coˆt est born´ par une constante. ou bien. Par hypoth`se d’induction I(x. En effet.val . L’analyse inductive conduit ` la question suivante : ae e a soit x un entier et Y une liste tri´e. selon l’analyse pr´c´dente on pose e e Si P (x. Y ′ ) et x ≤ y. ∅) = vrai I(x.val. une allocation de tableau par exemple prend un temps proportionnel ` la taille du tableau. alors Y s’´crit n´cessairement Y = (y. Y ′ ).3. Y ′ ) (par hypoth`se (y. Y ′ )) Et on aura raison. I(x. alors I(x. De fait. Par coˆt u e u nous entendons d’abord temps d’ex´cution. on a Y = (y. Y ) = (x. Il en r´sulte e a u e e que mˆme si le coˆt d’un appel ` la m´thode here est variable. ici e la longueur de la liste ys.next)) . Y ′ ) = (y. I(x. Y ′ )) est tri´e. ys) . TRI DES LISTES 25 Il reste ` ´crire l’insertion dans une liste tri´e. Ensuite on montre par induction structurelle que I(x. } puis la m´thode d’insertion insert. et donc y est plus petit (au sens large) les ´l´ments de I(x. Y ) P (x. Y ) est ee tri´e. e Il reste ` programmer d’abord le pr´dicat P . } else { // NB: !here(ys) => ys != null return new List (ys. si Y est la liste vide ∅. ys)) { return new List (x. selon que le param`tre ys vaut e u a e e null ou pas. le coˆt d’un appel e e e u a ` insert s’exprime comme une suite I(n) d’une grandeur qui exprime la taille de l’entr´e. ce coˆt reste inf´rieur ` une constante correspondant ` la somme des coˆts d’un u e a a u appel de m´thode ` deux param`tres. e Base Si P (x. Notons P (X) le pr´dicat d´fini par e e e P (x. ce n’est pas toujours le cas. par ee e transitivit´ de ≤. } } Penchons nous maintenant sur le coˆt de notre impl´mentation du tri par insertion. Y ′ ) tri´e et P (x. d’un acc`s au champs val etc. Y ) est elle tri´e ? De toute ´vidence cela e e e est vrai. (y. Y )) = x ≤ y si P (x.

pour n assez grand. e e u . La d´marche se justifie de deux fa¸ons : l’op´ration ee e c e compt´e est la plus coˆteuse (ici que l’on songe au tri des lignes d’un fichier par exemple). (( De l’ordre de )) a ici le sens pr´cis que nous avons trouv´ des ordres de e e grandeur asymptotiques pour n tendant vers +∞. Il faut noter que l’on peut e e u g´n´ralement limiter les calculs aux termes dominants en n. on a k1 · g(n) ≤ f (n) ≤ k2 · g(n) Par ailleurs. le cours pr´c´dent) que f est dans Θ(g) quand il existe deux constantes e e e k1 et k2 telles que. car les insertions se font toujours ` la fin de la liste r. Ici on dira. e En regroupant le coˆt des diverses instructions de coˆt constant de la m´thode insertionSort u u e selon qu’elles sont ex´cut´es une ou n fois. Estimer le coˆt du tri de listes d’entiers tir´s au hasard. Plus exactement encore. En premi`re approximation. Pour un tri on a tendance e e e a ` compter les comparaisons entre ´l´ments. LISTES Notons que pour borner I(n + 1) nous avons suppos´ qu’un appel r´cursif ´tait effectu´. la valeur des e e e constantes k1 et k0 importe peu et on en d´duit l’information pertinente que I(n) est en O(n). nous disposons d’une estimation r´aliste du coˆt global. insertionSort appelle e e n fois insert qui est en O(k) pour k ≤ n. a u u e Solution. le coˆt des op´rations en temps constant effectu´es lors e u e e d’une it´ration de la boucle de insertionSort peut ˆtre affect´ aux comparaisons effectu´es e e e e par l’appel ` insert de cette it´ration. En effet. pour n assez grand. ` e u a une comparaison effectu´e dans un appel donn´ ` insert nous associons toutes les op´rations du e ea e corps de cette m´thode . Il s’agit d’un coˆt dans le cas le pire en u u raison des majorations effectu´es (ici sur I(n) principalement). a e Exercice 7 Donner des exp´riences particuli`res qui conduisent d’une part ` un coˆt en n2 et e e a u d’autre ` un coˆt en n. I(0) = 0 1 ≤ I(k + 1) ≤ k On encadre ensuite facilement le nombre de comparaisons effectu´es par insertionSort pour e une liste de taille n + 1. Nous y reviendrons. le e e a coˆt S(n) d’un appel de cette m´thode est born´ ainsi : u e e n−1 S(n) ≤ k=0 I(k) + k2 · n + k3 ≤ k1 · n(n − 1) + (k0 + k2 ) · n + k3 2 Et donc le coˆt de insertionSort est en O(n2 ). et en tenant compte des n appels ` insert. Trier des listes de taille croissantes et d´j` tri´es en ordre croissant donne un coˆt ea e u 2 . S(n) est born´e e 2 ) et born´e inf´rieurement par une une fonction en sup´rieurement par une fonction en Θ(n e e e Θ(n). Il est rappel´ (cf. et d’autre part. d’une part. Notons que la notation f (n) e est en O(g(n)) (il existe une constante k telle que. et que insertionSort est donc en O(n2 ). e u ou bien on peut affecter un compte born´ d’op´rations ´l´mentaires ` chacune des op´rations e e ee a e compt´es. Mais si les listes sont tri´es en en n a e ordre d´croissant. n ≤ S(n + 1) ≤ n(n + 1) 2 Par cons´quent S(n) effectue au plus de l’ordre de n2 comparaisons et au moins de l’ordre e de n comparaisons. on a f (n) ≤ k · g(n)) est particuli`rement justifi´e pour les coˆts dans le cas le pire. Il est e e e e donc imm´diat que I(n) est major´e par k1 · n + k0 .26 CHAPITRE I. On peut aussi compter pr´cis´ment une op´ration en particulier. alors les insertion se font toujours au d´but de la liste r et le coˆt est en n. mais comptons d’abord les comparaisons effectu´es par un appel e e a ` insert.

la fusion e e e e de deux listes tri´es est une liste tri´e qui regroupe les ´l´ments des deux listes fusionn´es. Y ′ ) et x > y . ce qui confirme peut-ˆtre le r´sultat de notre e e u e e premier raisonnement hasardeux. Y ) la fusion (M pour merge). Y )). On a donc le coˆt en moyenne ee u 1 ˆ S(n) = 0 + 2 Autrement dit 1 3 (n + 1)(n + 2) ˆ − ln n + O(1) = n2 + n − ln n + O(1) S(n) = 4 4 4 Soit finalement le coˆt moyen est en Θ(n2 ). Y ′ )).2 Tri fusion Le tri par insertion r´sulte essentiellement de l’op´ration d’insertion dans une liste tri´e. on peut estimer le nombre moyen de comparaisons. Il y a deux cas ` e a consid´rer e Base De toute ´vidence. qui par hypoth`se d’induction renvoie une liste tri´e regroupant les ´l´ments e e ee de X ′ et de Y . quand X = (x. Un e e e autre tri plus efficace r´sulte de l’op´ration de fusion de deux listes tri´s. Y ). le nombre moyen de comparaisons pour une insertion est donc e 1 1 (1 + 2 + · · · + k − 1 + k − 1) = k k k(k + 1) −1 2 Par ailleurs. si les permutations e e sont ´quiprobables. u o Plus rigoureusement. X ′ ). Sans que la programmation soit bien difficile. TRI DES LISTES 27 Enfin dans une liste quelconque. les permutations de n ´l´ments distincts se repartissent uniform´ment selon les ee e ensembles de leurs k premiers ´l´ments. 1 2(2 + 1) − 1 + ··· + 2 k 1 k(k + 1) − 1 + ··· + 2 n n(n + 1) −1 2 3. ∅) = X e Induction On a X = (x. Y ) = Y et M (X. On peut pareillement estimer le coˆt en m´moire. M (X ee e M (X. parmi toutes les permutations ee e de k ´l´ments distincts. Ici on peut se contenter de compter les u e cellules de liste allou´es. M (X. Y ′ ). Proc´dons ` l’appel r´cursif ee e e e a e M (X ′ . c’est notre premier exemple de fonction qui op`re par induction sur deux listes. Y = (y. Y ) c’est ` dire que la liste ee a ′ . qui regroupe bien tous les ´l´ments de X et Y . M (∅. Y ) = (y. x minore tous les ´l´ments de M (X ′ . Clairement. On note au passage que le coˆt moyen est ` peu u u a pr`s en rapport moiti´ du coˆt dans le cas le pire. Cette e e ee e op´ration peut ˆtre programm´e efficacement. est tri´e. X ′ ). a e e Si x ≤ y. soit on pose M (X. Soit M (X. X ′ ) et Y = (y. Par d´finition. quand X = (x. Donc. les sites d’insertion du dernier ´l´ment parmi les k − 1 premiers tri´s se ee ee e r´partissent uniform´ment entre les k sites possibles. au moins dans le cas plus facile du tri des listes de n entiers deux ` deux distincts. Y ) = (x. Dans ces conditions. On distingue deux cas ` peu pr`s sym´triques. On raisonne comme ci-dessus. mais commen¸ons par la programmer correctee e e c ment. Y )). On pose donc (x. Consid´rons l’insertion du k-i`me a e e ´l´ment dans la liste r tri´e qui est de taille k − 1. M (X ′ .3. alors y < x et donc y ≤ x. alors x minore non seulement tous les ´l´ments de X ′ (X est tri´e) mais aussi ee e tous les ´l´ments de Y (transitivit´ de ≤ et Y est tri´e). les insertions se feront plutˆt vers le milieu de la liste r et le o coˆt sera plutˆt en n2 . Y ′ ) et x ≤ y Sinon. et voir qu’un tri alloue au plus de l’ordre de n2 cellules et au moins de e l’ordre de n cellules. Y = (y.

val < xs. zs) . en prenant bien garde d’appeler r´cursivement a e e a e mergeSort exclusivement sur des listes de longueur strictement plus petite que la longueur de xs. } } CHAPITRE I. car les appels r´cursifs s’effectuent sur une e e structure plus petite que la structure pass´e en argument. en comptant les u comparaisons entre ´l´ments.val. // k+1 a la parit´ oppos´e ` celle de k e e a } . e e e static List mergeSort( List xs) { List ys = null. ys)) . merge(xs. // et alors xs est tri´e e } else { // Les longueurs de ys et sz sont < ` la longueur de xs a return merge(mergeSort(ys). on peut regrouper les ´l´ments de rangs pair et impair e ee dans respectivement deux listes. . un appel donn´ a e e effectue un nombre born´ d’op´rations toutes en coˆt constant. e } // NB: d´sormais xs != null && ys != null else i f (xs. e Estimons le coˆt de la fusion de deux listes de tailles respectives n1 et n2 . i f (zs == null) { // xs a z´ro ou un ´l´ment e e e return xs . puisque compter les comu paraisons revient quasiment ` compter les appels et que. il vient static List merge( List xs. } else { zs = new List (p. que l’on trie (inductivement) puis on fusionne les deux e listes.next.val. ici une paire de listes. Au pire. p = p.val.next)) . e boolean even = true . Pour s´parer xs en deux listes.val <= ys.next ) { i f (even) { ys = new List (p.28 Et en Java.val) { return new List (xs. ys. e e u Trier une liste xs ` l’aide de la fusion est un bon exemple du principe diviser pour r´soudre. ee min(n1 . il y a une comparaison par ´l´ment de la liste produite. tous les ´l´ments d’une des listes sont inf´rieurs ` ceux ee ee e a de l’autre liste. . // z´ro est pair for ( List p = xs . mergeSort(zs)) . merge(xs. hors un appel r´cursif. LISTES On observe que la m´thode merge termine toujours. . n2 ) ≤ n1 + n2 Par ailleurs M (n1 . } } . p != null . Il reste ` proc´der aux appels r´cursifs et ` la fusion. ys) . List ys) { i f (xs == null) { return ys . n2 ) est une estimation raisonnable du coˆt global. zs = null . Au mieux. } else i f (ys == null) { return xs . n2 ) ≤ M (n1 .val return new List (ys.val. . a e On s´pare d’abord xs en deux listes. } else { // NB: ys. comme proc`de le d´but du code de la m´thode mergeSort. . } even = !even .

puisque que u c e l’on peut affecter le coˆt de entre une et deux it´rations de la boucle de s´paration ` chaque u e e a comparaison de la fusion. Ce qui suffit pour prouver l’encadrement pour tout p. on a U (2p ) ≤ U (n) < U (2p+1 ) Posons D(n) = U (n + 1) − U (n). il existe un unique p. il vient donc 1/2 · log2 (n/2) · n/2 ≤ U (n) . Par ailleurs on sait d´j` ea que U (2p ) vaut 1/2 · p · 2p . On a donc e l’encadrement 1/2 · p · 2p ≤ S(2p ) ≤ p · 2p Soit un coˆt de l’ordre de n log n qui se g´n´ralise ` n quelconque (voir l’exercice). Exercice 8 (Franchement optionnel) G´n´raliser l’ordre de grandeur du compte des come e paraisons ` n quelconque. TRI DES LISTES Comptons les comparaisons qui sont toutes effectu´es par la fusion : e S(0) = 0 S(1) = 0 S(n) = M (⌈n/2⌉ . ⌊n/2⌋) + S(⌈n/2⌉) + S(⌊n/2⌋). le compte des comparaisons estime le coˆt global de fa¸on r´aliste. avec 2p ≤ n < 2p+1 et on a donc 1/2 · p · 2p ≤ U (n) Par ailleurs. a Solution. pour n > 1 on a n/2 < 2p . T (p + 1) = k + T (p) de solution k · p. Le point u e e a important est que le tri fusion effectue toujours de l’ordre de n log n comparaisons d’´l´ments. pour n > 1 29 Soit l’encadrement ⌊n/2⌋ + S(⌈n/2⌉) + S(⌊n/2⌋) ≤ S(n) ≤ n + S(⌈n/2⌉) + S(⌊n/2⌋) Si n = 2p+1 on a la simplification tr`s nette e 2p + 2 · S(2p ) ≤ S(2p+1 ) ≤ 2p+1 + 2 · S(2p ) Ou encore 1/2 + S(2p )/2p ≤ S(2p+1 )/2p+1 ≤ 1 + S(2p )/2p Les r´currences sont de la forme T (0) = 0. Il vient alors D(0) = 0 D(1) = 1 D(2q + 2) = D(q + 1) D(2q + 3) = 1 + D(q + 1) La suite D(n) est donc de toute ´vidence ` valeurs dans N (c’est-`-dire D(n) ≥ 0) et on a e a a certainement D(n) > 0 pour n impair. D´finissons une premi`re suite U (n) pour minorer S(n) e e U (0) = 0 U (1) = 0 U (2q +2) = q +1+2·U (q +1) U (2q +3) = q +1+U (q +1)+U (q +2) Notons que nous avons simplement d´taill´ e e U (n) = ⌊n/2⌋ + U (⌊n/2⌋) + U (⌈n/2⌉) Si on y tient on peut montrer U (n) ≤ S(n) par une induction facile. Soit maintenant n > 0.3. ee Par ailleurs. la fonction x · log2 (x) est croissante. tandis U (n) < U (2p+1 ) r´sulte de ce que 2p+1 − 1 est e e impair. En effet U (2p ) ≤ U (n) r´sulte de D(n) ≥ 0. Or. Toute l’astuce est de montrer que pour p assez grand et n tel que 2p ≤ n < 2p+1 .

ce qui est l’essentiel. O` de l’ordre de 1000 lignes at List.609787 Exception in thread "main" java.lang. puis major´e par log2 (2n) · 2n. Les ´l´ments e ee des listes sont les N premiers entiers dans le d´sordre. on borne sup´rieurement S(n) par la suite e e V (0) = 0 V (1) = 0 CHAPITRE I. Nous obtenons : N=10000 T=0. nous essayons de mesurer le temps d’ex´cution du tri fusion pour des valeurs e e de N plus grandes.merge(List. Nous expliquerons plus tard pr´cis´ment ce qui se passe.java:78) . e 30 25 20 T (s) 15 10 5 3 3 3 3 3 3 I M 3 3 + 3 3 3 3 3 3 + + + + + + + 3 3 3 3 3 3 3 + + + + + + + + + + + + + 0 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 N Cette exp´rience semble confirmer que le tri par insertion est quadratique. e ee e e e Pour le moment.30 De mˆme.merge. notre e encadrement est sans doute un peu large. u e On voit que notre programme ´choue en signalant que l’exception StackOverflowError e (d´bordement de la pile) a ´t´ lanc´e. .3 Trier de grandes listes Pour confirmer le r´alisme des analyses de complexit´ des tris. Et nous touchons l` une limite de la a programmation r´cursive. merge appelle merge qui appelle merge qui etc. . alors que nous avons programm´ une m´thode qui semble e e e . .411111 N=30000 T=0. En effet. Elle montre surtout e que le tri fusion est bien plus efficace que le tri par insertion. nous trions des listes de N e e entiers et mesurons le temps d’ex´cution des deux tris par insertion I et fusion M.StackOverflowError at List.merge(List. contentons nous d’admettre que le nombre d’appels de m´thode en attente est e limit´. Encourag´s. sont effac´es. .093999 N=20000 T=0.java:80) at List. Soit enfin e 1/2 · log2 (n/2) · n/2 ≤ S(n) ≤ log2 (2n) · 2n On peut donc finalement conclure que S(n) est en Θ(n·log n). LISTES V (n) = n + V (⌈n/2⌉) + V (⌊n/2⌋)) Suite dont on montre qu’elle est croissante. 3. Si on souhaite estimer les constantes cach´es dans le Θ et pas seulement l’ordre de grandeur asymptotique. Or ici. et merge doit attendre le retour e de merge qui doit attendre le retour de merge qui etc.

et de changer le tableau en liste. e Plutˆt que de simplement mesurer le temps d’ex´cution du nouveau mergeSort. il suffit de changer cette liste en tableau. On emploie la technique de l’initialisation diff´r´e (voir l’exercice 2).val) .val) .next .next = new List (xs. a Pour ´viter les centaines de milliers d’appels imbriqu´s de merge nous pouvons reprogrammer e e la fusion it´rativement. TRI DES LISTES 31 th´oriquement capable de trier des centaines de milliers d’entiers. package java.next . ys = ys. certainement inf´rieur e a e e a ` 32 dans nos exp´riences o` N est un int . } return r . } else { last. } else { last. ys = ys.val) { r = new List (xs. une des deux listes peut ne pas ^tre vide */ e i f (xs == null) { last. i f (ys == null) return xs . xs != null . xs = xs.val <= ys. int k = 0 .next. List ys) { i f (xs == null) return ys . xs = xs. } else { r = new List (ys. La m´thode statique sort (classe a e e Arrays. // Derni`re cellule ` nouveau e a } /* Ce n’est pas fini. k++) { . de trier le tableau. puisque mergeSort elle mˆme e est r´cursive ? Oui. et donc le e e nombre d’appels imbriqu´s de mergeSort vaut au plus ` peu pr`s log2 (N ). e ee puisqu’il est ici crucial de construire la liste r´sultat selon l’ordre des listes consomm´es. } La nouvelle m´thode merge n’a rien de difficile ` comprendre. // Derni`re cellule de la liste r´sultat e e /* R´gime permanent */ e while (xs != null && ys != null) { i f (xs. ee e e Mais attendons ! Le nouveau tri peut il fonctionner en pratique. } last = last. // Longueur de la liste int [] t = new int[sz] .next . } List last = r . nous ne pouvons pas l’ex´cuter e e au del` de 30000 entiers. Ceci illustre qu’il n’y a pas lieu de (( d´r´cursiver )) e u ee syst´matiquement.util) trie un tableau de int pass´ en argument.val) { last.val <= ys.val) . Puisque nous tenons e a ` trier une liste.next = xs .val) .3.next = new List (ys. Il faut aussi ne pas oublier d’initialiser le champ next de la toute derni`re cellule allou´e.next . for ( List p = xs . nous allons o e le comparer ` un autre tri : celui de la biblioth`que de Java. static List arraySort( List xs) { // Copier les el´ments de xs dans un tableau t e int sz = card(xs) . e e static List merge( List xs. xs = xs.next . i f (xs. /* Ici le resultat a une premi`re cellule */ e /* reste ` trouver ce qui va dedans */ a List r = null .next = ys . car mergeSort s’appelle sur des listes de longueur divis´e par deux. une fois assimil´e l’initialisation e a e diff´r´e.

C’est-`-dire que les proe a grammes qui se servent de la classe des listes le font directement. Cette technique est parfaitement correcte et f´conde.32 CHAPITRE I.5 4 3. } return r . e 4 Programmation objet Dans tout ce chapitre nous avons expos´ la structure des listes. Supposons par exemple que nous voulions a impl´menter les ensembles d’entiers. Dans les e mesures. mais qu’il reste des progr`s ` accomplir. e class Set { Set() { .util. M est notre nouveau tri fusion. tandis que A est le tri arraySort. Chapitre 12] par ex. ` A vrai dire. } // Trier t java. 5 4.) qui n’alloue aucune cellule.sort(t) . LISTES t[k] = xs.5 0 3 M 3 3 A + 3 3 3 3 3 3 3 3 3 3 3 3 3 + 3 + + + + + + + + + + + + + + + + + + + 0 50000 100000 150000 200000 250000 300000 N 3 3 3 3 On voit que notre m´thode mergeSort conduit ` un temps d’ex´cution qui semble ` peu pr`s e a e a e lin´aire. en pleine conscience qu’ils manipulent des structures de listes.val . Il est alors plus conforme ` l’esprit de la programmation e a objet de d´finir un objet (( ensemble d’entiers )) Set.5 3 T (s) 2.5 2 1. De fait. k--) { r = new List (t[k]. e bien au contraire. on obtient des temps bien meilleurs. en programmant un tri fusion destructif e e a (voir [7. car elle est e e e ´crite avec beaucoup de soin et est donc tr`s efficace [1]. r) .5 1 0.Arrays.1 Ensembles non mutables Sp´cifions donc une classe Set des ensembles. for (k = sz-1 . et c’est e e plus que raisonnable. 4. qui domine asymtotiquement nos transferts u de liste en tableau et r´ciproquement (en O(N )). les temps sont de l’ordre du double de ceux du tri de la bibliot`que. attention ` l’ordre ! e e a List r = null . un coˆt du tri en O(N log N ). Cette mise en forme (( objet )) n’abolit pas e la distinction fondamentale des deux sortes de structures de donn´es. } . Observons qu’en supposant. mais e elle ne correspond pas ` l’esprit d’un langage objet. mutable et non mutable. // Fabriquer une liste avec les ´l´ments de t. k >= 0 . . . } Employer une m´thode de tri de la biblioth`que est probablement une bonne id´e. on obtient un tri en O(N log N ).

} // Partie visible Set () { xs = null .xs = xs . . x)) . } } La variable d’instance xs et le constructeur qui prend une liste en argument sont priv´s. 1) (voir section 2.add(3) . Bien entendu.remove(2) . L’ensemble vide est maintenant un objet construit par new Set (). pour produire un ensemble augment´. } Set remove(int x) { .add(xs. private Set ( List xs) { this. int x) La nouvelle m´thode add n’est plus statique. s1. x)) .remove(xs. on utilise e e maintenant la notation objet : par exemple s. PROGRAMMATION OBJET Set add(int x) { .2).4. Nous obtenons l’´tat m´moire simplifi´ e e e s3 xs 3 s2 xs 2 s1 xs 1 s0 xs La m´thode remove (page 12) ne modifie pas les champs des cellules de listes. En effet. } } 33 Comparons la signature de m´thode add des Set ` celle de la m´thode add de la classe List . il faut qu’une structure concr`te regroupe les ´l´ments des ensembles. s0. . de sorte e que les clients de Set ne peuvent pas manipuler la liste cach´e. soit le bout de programme Set Set Set Set s0 s1 s2 s3 = = = = new Set () . il importe que les listes soient trait´es comme des listes e non-mutables. On note que pour pouvoir e e e appeler la m´thode add sur tous les objets Set. de sorte que que e le programme Set s4 = s3. s1 et s0. e Comme le montre l’exemple suivant. class Set { // Partie priv´e e private List xs . . ıne . il n’est plus possible d’encoder l’ensemble vide e par null . s2. } Set add(int x) { return new Set ( List . Le plus e ee simple est d’encapsuler une liste dans chaque objet Set. e a e static List add( List xs.add(s. n’entraˆ aucune modification des ensembles s2.add(1) renvoie le nouvel ensemble s ∪ { 1} — alors que pr´c´demment on ´crivait List .add(1) .add(2) . . } Set remove(int x) { return new Set ( List . Les (( clients )) sont les parties e du programme qui appellent les m´thodes de la classe Set.

comme dans la m´thode remove qui appelle List . e e e e a ajoute l’´l´ment x ` s.nremove(x. ` savoir une meilleure structuration. l’emploi des listes mutables au prix d’un risque e mod´r´.nremove. 2} car s1 et s2 sont deux noms du mˆme ensemble imp´ratif.toString(xs) . Cette id´e s’exprime simplement en Java.out. xs) . } public String toString() { return List . afin de garantir que la classe Set est la seule ` manipuler cette liste. ´tant donn´ un ensemble s. e e Pour programmer la nouvelle classe Set des ensembles imp´ratifs. appliquer l’op´ration add( int x) ` s. e Les deux avantages de la technique. mais n’impose en aucun cas. Mais cette fois. s1. Set s2 = s1 .2 Ensembles mutables On peut aussi vouloir modifier un ensemble et non pas en construire un nouveau. En tout cas.add(2) .add(1) .34 CHAPITRE I. e e . // Partie accessible Set () { this.add (x. Cette e pr´caution autorise. On ajoute alors un ´l´ment x ` s par e ee a un appel de m´thode s. comme des objets. } } a Le champ xs est priv´. Le programme affiche {1. e e 4. les m´thodes des objets Set modifient la variable d’instance. xs) . en faisant de s un objet (d’une ee a e nouvelle classe Set) et de add une m´thode de cet objet. Le caract`re imp´ratif des ee e e e ensembles Set est clairement affirm´ et on ne sera (peut-ˆtre) pas surpris par l’affichage du e e programme suivant : Set s1 = new Set() . et la signature de add la plus naturelle est e void add(int x) La nouvelle signature de add r´v`le l’intention imp´rative : s connu est modifi´ et il n’y a donc e e e e pas lieu de le renvoyer apr`s modification. s1.xs = null . e class Set { // Partie priv´e e private List xs . L’ene e e e capsulage se rencontre plus fr´quemment quand il s’agit de prot´ger une structure mutable. on a recours encore une fois ` la technique d’encapsulage. Les objets de la classe Set poss`dent une variable d’instance a e qui est une List . } void add(int x) { xs = List . LISTES s3 xs 3 3 s4 xs s2 xs 2 s1 xs 1 s0 xs Il semble bien que ce style d’encapsulage d’une structure non-mutable est peu utilis´ en e pratique. y compris l’ensemble vide. la biblioth`que de Java ne propose pas de tels ensembles (( fonctionnels )).println(s2) . System. Il s’agit d’un point de vue imp´ratif. } void remove(int x) { xs = List . et une repr´sentation a e uniforme des ensembles. ne justifient sans doute pas la cellule de m´moire suppl´mentaire allou´e syst´matiquement pour chaque ensemble.add(x).

nremove(ys. a a On tente donc : void diff(Set s) { for ( List ys = s.diff(s) e Solution.xs. } O` List .next. on a e e e this . c’est la raison e e mˆme de l’encapsulage.xs.xs = List .xs. ys != null .xs 1 ys 2 3 2 3 . Utilsons maintenant les listes mutables. Et ` la fin de premi`re it´ration. mais pas celles accessibles ` partir de s.next) this.xs) .xs. Ne pas oublier de e consid´rer le cas s. // enlever les ´l´ments de s Aller d’abord au plus simple.diff est non destructive (voir exercice 3). Cet effet ici e e recherch´ est facilement atteint en mutant le champ priv´ xs des objets Set. Au d´but de la premi`re it´ration.xs. } L’emploi d’une nouvelle variable ys s’impose. car il ne faut certainement pas modifier s.xs = List . ys = ys. on souhaite faire profiter e e e e l’ensemble du programme de toutes les modifications r´alis´es sur cette structure. Plus pr´cis´ment on ne doit pas ´crire for ( . On peut ´crire d’abord e void diff(Set s) { this. (3. this.xs != null . s. Compte tenu de la s´mantique. PROGRAMMATION OBJET 35 s2 2 s1 xs 1 Plus g´n´ralement. s. On v´rifie que l’auto-appel s.xs 1 ys Dans ces conditions. enlever ys. Notons au e e e passage que la d´claration private List xs n’interdit pas la modification de s. ∅))). on a a e e this .xs = s.xs. en utilisant une structure de donn´e imp´rative.xs .diff(s) est correct.xs revient simplement ` renvoyer a this . mˆme si c’est de justesse.xs : le code de e diff se trouve dans la classe Set et il a plein acc`s aux champs priv´s de tous les objets de la e e classe Set.next ). s. Supposons par e e exemple que xs pointe vers la liste (1. puis utiliser les structures de donn´es mutables. il semble l´gitime de e e modifier les cellules accessibles ` partir de this .xs au lieu de xs pour u u e bien insister.val. e Exercice 9 Enrichir les objets de la classe Set avec une m´thode diff de signature : e e e void diff(Set s) . et o` on a ´crit this . (2.diff(this.4.val (1) de la liste this .xs) .

36 CHAPITRE I. on a e a e e this . les listes circulaires sont naturelles. on peut ˆtre tent´ de repr´senter les e e e d´cimales de 1/2 = 0. e A4 A3 B1 B0 B1 B0 A0 A2 A1 B2 B3 B2 B3 Les polygones ´tant ferm´s. 5 Compl´ment : listes boucl´es e e Des ´l´ments peuvent ˆtre organis´s en s´quence. e e e e 5. La s´quence des points est celle qui correspond ` l’organisation des sommets et e a des arˆtes.xs vaut bien null en sortie de m´thode diff. this . ainsi les polygones (B0 B1 B2 B3 ) et (B0 B1 B3 B2 ) sont distincts. sans que n´cessairement la s´quence soit ee e e e e e finie. on a e e this .090909 · · · et 1/6 = 0. et enfin une liste boucl´e la plus g´n´rale. e e e repointe sur une de ses propres cellules. il est pratique de repr´senter un polygone ferm´ par la liste circulaire de e e ses sommets.1 Repr´sentation des polygones e Plus couramment.xs.16666 · · · par e 5 0 9 1 6 Nous distinguons ici.xs 1 2 ys 3 1 2 ys this . 1/11 = 0.5. ouf. de sorte qu’en d´but puis fin de seconde it´ration. Par exemple. Si la s´quence est p´riodique. L’ordre des sommets dans la e e liste est significatif. nous d´finissons une classe des cellules de liste e e de points. .xs 3 Et donc. LISTES Les it´rations suivantes sont similaires : on enl`ve toujours le premi`re ´l´ment de la liste point´e e e e ee e par this .xs 1 ys Puis au d´but et ` la fin de la derni`re it´ration. Pour repr´senter les polygones en machine.xs 3 2 3 1 ys 2 this . elle devient repr´sentable par une liste qui au lieu de finir. ensuite une liste circulaire — le retour est sur la premi`re cellule. Tout cela est quand mˆme e e assez acrobatique. d’abord une liste finie.

´ ´ 5. COMPLEMENT : LISTES BOUCLEES import java.awt.* ; class Poly { private Point val ; private Poly next ;

37

private Poly (Point val, Poly next) { this.val = val ; this.next = next ; } . . . } Tout est priv´, compte tenu des manipulations dangereuses auxquelles nous allons nous livrer. e La classe Point des points est celle de la biblioth`que (package java.awt), elle est sans surprise : e p.x et p.y sont les coordonn´es, p.distance(q) renvoie la distance entre les points p et q. e Nous allons concevoir et impl´menter un algorithme de calcul de l’enveloppe convexe d’un e nuage de points, algorithme qui s’appuie naturellement sur la repr´sentation en machine des e polygones (convexes) par les listes circulaires. Mais commen¸ons d’abord par nous familiariser c tant avec la liste circulaire, qu’avec les quelques notions de g´om´trie plane n´cessaires. Pour e e e nous simplifier un peu la vie nous allons poser quelques hypoth`ses. e Les polygones ont toujours au moins un point. Par cons´quent, null n’est pas un polygone. e Les points sont en position g´n´rale. Ce qui veut dire que les points sont deux ` deux e e a distincts, et qu’il n’y a jamais trois points align´es. e Le plan est celui d’une fenˆtre graphique Java, c’est ` dire que l’origine des coordonn´es e a e est en haut et ` gauche. Ce qui entraˆ que le rep`re est en sens inverse du rep`re usuel a ıne e e du plan (l’axe des y pointe vers le bas).

5.2

Op´rations sur les polygones e

L’int´rˆt de la liste circulaire apparaˆ pour, par exemple, calculer le p´rim`tre d’un polygone. ee ıt e e Pour ce faire, il faut sommer les distances (A0 A1 ), (A1 A2 ), . . . (An−1 A0 ). Si nous repr´sentions e le polygone par la liste ordinaire de ses points, le cas de la derni`re arˆte (An−1 A0 ) serait e e particulier. Avec une liste circulaire, il n’en est rien, puisque le champ next de la cellule du point An−1 pointe vers la cellule du point A0 . La sommation se fait donc simplement en un parcours des sommets du polygone. Afin de ne pas sommer ind´finiment, il convient d’arrˆter e e le parcours juste apr`s cette (( derni`re )) arˆte, c’est ` dire quand on retrouve la (( premi`re )) e e e a e cellule une seconde fois. static double perimeter(Poly p) { double r = 0.0 ; Poly q = p ; do { r += q.val.distance(q.next.val) ; q = q.next ; e e e } while (q != p) ; // Attention : diff´rence des r´f´rences (voir B.3.1.1) return r ; } L’usage d’une boucle do {. . .} while (. . .) (section B.3.4) est naturel. En effet, on veut finir d’it´rer quand on voit la premi`re cellule une seconde fois, et non pas une premi`re fois. On e e e observe que le p´rim`tre calcul´ est 0.0 pour un polygone ` un sommet et deux fois la distance e e e a (A0 A1 ) pour deux sommets. Nous construisons un polygone ` partir de la s´quence de ses sommets, extraite d’un fichier a e ou bien donn´e ` la souris. La lecture des sommets se fait selon le mod`le du Reader (voir e a e section B.6.2.1). Soit donc une classe PointReader des lecteurs de points pour lire les sources de points (fichier ou interface graphique). Ses objets poss`dent une m´thode read qui consomme e e

38

CHAPITRE I. LISTES

et renvoie le point suivant de la source, ou null quand la source est tarie (fin de fichier, double clic). Nous ´crivons une m´thode de construction du polygone form´ par une source de points. e e e static Poly readPoly(PointReader in) { Point pt = in.read() ; i f (pt == null) throw new Error ("Polygone vide interdit") ; Poly r = new Poly (pt, readPoints(in)) ; nappend(r,r) ; // Pour refermer la liste, voir exercice 4 return r ; } private static Poly readPoints(PointReader in) { Point pt = in.read() ; i f (pt == null) { return null ; } else { return new Poly(pt, readPoints(in)) ; } } La programmation est r´cursive pour facilement pr´server l’ordre des points de la source. La e e liste est d’abord lue puis referm´e. C’est de loin la technique la plus simple. Conform´ment e e ` la place, il lance a ` nos conventions, le programme refuse de construire un polygone vide. A l’exception Error avec pour argument un message explicatif. L’effet du lancer d’exception peut se r´sumer comme un ´chec du programme, avec affichage du message (voir B.4.2 pour les e e d´tails). e Nous nous posons maintenant la question d’identifier les polygones convexes. Soit maintenant P un polygone. La paire des points p.val et p.next.val est une arˆte (A0 A1 ), c’est-`-dire un e a segment de droite orient´. Un point quelconque Q, peut se trouver ` droite ou ` gauche du e a a segment (A0 A1 ), selon le signe du d´terminant det(A0 A1 , A0 P ). La m´thode suivante permet e e donc d’identifier la position d’un point relativement ` la premi`re arˆte du polygone P . a e e private static int getSide(Point a0, Point a1, Point q) { return (q.y - a0.y) * (a1.x - a0.x) - (q.x - a0.x) * (a1.y - a0.y) ; } static int getSide(Point q, Poly p) { return getSide (p.val, p.next.val, q) ; } Par l’hypoth`se de position g´n´rale, getSide ne renvoie jamais z´ro. Une v´rification rapide e e e e e nous dit que, dans le syst`me de coordonn´es des fenˆtres graphiques, si getSide(q, p) est e e e n´gatif, alors le point q est a gauche de la premi`re arˆte du polygone p. e ` e e Un polygone est convexe (et bien orient´) quand tous ses sommets sont ` gauche de toutes e a ses arˆtes. La v´rification de convexit´ revient donc ` parcourir les arˆtes du polygone et, pour e e e a e chaque arˆte ` v´rifier que tous les sommets autres que ceux de l’arˆte sont bien ` gauche. e a e e a static boolean isConvex(Poly p) { Poly a = p ; // a est l’ar^te courante e do { Poly s = a.next.next ; // s est le sommet courant while (s != a) { i f (getSide(s.val, a) > 0) return false ; s = s.next ; // sommet suivant } e a = a.next ; // ar^te suivante

´ ´ 5. COMPLEMENT : LISTES BOUCLEES } while (a != p) ; return true ; }

39

On note les deux boucles imbriqu´es, une boucle sur les sommets dans une boucle sur les arˆtes. e e La boucle sur les sommets est une boucle while par souci esth´tique de ne pas appeler getSide e avec pour arguments des points qui ne seraient pas en position g´n´rale. e e Exercice 10 Que se passe-t-il si p est un polygone ` un, ` deux sommets ? a a Solution. Dans les deux cas, aucune it´ration de la boucle int´rieure sur les sommets n’est e e ex´cut´e. Par cons´quent les polygones a un et deux sommets sont jug´s convexes et bien orient´s. e e e ` e e Pour un polygone ` n sommets, la boucle sur les arˆtes est ex´cut´e n fois, et ` chaque fois, la a e e e a boucle sur les sommets est ex´cut´e n − 2 fois. La v´rification de convexit´ coˆte donc de l’ordre e e e e u de n2 op´rations ´l´mentaires. e ee Notons que la m´thode getSide nous permet aussi de d´terminer si un point est ` l’int´rieur e e a e d’un polygone convexe. static boolean isInside(Point q, Poly p) { Poly a = p ; do { i f (getSide(q, a) > 0) return false a = a.next ; } while (a != p) ; return true ; } Notons que la convexit´ permet une d´finition et une d´termination simplifi´e de l’int´rieur. e e e e e La m´thode isInside a un coˆt de l’ordre de n op´ration ´l´mentaires, o` n est la taille du e u e ee u polygone. Exercice 11 Que se passe-t-il si p est un polygone ` un, ` deux sommets ? a a Solution. Le cas limite du polygone ` deux sommets est bien trait´ : pour trois points en a e position g´n´rale, l’un des deux appels ` getSide renvoie n´cessairement un entier positif. e e a e Par cons´quent isInside renvoie n´cessairement false, ce qui est conforme ` l’intuition que e e a l’int´rieur d’un polygone ` deux sommets est vide. e a Il n’en va pas de mˆme du polygone ` un seul sommet, dont l’int´rieur est a priori vide. e a e Or, dans ce cas, getSide est appel´e une seule fois, les deux sommets de l’arˆte ´tant le mˆme e e e e point. La m´thode getSide renvoie donc z´ro, et isInside renvoie toujours true. e e

5.3

Calcul incr´mental de l’enveloppe convexe e

L’enveloppe convexe d’un nuage de points est le plus petit polygone convexe dont l’int´rieur e contient tous les points du nuage. Pour calculer l’enveloppe convexe, nous allons proc´der e incr´mentalement, c’est-a-dire, en supposant que nous poss´dons d´j` l’enveloppe convexe des e e ea points d´j` vus, construire l’enveloppe convexe des points d´j` vus plus un nouveau point. ea ea Soit donc la s´quence de points P0 , P1 , . . . Pk , dont nous connaissons l’enveloppe convexe E = e (E0 E1 . . . Ec ), Et soit Q = Pk+1 le nouveau point. Si Q est ` l’int´rieur de E, l’enveloppe convexe ne change pas. a e

40

CHAPITRE I. LISTES Sinon, il faut identifier deux sommets Ei et Ej , tels que tous les points de E sont ` gauche a de (Ei Q) et (QEj ). La nouvelle enveloppe convexe est alors obtenue en rempla¸ant la c s´quence (Ei . . . Ej ) par (Ei QEj ). e E1 E0 = Ej

E2 E4 Q

E3 = Ei Sans r´aliser une d´monstration compl`te, il semble clair que Q se trouve ` droite des e e e a arˆtes supprim´es et ` gauche des des arˆtes conserv´es. Mieux, lorsque l’on parcourt les e e a e e sommets du polygone, le sommet Ei marque une premi`re transition : Q est ` gauche de e a (Ei−1 Ei ) et ` droite de (Ei Ei+1 ), tandis que le sommet Ej marque la transition inverse. a On peut aussi imaginer que Q est une lampe, il faut alors supprimer les arˆtes ´clair´es e e e de l’enveloppe. Il faut donc identifier les sommets de E o` s’effectuent les transitions (( Q ` gauche puis ` droite )) u a a et inversement. Le plus simple paraˆ de se donner d’abord une m´thode getRight qui renvoie ıt e la (( premi`re )) arˆte qui laisse Q ` droite, ` partir d’un sommet initial suppos´ quelconque. e e a a e static Poly getRight(Poly e, Point q) { Poly p = e ; do { i f (getSide(q, p) > 0) return p ; p = p.next ; } while (p != e) ; return null ; } Notons que dans le cas o` le point Q est ` l’int´rieur de E, c’est-`-dire ` gauche de toutes ses u a e a a arˆtes, getRight renvoie null . On ´crit aussi une m´thode getLeft pour d´tecter la premi`re e e e e e arˆte de E qui laisse Q ` gauche. Le code est identique, au test getSide(q, p) > 0, ` changer e a a en getSide(q, p) < 0, pr`s. e La m´thode extend d’extension de l’enveloppe convexe, trouve d’abord une arˆte qui laisse e e Q ` droite, si une telle arˆte existe. Puis, le cas ´ch´ant, elle continue le parcours, en enchaˆ a e e e ınant un getLeft puis un getRight afin d’identifier les points Ej puis Ei qui d´limitent la partie e (( ´clair´e )) de l’enveloppe. e e private static Poly extend(Poly e, Poly oneRight = getRight(e, q) ; i f (oneRight == null) return e ; // L’ar^te issue de oneRight est e Poly ej = getLeft(oneRight.next, Poly ei = getRight(ej.next, q) ; Poly r = new Poly(q, ej) ; ei.next = r ; return r ; } Point q) { // Le point q est int´rieur e e ´clair´e e q) ; // lumi`re -> ombre e // ombre -> lumi`re e

La structure de liste circulaire facilite l’´criture des m´thodes getRight et getLeft. Elle pere e met aussi, une fois trouv´s Ei et Ej , une modification facile et en temps constant de l’enveloppe e convexe. Les deux lignes qui effectuent cette modification sont directement inspir´es du dese sin d’extension de polygone convexe ci-dessus. Il est clair que chacun des appels ` getRight a et getLeft effectue au plus un tour du polygone E. La m´thode extend effectue donc au pire e

e static Poly convexHull(PointReader in) { Point q1 = in. car dans ce cas il existe une arˆte ´clair´e et une arˆte e e e e e e sombre. . En raison de sa convexit´ mˆme. en raison de l’alternance des appels a ` getRight et getLeft). Chapitre 35] ou [7. Il nous reste pour calculer l’enveloppe convexe d’un nuage de points lus dans un PointReader a ` initialiser le processus. Supposons connaˆ a e e ıtre un sommet P de l’enveloppe convexe qui est (( ´clair´ )) par le nouveau point Q (c’est-a-dire que e e l’arˆte issue de P laisse Q ` droite). nappend(e. alors on a un coˆt lin´aire.e) . si tous les points de e e a u e l’entr´e se retrouvent au final dans l’enveloppe convexe. pour les a chaˆ ıner selon l’autre sens de rotation. L’enveloppe e e e convexe E est soumise ` la lumi`re ´mise par le nouveau point Q. Poly e = new Poly (q1. Le programme est donc en O(n2 ). e u sur des enveloppes qui poss`dent c ≤ n sommets.read() . // Refermer la liste for ( . un raffinement de la technique incr´mentale inspire un algorithme tout aussi efficace.read() i f (q == null) return e . En effet. dont le coˆt est en O(c). o` c est le nombre de sommets de l’enveloppe u convexe. On ´crit donc. c’est-`-dire sans passer par la face sombre. le coˆt est bien quadratique. De plus ce nouvel algorithme va nous permettre d’aborder une nouvelle sorte de liste. i f (q1 == null || q2 == null) throw new (Error "Pas assez de points") . Le temps e d’ex´cution d´pend parfois crucialement de l’ordre de pr´sentation des points. Malheureusement. on ex´cute de l’ordre de n fois extend. ) { // Idiome : boucle infinie Point q = in. Elle est donc en O(c). e e e e e et l’extension de l’enveloppe revient ` remplacer la face ´clair´e par Ei QEj . } } Pour un nuage de n points. COMPLEMENT : LISTES BOUCLEES 41 trois tours du polygone E (en pratique au plus deux tours. e = extend(e. Le sens de rotation selon les next nous permet de trouver e a la fin de la face ´clair´e directement. . il faut pouvoir tourner dans l’autre e e e sens ! Nous allons donc ajouter un nouveau champ prev ` nos cellules de polygones. Pour pouvoir e e a trouver le d´but de la face ´clair´e tout aussi directement.read() . Point q2 = in. dumoins e asymptotiquement.4 Calcul efficace de l’enveloppe convexe L’enveloppe convexe d’un nuage de n points du plan peut se calculer en O(n log n) op´rations. voir par exemple [2. . tous les points suivants e ´tant int´rieurs ` ce triangle. a e e e e E poss`de une face ´clair´e et une face sombre. Dans le cas o` par e e e u exemple l’enveloppe convexe est form´e par les trois premiers points lus. q) . On peut se convaincre assez facilement que extend fonctionne correctement d`s que E poss`de deux points. e a ` l’aide de l’algorithme classique de la marche de Graham (Graham scan) Nous n’expliquerons pas la marche de Graham. Rappelons l’id´e de l’algorithme incr´mental. null)) . La version e u web du cours pointe vers un programme de d´monstration de cet algorithme. La face ´clair´e va du sommet Ei au sommet Ej . .´ ´ 5. class Poly { . private Poly prev . e 5. new Poly (q2. sous une forme un peu imag´e. Chapitre 25].

val doit ^tre (( eclair´ )) par q (ie getSide(e. return r . que nous pouvons illustrer simplement par le sch´ma e e e suivant : Ej P Q Ei En suivant les champs prev (not´s par des fl`ches courbes).42 private Poly (Point val.q) > 0) e e private static Poly extend2(Poly e.next = r . ei) .val. .prev = r . ej. c’est-`-dire le premier sommet Ei dont est issue une e a a arˆte inverse qui laisse le point Q ` droite.prev. Poly r = new Poly(q. e a .prev = prev . p = p. ej.prev . e e // Attention e. } . elle construit la nouvelle enveloppe en rempla¸ant la face ´clair´e par (Ei QEj ). quand e e e (1) P est le point d’abscisse maximale parmi les points d´j` vus. for ( . Point q) { return getSide(a. Puis. // Appeler l’autre constructeur this. a. Point q) { Poly p = e . ea (2) Q est d’abscisse strictement sup´rieure ` celle de de P . q) .val. ) { i f (getSidePrev(q. Poly ei = getRightPrev(e. Ce sera le cas. q) . . } CHAPITRE I. e a static int getSidePrev(Poly a.next. Poly prev) { this(val. une ın´ structure de donn´es assez sophistiqu´e. Point q) { Poly ej = getLeft(e. } La m´thode extend2 identifie la face ´clair´e pour un coˆt proportionnel ` la taille de cette e e e u a face. } } Et nous pouvons ´crire la nouvelle m´thode d’extension de l’enveloppe convexe. Les c e e manipulations des champs next et prev sont directement inspir´es par le sch´ma ci-dessus. Poly next. . LISTES Nos listes de sommets deviennent des listes doublement chaˆ ees (et circulaires qui plus est). ei. nous trouvons facilement la premi`re e e e arˆte inverse sombre ` partir du point P . next) . } static Poly getRightPrev(Poly e. p) > 0) return p . q) . e e La m´thode n’est correcte que si Q ´claire l’arˆte issue de P .

e a u a Le tri domine et l’algorithme complet est donc en O(n log n).y) return 1 . Poly p1 = new Poly(pt1. i f (compare(e.p2) < 0 static int compare(Point p1. null) . p1. Cet exemple illustre que le choix d’une structure de donn´es appropri´e (ici la liste doublement chaˆ ee) est parfois crucial pour e e ın´ atteindre la complexit´ th´orique d’un algorithme.x < p2. COMPLEMENT : LISTES BOUCLEES 43 En fait l’hypoth`se de position g´n´rale n’interdit pas que deux points poss`dent la mˆme abscisse e e e e e (mais c’est interdit pour trois points). en raison de la convention que l’axe des e e a y pointe vers le bas. P est strictement inf´rieur ` Q selon l’ordre suivant. pour que Q ´claire l’arˆte issue de P . Nous pr´tendons ensuite que le calcul propree ment dit de l’enveloppe est en O(n).prev = p2 . else return 0 . Point p2) { i f (p1. for ( . Ici. On peut trier les n points en O(n log n). e Estimons maintenant le coˆt du nouvel algorithme de calcul de l’enveloppe convexe de u n points.next = p2 .y > p2. comme exprim´ par la m´thode e e e compl`te de calcul de l’enveloppe convexe. il e sera le nouveau point distingu´ lors de la prochaine extension. e e . } } On peut donc assurer la correction de l’algorithme complet en triant les points de l’entr´e e selon les abscisses croissantes. Point pt2 = in.read() .´ ´ 5.y < p2. le coˆt d’un appel ` extend2 est en O(j − i) o` u a u j − i − 1 est le nombre de sommets supprim´s de l’enveloppe ` cette ´tape. i f (q == null) return e .read() . p1.y) return -1 . l’algorithme n’est donc plus e incr´mental. e static Poly convexHullSorted(PointReader in) { Point pt1 = in. null. Evidemment e e e e ´ pour pouvoir trier les points de l’entr´e il faut la lire en entier. Poly p2 = new Poly(pt2. il n’est pas possible de trouver le sommet Ei ` partir d’un sommet de la face a ´clair´e sans faire le tour par la face sombre. . p1.x) return -1 . e e = extend2(e. p2.x > p2. else i f (p1. q) . i f (pt1 == null || pt2 == null || compare(pt1. Or un sommet est e a e supprim´ au plus une fois. e a // p1 < p2 <=> compare(p1.read() . avec une liste chaˆ ee seulement selon e e ın´ les champs next.x) return 1 . En effet. Poly e = p2 . q) >= 0) throw new Error ("Points non tri´s") . En fait. } On note que le point ajout´ Q fait toujours partie de la nouvelle enveloppe convexe. else i f (p1.pt2) >= 0) throw new Error ("Deux premiers point incorrects") .prev = p1 . ) { Point q = in. il e e suffit que l’ordonn´e de Q soit inf´rieure ` celle de P . ce qui conduit ` une somme des coˆts des appels ` extend2 en O(n). null) . Autrement dit. En pareil cas.val. puis les ordonn´es d´croissantes en cas d’´galit´. else i f (p1.

44 CHAPITRE I. LISTES .

e oe oe e 5 4 3 2 1 0 0 1 5 4 3 2 1 0 0 1 Sommet de pile Sortie de file Entr´e de file e un guichet. Dans le ee e ee e cas des piles. (2) ajouter un ´l´ment dans le sac. tandis que la file poss`de une entr´e et une sortie. } E remove() { . il est e logique de repr´senter un sac (d’´l´ments E) par un objet (d’une classe Bag E ) qui poss`de e ee e trois m´thodes. On dit que la pile est une structure LIFO (last-in first-out). e u e ee e e Tout ceci est parfaitement conforme ` l’intuition d’une pile d’assiettes. o` e e u sont ajout´s et d’o` sont retir´s les ´l´ments. ou de la queue devant a Fig. e e 45 . . Dans le cas d’une file c’est le premier ee e e ´l´ment ajout´ qui est retir´. . En Java. } void add(E e) { . . une file (ajouter et retirer de cˆt´s oppos´s). . . . c’est le dernier ´l´ment ajout´ qui est retir´. et que ee e e la file est une structure FIFO (first-in first-out). nous e appellerons des sacs. . on voit que la pile poss`de un sommet. Piles et files se distinguent par la relation entre ´l´ments ajout´s et ´l´ments retir´s. ee ee e Le sac est une structure imp´rative : un sac se modifie au cours du temps. e class Bag E { Bag E { . 1 – Une pile (ajouter et retirer du mˆme cˆt´). ee (3) retirer un ´l´ment du sac (tenter de retirer un ´l´ment d’un sac vide d´clenche une erreur).add(e). . Un sac offre trois op´rations ´l´mentaires : e ee (1) tester si le sac est vide. et sugg`re fortement des techniques d’impl´mentations pour les piles et les files. ce qui modifie l’´tat ee e interne du sac. } // Construire un sac vide boolean isEmpty() { . Si on repr´sente pile et file par des tas de cases. } } Ainsi on ajoute par exemple un ´l´ment e dans le sac b par b.Chapitre II Piles et files Les piles et les files sont des exemples de structures de donn´es que faute de mieux.

en fonction de la probabilit´ p. . PILES ET FILES Exercice 1 Soit la m´thode build suivante qui ajoute tous les ´l´ments d’une liste dans un sac e ee de int puis construit une liste en vidant le sac. Si le sac est une pile. etc. . ajouter e un ´l´ment dans une pile se dit empiler (push). Les piles a a mod´lisent aussi tout syst`me o` l’entr´e et la sortie se font par le mˆme passage oblig´ : e e u e e e wagons sur une voie de garage. 300[.remove(). Si le sac est une file. premier servi e Utiliser une file informatique est donc naturel lorsque l’on mod´lise une file d’attente au sens e courant du terme. Leur patience est une e loi uniforme sur [120 . p = p. ee e 1. . et l’enlever d´filer. for ( . Elle correspond e e pourtant ` une situation courante. Le temps que prend le service d’un client suit une loi uniforme sur [30 . premier-servi n’est pas tr`s populaire. alors build renvoie une copie de la liste p. tandis qu’ajouter un ee e ´l´ment dans une file se dit enfiler. les objets de la classe Random du e e package java.val) .nextInt(max-min) . ee e Dans la vie.1. e Les clients arrivent et font la queue. Il y a un peu de vocabulaire sp´cifique aux piles et aux files. // Source pseudo-al´atoire e static private Random rand = new Random () . // Loi uniforme sur [min. Les clients. t + 1[ est p. . . premier-servi.isEmpty()) r = new List (bag.3. while (!bag. La pile est plus informatique par nature. La probabilit´ d’arriv´e d’un nouveau client dans e e l’intervalle [t . e e Nous impl´mentons la simulation par une classe Simul.4 pour la notion de package et B. Pour r´aliser les tirages al´atoires e e e n´cessaires. r) .max[ static private int uniform(int min. . partent sans ˆtre servis. elle mod´lise directement les files d’attente e e e g´r´es selon la politique premier-arriv´. } Quelle est la liste renvoy´e dans le cas o` le sac est une pile. 1800[ Notre objectif est d’´crire une simulation informatique afin d’estimer le rapport clients repartis e sans ˆtre servis sur nombre total de clients. alors build renvoie une copie en ordre inverse de la liste p. return r .46 CHAPITRE II. .6. Traditionnellement. p != null . Consid´rons un exemple simple : e Un unique guichet ouvert 8h00 cons´cutives (soit 8 × 60 × 60 secondes).1 pour une description de Random).1 Premier arriv´. int max) { return min + rand. . cabine de t´l´ph´rique. s’il attendent trop longtemps. List r = null . ee e 1 ` A quoi ¸a sert ? c La file est peut-ˆtre la structure la plus imm´diate. .next) bag. nous employons une source pseudo-al´atoire. et l’enlever d´piler (pop). static List build( List p) { Bag bag = new Bag () . la politique dernier-arriv´. puis une file ? e u Solution. la survenue de tˆches de plus en plus urgentes.util (voir B.add(p.

e a – Tirer al´atoirement un temps de traitement. Le code suit les grandes lignes de la description pr´c´dente. int arrival. . ajouter le nouveau client (avec sa limite de temps) dans la file d’attente. add et remove d´crites pour les Bag de l’introduction. Quand un client est disponible et que le guichet est libre (si t est sup´rieur ` tFree). – V´rifier qu’il est toujours l` (sinon. n´cessaire pour forcer la division flottante. Il faut e e surtout noter comment on r´sout le probl`me d’une boucle while qui doit d’une part s’efe e fectuer tant que la file n’est pas vide et d’autre part cesser d`s qu’un client effectivement e pr´sent est extrait de la file (sortie par break). passer au (( client )) suivant).1] e e e static private boolean occurs(double prob) { return rand. e 1 .uniform(waitMin. Durant la simulation. e a – Extraire le client de la file. une variable tFree indique la date o` le e u ` guichet sera libre. e Le code qui r´alise cette simulation na¨ en donn´ par la figure 2. Client (int t) { arrival = t . L’impl´mentation d’une file sera d´crite plus tard.nextDouble() < prob . il faut : Faire un tirage al´atoire pour savoir si un nouveau client arrive. La m´thode simul prend e ıve e e la probabilit´ d’arriv´e d’un client en argument et renvoie le rapport clients non-servis sur e e nombre total de clients.` 1. Sans cette e conversion on aurait la division euclidienne. departure . . } } Noter que les constantes qui gouvernent le temps d’attente sont des variables final de la classe Client . ee ee class Client { // Temps d’attente final static int waitMin = 120. e waitMax = 1800 . La simulation est discr`te. ce qui donne l’occasion de calculer l’heure ` laquelle le e a client exasp´r´ repartira s’il il n’a pas ´t´ servi. La figure 3 donne les r´sultats de la simulation pour e diverses valeurs de la probabilit´ prob. 1. Les calculatrices d’une c´l`bre marque ´tats-unienne ont popularis´ une o ee e e notation des calculs dite parfois (( polonaise inverse ))1 ou plus g´n´ralement postfixe. A chaque seconde t. Un objet Client est cr´´ e e ee lors de l’arriv´e du client au temps t. Les calculs e e En hommage au philosophe et math´maticien Jan Lukasiewicz qui a introduit cette notation dans les e ann´es 20. A QUOI CA SERT ? ¸ } // Survenue d’un ´v´nement de probabilit´ prob ∈ [0. e – Si oui.2 Utilisation d’une pile Nous allons prendre en compte la nature tr`s informatique de la pile et proposer un exemple e plutˆt informatique. departure = t+Simul. Il s’agit de moyennes sur dix essais. e – Et ajuster tFree en cons´quence. waitMax) . } 47 Un client en attente est mod´lis´ par un objet de la classe Client . On note aussi la conversion de type explie cite ((double)nbFedUp)/nbClients. Pour l’instant une file (de clients) est un e e e e e objet de la classe Fifo qui poss`de les m´thodes isEmpty.

1 + + + + ++ 0 ++ + + ++ + + ++ + ++ + + 0 0. } } } } return ((double)nbFedUp)/nbClients .01 0. break .isEmpty()) { Client c = f.7 ++ ++ + + ++ + + + + + ++ + ++ + + ++ +++ ++ 0.3 ++ + + + ++ 0.02 0. t < tMax .48 CHAPITRE II. 2 – Code de la simulation. 3 – R´sultat de la simulation de la file d’attente e 0. static double simul(double probNewClient) { Fifo f = new Fifo () . t++) { // Tirer l’arriv´e d’un client dans [t. a i f (c.add(new Client(t)) .remove() . } i f (tFree <= t) { // Le guichet est libre.t+1[ e i f (occurs(probNewClient)) { nbClients++ .departure >= t) { // Le client est encore l` tFree = t + uniform(serviceMin.025 Clients/sec Non-servis/venus . nbFedUp = 0 .4 + + + + + ++ + 0. int tFree = 0 . serviceMax) . f.. } Fig. PILES ET FILES Fig. // Le guichet est libre au temps tFree for (int t = 0 . servir un client (si il y en a encore un) while (!f.2 + ++ + + ++ 0.015 0. int nbClients = 0.005 0.8 ++ + 0. // Sortir de la boucle while } else { // Le client ´tait parti e nbFedUp++ .6 ++ + +++ + ++ +++ + ++ 0.5 ++ + + ++ ++ + ++ + 0.

class Calc { public static void main (String [] arg) { Stack stack = new Stack () .println(stack.err on n’insistera jamais assez). C’est peut-ˆtre vrai.err.length . puis un second e2 . un essai2 nous donne (le sommet de pile est ` droite) : a % java Calc 6 3 2 .. A QUOI CA SERT ? ¸ 49 se font ` l’aide d’une pile. .println(x + " -> " + stack) .push(Integer. } System. k++) { String x = arg[k] .pop(). les nombres sont simplement empil´s. et enfin ` empiler le a e e a r´sultat e2 op e1 . k < arg.. } } Le source contient des messages de diagnostic (sur System. /* Idem pour "*" et "/" */ . i2 = stack. for (int k = 0 .1..1 + / 9 6 . stack.3. i2 = stack.equals("+")) { int i1 = stack.’*’ 6 -> [6] 3 -> [6.* 6 3 2 2 3 6 − 1 1 1 6 + / 9 6 6 9 3 − ∗ 6 3 6 1 6 2 6 3 9 3 3 3 9 un peu d’habitude la notation postfixe est plus commode que la notation usuelle (dite infixe). afin d’´viter son interpr´tation par le shell. 4 – Calcul de l’expression postfixe 6 3 2 .pop() .pop(). En voici pour preuve a e le programme Calc qui r´alise le calcul postfixe donn´ sur la ligne de commande. .* exprim´ en notation postfixe. et l’ex´cution d’une op´ration a e e e op revient ` d’abord d´piler un premier op´rateur e1 . Le fabricant ´tats-unien de calculatrices affirme qu’avec e e Fig.1 + / 9 6 ..equals("-")) { int i1 = stack. } else i f (x.1 + / e e 9 6 .out.push(i2-i1) .parseInt(x)) .pop() . Dans ce code e e les objets de la classe Stack sont des piles d’entiers. mais on peut en tout cas ˆtre sˆr que l’interpr´tation de la notation postfixe e e u e par une machine est bien plus facile ` r´aliser que celle de la notation infixe. e e . 3] . la figure 4 donne les ´tapes successives du calcul 6 3 2 . } else { stack. 2 Il faut citer le symbole * par exemple par ’*’.` 1. } System. Par exemple. . stack.push(i2+i1) . voir VI. i f (x.pop()) .

3] * -> [9] 9 CHAPITRE II. k < arg. il faut r´gler le e e probl`me des priorit´s des op´rateurs. . Il suffit tout simplement de construire la repr´sentation infixe au lieu de calculer. 6] . } } L’emploi syst´matiques des parenth`ses permet de produire des expressions infixes justes. 9.equals("+") || x. (9-6)] * -> [((6/((3-2)+1))*(9-6))] ((6/((3-2)+1))*(9-6)) Nous verrons plus tard que se passer des parenth`ses inutiles ne se fait simplement qu’` l’aide e a d’une nouvelle notion. la notation e e postfixe rend les parenth`ses inutiles (ou empˆche de les utiliser selon le point de vue). i f (x. PILES ET FILES La facilit´ d’interpr´tation de l’expression postfixe provient de ce que sa d´finition est tr`s e e e e op´rationnelle.3 . la notation postfixe oblige l’utilisateur de la calculatrice ` donner l’ordre d´sir´ des op´rations (comme par exemple 1 2 3 * + et 1 2 * 3 a e e e +) et donc simplifie d’autant le travail de la calculatrice. } System.’*’ 6 -> [6] 3 -> [6.equals("-") || x. Dans le cas de la notation infixe. Dans le mˆme ordre d’id´e.out.3 qui serait e e e incorrect pour 1 2 3 . for (int k = 0 . 6] .-> [3. . } else { stack. Sur l’expression d´j` donn´e.err. Par contraste. on obtient : ea e % java Infix 6 3 2 .2 .length . k++) { String x = arg[k] .et 1 2 . e e ´ Exercice 2 Ecrire un programme Infix qui lit une expression sous forme postfixe et affiche l’expression infixe (compl`tement parenth´s´e) qui correspond au calcul effectu´ par le proe ee e e ınes.println(stack. gramme Calc. Sans e e les parenth`ses 1 2 3 . elle dit exactement quoi faire.conduiraient au mˆme r´sultat 1 . } System.push(x) . tandis que pour calculer 1 * 2 + 3.-> [(6/((3-2)+1))..push("(" + i2 + x + i1 + ")") .pop(). On supposera donn´e une classe Stack des piles de chaˆ Solution. e class I n f i x { public static void main (String [] arg) { Stack stack = new Stack () .pop()) . Par exemple si on veut effectuer le calcul infixe 1 + 2 * e e e 3 il faut proc´der ` la multiplication d’abord (seconde op´ration). .equals("*") || x. on effectue d’abord la premi`re op´ration (mule e e tiplication) puis la seconde (addition). puis ` l’addition (premi`re e a e a e op´ration) . 6 -> [(6/((3-2)+1)). i2 = stack.equals("/")) { String i1 = stack. 9.pop() .-. stack.println(x + " -> " + stack) .1 + / 9 6 .50 6 -> [3. 3] .

e 2. sp = 0. private int [] t . IMPLEMENTATION DES PILES 51 2 Impl´mentation des piles e Nous donnons d’abord deux techniques d’impl´mentation possibles. une pile (d’entiers) contient un tableau d’entiers. avec un tableau et avec e une liste simplement chaˆ ee. 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 sp 0 Il nous reste ` coder les trois m´thodes des objets de la classe Stack pour respectivement. empiler et d´piler. dont nous fixons la taille e e arbitrairement ` une valeur constante. } e Notons que tous les champs des objets Stack sont priv´s (private. e boolean isEmpty() { return sp == 0 . // assez grand ? private int sp . mais cela n’a pas d’importance ici.´ 2. seules les 5 premi`res valeurs du tableau sont significatives. Stack () { // Construire une pile vide t = new int[SIZE] . ee e 0 6 1 2 2 7 3 0 4 2 5 8 6 0 7 2 8 3 9 4 sp 5 Dans ce sch´ma. Seul le d´but du tableau (un segment initial) est utilis´. Plus pr´cis´ment.4). // Ou bien : t[sp] = x . statique puisque qu’il n’y a aucune raison de cr´er e e de nombreux exemplaires de cette constante.1. e e class Stack { final static int SIZE = 10. a e e et un indice variable sp indique le pointeur de la pile. La taille des 0 1 2 3 4 5 6 7 8 9 10 piles est donn´e par une constante (final). } . voir B.2). sp = sp+1 .6. ` A la cr´ation. . Le pointeur de pile indique la prochaine position libre dans la pile.3. } 0 1 2 3 4 5 6 7 8 9 10 . La valeur initiale e contenue dans les cases du tableau est z´ro (voir B. une pile contient donc un tableau de SIZE = 10 entiers. t[sp++] = x . Ensuite. directement inspir´e par le dessin de e gauche de la figure 1. e car aucune case n’est valide (sp est nul). nous montrons comment utiliser la structure de pile toute ın´ faite de la biblioth`que. .1 Une pile dans un tableau C’est la technique conceptuellement la plus simple. tester a e si la pile est vide. } void push(int x) { i f (sp >= SIZE) throw new Error ("Push : pile pleine") . mais aussi le nombre d’´l´ments pr´sents dans la pile.

Nous allons voir ce que nous pouvons faire au sujet de ces deux erreurs. Notons surtout que toutes op´rations sont en e e e e e temps constant.2). puis de remplacer l’ancien tableau par le nouveau ee (et dans cet ordre. // Ou bien : sp = sp-1 .. Une premi`re technique simple est de passer le c e b´b´ ` l’utilisateur. e Mais en fait. on peut toujours compter sur l’utilisateur pour recommencer avec une pile plus grande. mais c’est ` peu pr`s ce qui se passe au sujet de la pile ıt a e des appels de m´thode.. Il suffit d’allouer un nouveau tableau plus grand. de recopier les ´l´ments de l’ancien tableau dans le nouveau. Le code cie Fig. } . } La figure 5 donne un exemple d’´volution du tableau t et du pointeur de pile sp. puis d´piler.. return t[sp] . e 0 6 Empiler 9 0 6 D´piler e 0 6 1 2 2 7 3 0 4 2 5 9 6 0 7 2 8 3 9 4 sp 5 1 2 2 7 3 0 4 2 5 9 6 0 7 2 8 3 9 4 sp 6 1 2 2 7 3 0 4 2 5 8 6 0 7 2 8 3 9 4 sp 5 dessus signale les erreurs en lan¸ant l’exception Error (voir B. return t[--sp] . e ea e class Stack { . . sp =7 0 . ce qui semble attendu e ee dans le cas d’op´rations aussi2simples 4 5 6 et 7 8 9 10 e que push pop.4. la premi`re d´coule d’une contrainte technique (la pile est insuffisame e e ment dimensionn´e). } Ainsi. t[sp++] = x . 0 1 2 3 4 5 6 7 8 9 10 Commen¸ons par l’erreur "pile pleine". et emploie les op´rateurs c e de post-incr´ment et pr´-d´cr´ment (voir B.length) throw new Error ("Push : pile pleine") .4). c’est plus simple).3. 0 6 8 Stack (int sz) { t 1 new int[sz] 5. En effet. tandis que la seconde d´coule d’une erreur de programmation de l’utilisae e teur de la pile. 0 1 3 Les deux erreurs possibles "Push : pile pleine" et "Pop : pile vide" sont de natures bien diff´rentes. ind´pendant du nombre d’´l´ments contenus dans la pile. Cela paraˆ abusif. PILES ET FILES int pop() { i f (isEmpty()) throw new Error ("Pop : pile vide") . } 9 = 2 3 4 10 void push(int x) { i f (sp >= t. les tableaux de Java sont suffisamment flexibles pour autoriser le redimensionnement automatique de la pile. 5 – Empiler 9. en cas de pile pleine. en lui permettant de dimensionner la pile lui-mˆme..52 CHAPITRE II.

En effet. On dira alors que le coˆt amorti de push est constant. } On note surtout que la m´thode pop d´clare (par throws. } Le redimensionnement automatique offre une flexibilit´ maximale. non plus l’exception Error.length + K] est rarement une bonne id´e. return t[--sp] .length] . u u Pour ce qui est de la m´moire. ce qui donne la possibilit´ au programmeur fautif de e la r´parer. Pour ce faire. supposons u e a que le tableau est redimensionn´ P fois (taille initiale 20 ). noter le (( s ))) qu’elle peut lane e e cer l’exception StackEmpty. le coˆt de N appels ` push reste de l’ordre e e u a de N . il n’y a pas de pop et la pile contient finalement N ´l´ments pour une taille 2P ee du tableau interne. } void push(int x) { i f (sp >= t. parce que StackEmpty est une Exception et non plus une Error.t = newT .´ 2. e Nous supposons que la classe Stack est telle que ci-dessus (avec une m´thode pop qui d´clare e e lancer StackEmpty) et que nous ne pouvons pas modifier son code. e e class StackEmpty extends Exception { } C’est en fait une d´claration de classe ordinaire. // Remplacer this. IMPLEMENTATION DES PILES 53 private void resize() { int [] newT = new int [2 * this. L’ordre de grandeur du coˆt cumul´ e u e k=P de tous les push est donc de l’ordre de N (coˆt constant de N push) plus k=1 2k = 2P +1 − 1 u (coˆt des P redimensionnements).). elle d´pile et empile directement par stack. e int pop () throws StackEmpty { i f (isEmpty()) throw new StackEmpty () . car une exception est un objet d’une classe un e peu sp´ciale (voir B. o` 2P est la plus petite puissance de 2 sup´rieure ` N . // Allouer le nouveau tableau for (int k = 0 .. ´crire e e int [] newT = new int [t. e e dont la moiti´ ont pu ˆtre r´cup´r´es par le garbage collector. Les exceptions sont compl`tement d´crites dans l’annexe Java en B.2). Ici. En contrepartie. Mais e e e on peut signaler l’erreur plus proprement. Pour organiser un peu .4. k++) // Recopier newT[k] = this. car rien n’empˆchera jamais un programmeur maladroit ou fatigu´ de d´piler une pile vide.4. En outre.length) resize() . La pr´sence de la d´claration va obliger l’utilisateur des e e piles ` s’occuper de l’erreur possible. il convient de lancer. on alloue au total de l’ordre de 2P +1 cellules de m´moire.pop() et stack. il a un coˆt : e u un appel ` push ne s’ex´cute plus en temps garanti constant. Au pire.. il n’y a aucun moyen de supprimer l’erreur.2).t. puisqu’il peut arriver qu’un appel a e a ` push entraˆ un redimensionnement dont le coˆt est manifestement proportionnel ` la taille ıne u a de la pile. En revanche. Soit un coˆt final de l’ordre de N . Pour cette raison. La nouvelle exception StackEmpty se d´finit ainsi. puisque 2P < 2 · N . k < sp . Mais vu globalement sur une ex´cution compl`te. Cette d´claration est ici obligatoire. La nouvelle exception se lance ainsi. dont la raison n’est pas forc´ment 2. t[sp++] = x . consid´rons N op´rations u e e push. Ici.push(. Notons finalement que le coˆt e e e ee u amorti constant est assur´ quand les tailles des tableaux successifs suivent une progression e g´om´trique. e Abordons maintenant l’erreur "pile vide". La calculatrice emploie une pile stack. mais une exception e sp´cifique. a Pour illustrer ce point revenons sur l’exemple de la calculatrice Calc (voir 1.t[k] . le coˆt amorti devient lin´aire e e e u e pour des tableaux les tailles suivent une progression arithm´tique. nous nous e e e contentons d’une pr´sentation minimale.

exit(2) .pop() . En fait.1. Par e cons´quent. PILES ET FILES la suite.pop puis main peuvent lancer l’exception StackEmpty. i2 = pop(stack) .println("Adieu : pop sur pile vide") . e class Calc { static int pop(Stack stack) throws StackEmpty { return stack. i f (x. par e e l’instruction try.err. // Arr^ter le programme imm´diatement (voir B.. } public static void main (String [] arg) throws StackEmpty { . Cela revient ` remplacer la valeur e e a (( exceptionnelle )) StackEmpty par la valeur plus normale 0 (voir B. et e a transformons les appels de m´thode dynamiques en appels statiques. } catch (StackEmpty e) { System.pop() ´choue parce que l’exception StackEmpty e est lanc´e. e class Calc { static int pop(Stack stack) { return stack.pop() .. Traiter l’exception peut se faire en attrapant l’exception.pop() . mais alors il faut signaler que les m´thodes e e Calc. une pile vide se comporte comme si elle contenait une infinit´ de z´ros. int x) { stack.4.54 CHAPITRE II. // Le compilateur exige ce return jamais ex´cut´ e e } } .pop(). } catch (StackEmpty e) { return 0 .equals("+")) { int i1 = pop(stack). i2+i1) . Il exige le traitement de StackEmpty qui e peut ˆtre lanc´e par stack. nous ajoutons deux nouvelles m´thodes statiques push et pop ` la classe Calc. elle remontera de m´thode en m´thode et fera finalement ´chouer le e e e programme. sont quand mˆme p´nibles. } } Le compilateur refuse le programme ainsi modifi´.6. e e e On peut aussi d´cider de ne pas traiter l’exception. e e System. static int pop(Stack stack) { try { return stack. o static int pop(Stack stack) { try { return stack. push(stack.pop() . ici obligatoires. } static void push(Stack stack. .. c’est alors l’instruction return 0 qui s’ex´cute.. comme les d´clarations throws.. } } Si l’exception survient.. mˆme si c’est indirectement. } public static void main (String [] arg) { .push(x) .1 pour les d´tails).5) return 0 . e e e on a plutˆt tendance ` faire ´chouer le programme explicitement d`s que l’exception atteint le o a e e code que l’on contrˆle. } } L’effet est ici que si l’instruction return stack.

p = p. } void push(int x) { p = new List (x. Java a r´solu ce e e e e e probl`me en proposant des classes g´n´riques.´ 2. Mais une classe des piles de quoi ? e e e En effet.val . si on veut que d´piler une pile vide provoque un arrˆt du programme. IMPLEMENTATION DES PILES 55 Bien sˆr.3 Les piles de la biblioth`que e Il existe d´j` une classe des piles dans la biblioth`que. Et on ´crit : e class Stack { private List p . Apr`s bien des h´sitations. } int pop() throws StackEmpty { i f (p == null) throw new StackEmpty () . p) .util. La classe Stack est en fait une e e e e e classe Stack<E>. il nous suffisait de remplacer int par String partout dans le code. par exemple de String. la classe Stack du package java.next . ou param´tr´es.2 Une pile dans une liste C’est particuli`rement simple. Il est clair qu’une telle (( solution )) ne convient pas ` une classe a de biblioth`que qui est compil´e par le fabricant. il aurait ´t´ u e e ee plus court de lancer new Error ("Pop : pile vide") dans la m´thode pop des objets Stack. nous n’avons cod´ que des piles de int en e e e nous disant que pour coder une pile. } } Voici un exemple d’´volution de la liste p. o` E est n’importe quelle classe. e p 2 Empiler 9 p 9 D´piler e p 9 2 0 7 2 6 2 0 7 2 6 0 7 2 6 2. On suppose donc donn´e une classe des listes e e e (d’entiers). } boolean isEmpty() { return p == null . les ´l´ments sont empil´s et d´pil´s du mˆme cˆte e ee e e e e o de la pile. dans les deux sections pr´c´dentes. ea e impl´ment´e selon la technique des tableaux redimensionn´s. 2. e Mais nous nous sommes interdit de modifier la classe Stack. et les objets de la classe Stack<E> sont des u . En effet. qui peut ˆtre le d´but d’une liste. Stack () { p = null . int r = p. return p .

stack. o` par exemple pop() renvoie un objet E. Tout se passe plutˆt comme si le compilateur traduisait o le programme avec les int vers le programme avec les Integer.. Plus g´n´ralement. le compilateur Java sait ins´rer les appels aux m´thodes de conversion automatie e quement.pop() . et c’est bien ce dernier qui s’ex´cute. Un objet Integer n’est gu`re plus qu’un objet dont une e variable d’instance (priv´e) contient un int (voir B. il est impossible de fabriquer des piles de scalaires (voir B. valueOf (logiquement statique) e et intValue (logiquement dynamique).util. } } Mais en fait. e import java. Stack< List > etc. Informellement.3. PILES ET FILES piles d’objets de la classe E. avec les cons´quences pr´visibles sur la performance en g´n´ral et la consommation e e e e e m´moire en particulier. Formellement. int i1 = stack.) Cela fonctionne parce que la classe Stack<E> poss`de bien des e m´thodes pop et push. stack. Il existe deux m´thodes pour convere e tir un scalaire int en un objet Integer et r´ciproquement.push(Integer. i2 = stack.2) avec e une pile de la biblioth`que semble assez lourd au premier abord.5. int i1 = stack..util. .1). e .6.. i2 = stack. class I n f i x { public static void main (String [] arg) { Stack<String> stack = new Stack<String> () .pop(). i2 = stack. String i1 = stack.* .util.. . import java.valueOf(i2+i1)) .pop(). par exemple Integer pour int . voir B...push(i2+i1) . class Calc { public static void main (String [] arg) { Stack<Integer> stack = new Stack<Integer> () . c’est-`-dire que l’on peut ´crire plus directement.* .pop().Stack<String>. De sorte qu’´crire la calculatrice Calc (voir 1..intValue() ..56 CHAPITRE II.pop(). Par exemple on code facie lement la pile de chaˆ ınes de l’exercice 2 comme un objet de la classe java. class Calc { public static void main (String [] arg) { Stack<Integer> stack = new Stack<Integer> () .push("(" + i2 + x + i1 + ")") .3. .. .util...pop() . mais attention.intValue(). Stack n’est donc pas exactement une classe.* ..1 e e pour la d´finition des scalaires). ici un String. . Mais la biblioth`que fournit une classe associ´e par type scae e e laire. on peut consid´rer que nous o e disposons d’une infinit´ de classes Stack<String>. . e u Comme le param`tre E dans Stack<E> est une classe. } } Tout semble donc se passer presque comme si il y avait une pile de int . il n’est pas possible de fabriquer des e piles de int .1. mais plutˆt une fonction des classes dans les classes. } } (Pour import. stack. il s’agit bien en fait d’une pile de Integer. a e import java.

Autrement dit.1 Une file dans un tableau L’id´e.´ 3. notamment ıtre ee 2 3 4 5 6 7 0 1 2 3 4 5 6 7 8 9 10 pour g´rer le1redimensionnement. Il en r´sulte que l’on peut avoir out < in. car connaˆ simplement le nombre d’´l´ments de la file est pratique. out (d´but) et in (fin) valent tous deux 4. 7. listes et biblioth`que. Plus pr´cis´ment. e 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 1 6 2 7 0 9 8 2 2 0 4 6 2 7 0 9 8 2 2 0 4 0 4 4 0 in 1 out 4 2 3 4 5 6 7 8 9 10out 4 in Ici. 9 (dans l’ordre. les cases valides sont gris´es. qui contient le nombre d’´l´ments contenus dans la file. L’indice out marque le d´but de la file et in sa u ee e e fin. que l’on parcourt en incr´mentant un indice e modulo n. Nous choisissons la solution de la ıt´ a variable nb. 2 est le premier entr´ et 9 le dernier entr´). On r´sout facilement la difficult´ par une variable nb e e suppl´mentaire. 0 1 2 3 4 5 6 7 8 9 6 0 2 1 7 2 0 33 9 4 8 5 2 6 2 77 0 8 4 9 10 in (fin) out (d´but) e Le tableau d’une file est un tableau circulaire. il fait tout e e simplement le tour du tableau et repart ` z´ro. On teste en effet cette derni`re ee e e ee e condition sans ambigu¨ e par in + 1 congru ` out modulo n. 0. nous pr´sentons trois techniques. les deux indices out et in n’y suffisent pas. et une seconde une file pleine. Par a e e exemple. Une autre solution aurait e ee ´t´ de d´cr´ter qu’une file qui contient n − 1 ´l´ments est pleine. o` n est la taille du tableau.8 9 10 e 0 . Pour connaˆ le contenu de la file il faut donc e ıtre parcourir le tableau de l’indice 4 ` l’indice 3. 4. Une premi`re interpr´tation du parcours donne a e e une file vide. les deux indices sont croissants. e e 3. 7. a Une derni`re difficult´ est de distinguer entre file vide et file pleine. Par exemple pour parcourir le contenu de la file. 0 1 2 3 4 5 6 7 8 9 6 2 7 0 9 8 2 2 0 4 1 out (d´but) e 5 in (fin) Dans le sch´ma ci-dessus. 2. tableaux. 0. IMPLEMENTATION DES FILES 57 3 Impl´mentation des files e Comme pour les piles. est de g´rer deux ine e a e dices : in qui marque la position o` ajouter le prochain ´l´ment et out qui marque la position u ee d’o` proviendra le prochain ´l´ment enlev´. Soit un parcours de 1 ` 4 dans le premier exemple a a et de 7 ` 2 dans le second. conceptuellement simple mais un peu d´licate ` programmer. de sorte que la file ci-dessus contient e e les entiers 2. Comme le montre le e e sch´ma suivant. Au cours de la e e vie de la file. on u parcourt les indice de out ` (in − 1) mod n. on incr´mente in apr`s ajout e e e e et on incr´mente out apr`s suppression. Lorsqu’un indice atteint la fin du tableau. voici une autre file contenant cette fois 2. 6. le contenu de la file va la case d’indice out ` la case qui pr´c`de la case a e e d’indice in.

PILES ET FILES Fig.append(t[i]) . i = incr(i)) b.length] . // Effectivement ajouter } private void resize() { int [] newT = new int[2*t. return b. // indice du parcours de t for (int k = 0 . in = out = nb = 0 . i = incr(i) . } t = newT . donne un exemple de parcours de la file */ e public String toString() { StringBuilder b = new StringBuilder () . Fifo () { t = new int[SIZE] . k < nb . private int in. i f (nb > 0) { int i = out . out = incr(out) . t[in] = x . } b. nb . nb++ .toString() . int r = t[out] . b. for ( .length .append("]") . in = incr(in) . i = incr(i) . " + t[i]) . // Allouer int i = out .append(". nb-. k++) { // Copier newT[k] = t[i] . } /* Increment modulo la taille du tableau t. out = 0 .. in = nb . // Remplacer } /* M´thode toString. 6 – Impl´mentation d’une file dans un tableau e class FifoEmpty extends Exception { } class Fifo { final static int SIZE=10 . out. b. i != in . } } . } void add(int x) { i f (nb+1 >= t.length) resize() .58 CHAPITRE II. private int [] t . // Effectivement enlever return r . } int remove() throws FifoEmpty { i f (isEmpty()) throw new FifoEmpty () .append("[") . utilis´ partout */ e private int incr(int i) { return (i+1) % t. } boolean isEmpty() { return nb == 0 .

Nous avons d´j` largement ee e ea exploit´ cette id´e. 7. 2. voici la suppression du premier ee e ´l´ment d’une file (la m´thode remove renvoie ici 2). voici l’ajout (m´thode add) de ee e e e l’entier 11 dans une file. et une r´f´rence in sur ee e ee la derni`re cellule. 0. in est incr´ment´ (modulo n). qui doit particulariser le cas d’une file vide et effectuer la comparaison ` in ` partir du second ´l´ment parcouru (` cause du cas de la file a a ee a pleine). Voici l’exemple de l’ajout de 13 dans une file pleine de taille 4. ee e 0 1 2 3 4 5 6 7 8 9 6 2 7 11 9 8 2 2 0 4 4 in 7 out 0 1 2 3 4 5 6 7 8 9 6 2 7 11 9 8 2 2 0 4 4 in 8 out Le redimensionnement (m´thode resize) a pour effet de tasser les ´l´ments au d´but du nouveau e ee e tableau. 0 1 2 3 6 2 7 11 2 in 2 out 0 1 2 3 4 5 6 7 7 11 6 2 13 0 0 0 0 out 5 in Avant ajout la file contient 7. entre les indices out (inclus) et in (exclu). 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 6 2 7 0 9 8 2 2 0 4 6 2 7 11 9 8 2 2 0 4 3 in 7 out 4 in 7 out Pour supprimer un ´l´ment. Pour garantir des op´rations en temps e e a e constant.2 Une file dans une liste L’impl´mentation est conceptuellement simple. 3. avec redimensionnee e ment automatique. le contrˆle est assur´ par une simple ee o e boucle sur le compte des ´l´ments transf´r´s. 13. Un point cl´ de ce code est la gestion des indices in et out. on e ee enl`ve au d´but de la liste et on ajoute ` la fin de la liste. e Le code du redimensionnement (m´thode resize) est un peu simplifi´ par la pr´sence de e e e la variable nb. Par exemple. e out 2 7 0 in 9 La file ci-dessus contient donc les entiers 2.´ 3. 9 dans cette ordre. on incr´mente out. La m´thode toString donne un exemple de ce style de parcours du contenu de e e la file. Une file vide est logiquement identifi´e par une variable out valant null . 6. IMPLEMENTATION DES FILES 59 La figure 6 donne la classe Fifo des files impl´ment´es par un tableau. Nous e e e nous donnons donc une r´f´rence out sur la premi`re cellule de la liste. on utilise une r´f´rence sur la derni`re cellule de liste. Les ´l´ments de la file sont dans une liste. Par exemple. apr`s ajout elle contient 7. 2. 11. Un contrˆle sur l’indice i dans le tableau t serait ee ee o plus d´licat.2). e e . 6. 11. Par souci de coh´rence in vaudra alors aussi null . Pour copier les ´l´ments de t dans newT. par exemple pour copier une liste it´rativement (voir l’exercice I. Pour ajouter un e ´l´ment dans la file.

next = in.next = in . } boolean isEmpty() { return out == null . in . null) . // La file est vide return r . out 2 7 0 last 9 in 3 9 in 3 in 9 .val . Fifo () { out = in = null . out = out. PILES ET FILES int remove() throws FifoEmpty { i f (isEmpty()) throw new FifoEmpty () . out 2 7 0 last Et enfin. l’instruction last.2). e i f (last == null) { // La file ´tait vide out = in .next . in = new List (x. int r = out. La premi`re instruction List last = in garde une copie de la r´f´rence in. } } } On constate que le code est bien plus simple que dans le cas des tableaux (figure 6). voici par exemple la suite des ´tats m´moire quand on ajoute 3 ` la a e e a file d´j` dessin´e. } else { last. i f (out == null) in = null . } void add(int x) { List last = in . car les deux e e e m´thodes r´alisent la mˆme op´ration d’enlever le premier ´l´ment d’une liste. ea e e ee out 2 7 0 last L’instruction suivante in = new List (x. La m´thode remove est essentiellement la mˆme que la m´thode pop des piles (voir 2. ajoute effectivement la nouvelle cellule point´e par in e a ` la fin de la liste-file. } CHAPITRE II.60 class Fifo { private List out. La m´thode add e e e e ee e est ` peine plus technique. null ) alloue la nouvelle cellule de liste.

prev. DeList next.prev == e e p . mˆme si nous ne respectons pas trop l’esprit dans lequel ces classes sont organis´es. D´composons une op´ration. ın´ class DeList { int val . e e Exercice 3 Les objets LinkedList impl´mentent en fait les (( queues ` deux bouts )) (double ene a ded queue).util de la biblioth`que offre plusieurs classes qui peuvent servir comme e une file.prev = prev .next. DeList next. e Solution. ce qui la rend relativement simple d’emploi. on a l’´galit´ p. fst 3 2 0 7 11 lst . IMPLEMENTATION DES FILES 61 3. si p pointe vers une cellule qui n’est pas la derni`re.val = val . comme la classe Stack de la section 2. this. et si p pointe vers la premi`re cellule.next = next . DeList prev) { this. e e e a fst 3 2 0 7 11 lst Reste ensuite ` ajuster le champ prev de la nouvelle seconde cellule de la liste. Comme le nom l’indique ` peu pr`s. cellule accessible a par fst. la classe de biblioth`que repose sur les listes doublement chaˆ ees.next == p . Dans un premier temps on peut allouer la nouvelle cellule.´ 3. DeList (int val.next ((fst. Le champ prev peut aussi ˆtre initialis´ imm´diatement ` null . mais elle poss`de les m´thodes add et remove des files. si p est une r´f´rence vers une cellule de liste doublement chaˆ ee qui n’est pas la ee ın´ premi`re. Voici une e ın´ classe DeList des cellules de liste doublement chaˆ ee. Afin d’assurer des op´rations e a e en temps constant. (Les e e files de la biblioth`que Queue ne sont pas franchement compatibles avec notre mod`le simple e e des files). cette a e classe est impl´ment´e par une liste chaˆ ee. La classe LinkedList est e e e g´n´rique. on a p.3. Nous nous focalisons sur la classe LinkedList.prev = fst). } } Le chaˆ ınage double permet de parcourir la liste de la premi`re cellule ` la derni`re en suivant e a e les champs next. De mˆme.next).next == null. prev . et de la derni`re ` la premi`re en suivant le champ prev. avec un champ ea e next correct (voir push en 2. en ajoutant 3 au d´but de la e e e queue d´j` dessin´e. et si p pointe vers la derni`re cellule. par exemple addFirst.3 Les files de la biblioth`que e Le package java.2).prev e e e e == null. e a e fst 2 0 7 11 lst Autrement dit. Elle offre en fait bien plus que la simple fonctione e ın´ nalit´ de file. on a p. qui offrent deux couples de m´thodes addFirst/removeFirst et addLast/removeLast e pour ajouter/enlever respectivement au d´but et ` la fin de queue. this. on a p.

lst = lst.prev = fst . lst) .prev = null . int r = fst.next. null) . i f (fst == null) { lst = null . } } int removeFirst() { i f (fst == null) throw new Error ("removeFirst: empty queue") . 7 – Classe des queues ` deux bouts a class DeQueue { private DeList fst. } return r . } else { fst. fst = fst. } } .next = null . } } void addLast(int x) { lst = new DeList(x. } else { lst. fst. lst .next . } int removeLast() { i f (lst == null) throw new Error ("removeLast: empty queue") .prev. } else { lst. } boolean isEmpty() { return fst == null . } return r . null.62 CHAPITRE II. i f (fst == null) { fst = lst . } void addFirst(int x) { fst = new DeList(x.val . i f (lst == null) { fst = null . PILES ET FILES Fig. } else { fst. // First and last cell DeQueue () { fst = lst = null .val .prev .next = lst . i f (lst == null) { lst = fst . int r = lst.

la question ne se pose pas. . le tableau (ainsi e que le pointeur de pile sp) et la liste sont des champs priv´s. pas si difficile est donn´ par la figure 7. 3 2 0 7 11 fst lst Reste ensuite ` ajuster le champ next de la nouvelle derni`re cellule (lst. CHOIX DE L’IMPLEMENTATION 63 Pour supprimer par exemple le dernier ´l´ment de la queue.next = null ). Il faut toutefois remarquer que. Mais nous tenons d’abord a faire remarquer que. ` moins de changer le nom des classes ce qui contredirait l’id´e e a e d’un type abstrait. e a 4 Type abstrait. e a R´pondons maintenant s´rieusement ` la question du choix de l’impl´mentation. } int pop() throws StackEmpty { . Il est naturel de se demander e e c e quelle impl´mentation choisir. . Un programme qui utilise nos piles e doit donc le faire exclusivement par l’interm´diaire des m´thodes push et pop. du strict point e ` de vue de la fonctionnalit´. e parce que c’est la solution qui demande d’´crire le moins de code. les trois impl´mentations des piles offrent exactement le mˆme service. TYPE ABSTRAIT. il suffit de changer le contenu de ee lst (lst = lst. Champs priv´s e e e et m´thodes accessibles correspondent directement ` la notion de type abstrait d´fini par les e a e services offerts. Dans nos deux classes Stack. .prev). En effet. Une fois compris le m´canisme d’ajout et de e e retrait des cellules. . Dans disons e e a e 99 % des cas pour les files et 90 % des cas pour les piles. } void push(int x) { . le point d´licat est de bien g´rer le cas de la queue vide qui correspond ` e e a fst et lst tous deux ´gaux ` null . choix de l’impl´mentation e Nous avons pr´sent´ trois fa¸ons d’impl´menter piles et files. La pile qui est d´finie exclusivement par les services qu’elle offre e est un exemple de type de donn´es abstrait. . . il faut choisir la classe de biblioth`que.´ 4. a e 3 2 0 7 11 fst lst Le code. La diff´rence de pourcentage e e s’explique en comparant le temps de lecture de la documentation au temps d’´criture d’une e classe des files ou des piles. ` l’aide des interfaces. e e de sorte que le choix d’une impl´mentation particuli`re n’a aucun impact sur le r´sultat d’un e e e programme qui utilise une pile. et parce que la classe des piles est quand mˆme simple ` ´crire. Tout ce qui compte est d’avoir une pile e (ou une file). l’existence des deux (trois en fait) m´thodes reste pour le moment une convention : nous avons convene e tionnellement appel´ Stack la classe des piles et conventionnellement admis qu’elle poss`de ces e trois m´thodes : e boolean isEmpty() { . e Java offre des traits qui permettent de fabriquer des types abstraits qui prot`gent leur e impl´mentation des interventions intempestives. e ae . } Il en r´sulte par exemple que nous ne pouvons pas encore m´langer les piles en tableau et les piles e e en liste dans le mˆme programme. du point de vue du langage de programmation. Nous verrons au chapitre suivant comment proc´der.

dans le pass´.64 CHAPITRE II. On e e peut aussi penser que les tableaux seront un plus efficaces que les listes. En effet. autrement le choix est moins net. ou en tout cas utiliseront moins de m´moire au final. u e Il reste alors ` choisir entre tableaux et listes. Mais il faut alors y regarder ` deux fois. On peut aussi envisager de r´duire la taille du tableau e interne de la pile tableau. e c e e e trop coˆteux. a e e ıne e . PILES ET FILES La biblioth`que peut ne pas convenir pour des raisons d’efficacit´ : le code est trop lent ou e e trop gourmand en m´moire pour notre programme particulier et nous pouvons faire mieux que e lui. et que le programme e alterne push et pop) et environ 4 · P + 3 · log2 P cases de m´moire (dans le cas o` aucun pop ne e u s´pare les push et o` le tableau est redimensionn´ par le dernier push). ou tout simplement impossible avec notre connaissance limit´e de Java. La classe de biblioth`que peut aussi ne pas e convenir parce qu’elle n’offre pas la fonctionnalit´ in´dite dont nous avons absolument besoin (par e e exemple une inversion compl`te de la pile). Un a e nouvel ´l´ment intervient donc dans le choix de l’impl´mentation : la profondeur maximale de ee e pile au cours de la vie du programme. et g´n´ralement plutˆt moins. Il n’y a pas de r´ponse toute faite ` cette a e a derni`re question. Pour un programme effectuant au total P push. le choix d’une pile-tableau s’impose probablement. la pile-liste aura allou´ au total 4 · P cases de m´moire. le code de biblioth`que entraˆ la fabrication d’objets Integer en pagaille. Si cette profondeur reste raisonnable le choix de la piletableau s’impose. e e Supposons qu’un objet occupe deux cases de m´moire en plus de l’espace n´cessaire pour ses e e donn´es (c’est une hypoth`se assez probable pour Java). et e ıne une pile de scalaires se montre au final plus rapide. par exemple en r´duisant le tableau de la moiti´ de sa taille quand e e il n’est plus qu’au quart plein. la quantit´ de m´moire mobilis´e ` un u e e e e e a instant donn´. Toutefois. puisque u simplicit´ et efficacit´ concordent. et d’autre part. La pile-tableau n’alloue e u e donc jamais significativement plus de m´moire que la pile-liste. Une pile-file de N ´l´ments mobilise 4 · N cases de m´moire. (la pile croˆ e ıt-elle beaucoup. Une pile-tableau e ee e mobilise entre 3+N et un nombre arbitrairement grand de cases de m´moire (le nombre arbitraire e correspond ` la taille du tableau lorsque. notre classe des piles a commence ` devenir compliqu´e est tout ce code ajout´ entraˆ un prix en temps d’ex´cution. on peut tout de mˆme pr´voir qu’utiliser e e e e e e les listes demande d’´crire un peu moins de code que d’utiliser les tableaux (redimensionn´s). Or. Dans le cas g´n´ral. tandis que e e la pile-tableau aura allou´ entre une (si la taille initiale du tableau est 1. par exemple) et aussi de l’efficacit´ de la gestion de la m´moire par le syst`me d’ex´cution de Java. mais la flexibilit´ de l’allocation petit-`-petit e a des cellules de la pile-liste est un avantage. allouer un objet est une op´ration ch`re. fabriquer une pile-tableau de N ´l´ments demande d’allouer e ee log2 N objets (tableaux) contre N objets (cellules de listes) pour une pile-liste. ou plus fr´quemment parce que programmer cette e e fonctionnalit´ de la fa¸on autoris´e par la structuration de la biblioth`que serait trop compliqu´. la difficult´ de l’´criture d’un programme d´pend aussi e e e e du goˆt et de l’exp´rience de chacun . Une cellule de liste occupe donc 4 cases e e et un tableau de taille n occupe 3+n cases (dont une case pour la longueur). la pile a atteint sa taille maximale). En effet. l’efficacit´ respective de l’une ou de l’autre u e e technique d´pend de nombreux facteurs. Par exemple. e e e o Un autre coˆt int´ressant est l’empreinte m´moire. d’une part. dans le cas e e e e o` le redimensionnement est inutile.

e e e Trouver l’information associ´ ` une cl´ donn´e. c’est-`-dire aussi pouvoir ajouter ou a supprimer un ´l´ment d’information. La notion se e a e rencontre dans la vie de tous les jours. l’id´e du num´ro de s´curit´ sociale. dans ce cas e e e a e la table d’association se r´duit ` l’ensemble (car il n’y a pas de doublons). e e ee En cas d’homonymie absolue (tout de mˆme rare). e e La seconde op´ration m´rite d’ˆtre d´taill´e. Dans ce cas appelons enregistrement un ´l´ment d’information. u e e e e Nous voulons un ensemble dynamique d’informations. En g´n´ral. On en vient naturellement ` d´finir un type abstrait de ee a e donn´es. e a 1 Statistique des mots Nous illustrons l’int´rˆt de la table d’association par un exemple ludique : un programme Freq ee qui compte le nombre d’occurrences des mots d’un texte. e a Il en r´sulte qu’il n’y a jamais dans la table deux informations distinctes associ´es ` la mˆme e e a e cl´. une nouvelle association est ajout´e ` la table. un exemple typique est l’annuaire t´l´phonique : connaˆ ee ıtre le nom et les pr´noms d’un individu suffit normalement pour retrouver son num´ro de t´l´phone. il peut se faire que la cl´ fasse partie de l’information. Sinon. alors la nouvelle information ea e a e remplace l’ancienne. Lors de l’ajout d’une paire cl´-information. Nous nous pla¸ons dans le cadre o` ee c c u une information compl`te se retrouve normalement ` l’aide d’une cl´ qui l’identifie. a Enfin. e Retirer une cl´ de la table (avec l’information associ´e). on arrive toujours ` se d´brouiller. num´ro de s´curit´ sociale. e Il n’est pas trop difficile d’envisager la possibilit´ inverse. nous e e e e e e pr´cisons : e S’il existe d´j` une information associ´e ` la cl´ dans la table. Il faut alors r´viser un peu les deux e ea e autres op´rations. Il n’y a l` aucune e a difficult´ du moins en th´orie. ea e e Ajouter une nouvelle association entre une cl´ et une information. que la cl´ s’y trouve d´j` ou pas. ce e a e e e e e e qui revient ` un comportement de pile. d’o`. Il se peut aussi que l’information se r´duise ` la cl´.Chapitre III Associations — Tables de hachage Ce chapitre aborde un probl`me tr`s fr´quemment rencontr´ en informatique : la recherche e e e e d’information dans un ensemble g´r´ de fa¸on dynamique. on choisit l’information la plus r´cemment entr´e. dans le but de produire d’int´ressantes e 65 . par exemple. pr´nom. Dans les e a e soci´t´s modernes qui refusent le flou (et dans les ordinateurs) la cl´ doit identifier un individu ee e unique. en ajoutant une nouvelle association e a ` la table dans tous les cas. afin d’identifier l’information concern´e parmi les ´ventuellement multiples qui e e e sont associ´es ` une mˆme cl´. e e e e e sexe. e ee Un enregistrement est compos´ de plusieurs champs (nom. appel´ table d’association qui offre les op´rations suivantes. date de naissance etc. comme c’est g´n´ralement le e e e cas dans une base de donn´es.) dont un certain nombre peuvent servir de cl´.

ASSOCIATIONS — TABLES DE HACHAGE statistiques. a Une table d’association permet de r´soudre facilement le deuxi`me point : il suffit d’associer e e ` mots et entiers. int val) .toLowerCase() . nous pouvons e donc parfaitement ´crire une m´thode count. sans que la classe exacte de t soit connue1 . e a e e Cette absence n’empˆche nullement d’employer une interface comme un type. } La d´finition d’interface ressemble ` une d´finition de classe. (3) Produire un bel affichage. Nous pr´f´rons le faire directement en Java en e e ee d´finissant une interface Assoc.read()) { i f (word. mais sans le code des m´thodes.put(word.get(word)+1) .66 CHAPITRE III. Les objets de cette classe poss`dent une m´thode read qui e e renvoie le mot suivant du texte (un String) ou null ` la fin du texte. Le troisi`me par un tri et le premier par une e e e classe WordReader des flux de mots. par exemple pr´senter les mots dans l’ordre d´croissant du e e nombre de leurs occurrences. la table est e e simplement affich´e sur la sortie standard. qui compte les occurrences des mots du texte e e dans un Assoc t pass´ en argument.txt nous: 10 vous: 8 fran¸ais: 7 c leur: 7 dans: 6 libert´: 6 e . ou z´ro. word = in. // Minusculer le mot t. */ e a e int get(String key) . nous aurons % java Freq marseillaise.read() . a (2) Compter les occurrences des mots. } } } (Voir B. 1. A ea e chaque mot lu m on retrouve l’entier i associ´ ` m. Soit un texte. puis on change l’association en m associ´ ea e a ` i + 1.length() >= 4) { // Retenir les mots suffisamment longs word = word. e interface Assoc { /* Cr´er/remplacer l’association key → val */ e void put(String key. e 1 e e Vous noterez la diff´rence avec les classes Fifo et Stack du chapitre pr´c´dent e . e static void count(WordReader in. t. Nous pourrions sp´cifier en fran¸ais une version raffin´e de table d’association (pas de e c e suppression. Un mot qui n’est pas dans la table est conventionnellement associ´ ` z´ro.1 R´solution par une table d’association e Le probl`me ` r´soudre se d´compose naturellement en trois : e a e e (1) Lire un fichier texte mot ` mot.6.. information valant z´ro par d´faut). Assoc t) { for (String word = in. /* Trouver l’entier associ´ ` key. Une fois remplie. word != null ..1.3 pour les deux m´thodes des String employ´es). Supposons les premier et troisi`me points r´solus. par exemple notre hymne national.

AList p) { for ( . count(in.next = next . .1). Si cette cellule n’existe pas.key = key . AList next . L() { p = null . e class AList { String key .val . int val .1. puis une classe des listes de paires. STATISTIQUE DES MOTS Assoc t = . p) . mais e nous pr´f´rons la solution plus ´conome en m´moire qui consiste ` ranger les deux composantes ee e e a de la paire directement dans la cellule de liste. . } } . Dans l’exemple de la classe Freq. 67 Les points de la cr´ation du WordReader et du tri de la table dans sa m´thode toString sont e e e e e e suppos´s r´solus. . i f (r == null) { // Absent de la table return 0 . // Pr´sent dans la table e } else { return r.println(t. e 1. this. } public int get(String key) { AList r = AList. null ´tant une liste valide) destin´e ` retrouver une paire cl´-information ` partir de sa cl´. List next) { this. int val.3.out. . p != null .getCell(key.toString()) . return null . . } La m´thode getCell renvoie la premi`re cellule de la liste p dont le champ vaut la cl´ pass´e en e e e e argument. null est renvoy´. System.val = val .1. t) .key)) return p . this. . p = p. e e Nous dotons la classe AList d’une unique m´thode (statique. Cette e e e m´thode getCell suffit pour ´crire la classe L des tables d’associations bas´e sur une liste e e d’association interne. AList (String key.next) i f (key. c’est plus prudent (voir B. nous d´finissons donc une classe simple des cellules de listes. e a e a e static AList getCell(String key. WordReader in = . les cl´s sont des chaˆ e e ınes et les informations des entiers. class L implements Assoc { private AList p . nous estimerons donc le programme Freq ´crit d`s que nous aurons impl´ment´ e e la table d’association n´cessaire.2 Impl´mentation simple de la table d’association e La technique d’impl´mentation la plus simple d’une table d’associations est d’envisager une e liste des paires cl´-information. Notons que les chaˆ e ınes sont compar´es e par la m´thode equals et non pas par l’op´rateur ==. } } Nous aurions pu d´finir d’abord une classe des paires.equals(p.

Le rang ` l’arriv´e d’un concurrent peut servir de cl´ dans la base de a e e donn´es. . mais il n’est pas tr`s efficace. En effet si la liste d’association e est de taille N .68 CHAPITRE III. seulement l’interface Assoc. Appelons e univers des cl´s l’ensemble U de toutes les cl´s possibles. e } else { // Pr´sent dans la table. Ce cas s’applique par exemple ` la base de donn´e des concurrents d’une ´preuve sportive a e e qui exclut les ex-æquos. ASSOCIATIONS — TABLES DE HACHAGE public void put(String key. .getCell(key. Il suffit alors d’utiliser un tableau de taille n pour repr´senter la ea e e table d’association. // Compter les mots de in. n − 1}. Le fait e pertinent est de remarquer que le probl`me de la recherche d’information se simplifie beaucoup e quand les cl´s sont des entiers pris dans un petit intervalle. e . int val) { AList r = AList. ıt Notre programme Freq fonctionne. e e e e Mais il faut surtout noter une nouveaut´ : la classe L d´clare impl´menter l’interface Assoc e (mot-cl´ implements). e e a 2. nous l’avons d´j` exploit´e e e ea e pour les ensembles. Il faut que l’univers des cl´s soit de la forme {0. } } } Les objets L encapsulent une liste d’association. e i f (r == null) { // Absent de la table. cr´er une nouvelle association p = new AList (key. e e e Un objet L peut prendre le type Assoc. ce qui arrive par exemple dans l’appel suivant : // in est un WordReader count(in. 2 Table de hachage La table de hachage est une impl´mentation efficace de la table d’association. les piles et les files dans les chapitres pr´c´dents. avec les signatures conformes. Pour atteindre une efficacit´ bien meilleure. modifier l’ancienne association r. . ne nous leurrons pas un cas aussi simple est exceptionnel en pratique. puis ramener le e cas g´n´ral ` ce cas simple. p) . new L()) . Les m´thodes get et put emploient toutes les e deux la m´thode AList . . nous u e e allons introduire la nouvelle notion de table de hachage. une recherche par getCell peut prendre de l’ordre de N op´rations. et d’autre part que deux ´l´ments distincts aient des cl´s distinctes (ce que nous ee e avons d’ailleurs d´j` suppos´).getCell pour retrouver l’information associ´e ` la cl´ key pass´e en e e a e e argument. Notez que count ne connaˆ pas la classe L. Mais. Cette d´claration entraˆ deux cons´quences importantes. en particulier e dans le cas fr´quent o` la cl´ n’est pas dans la liste. Il en r´sulte que le programme Freq est en e u e e O(n2 ) o` n est le nombre de mots de l’entr´e. Nous allons d’abord observer qu’il existe e e un cas particulier simple quand l’univers des cl´s est un petit intervalle entier. val. La technique de l’encapsulage est d´sormais famili`re. e e ıne e Le compilateur Java v´rifie que les objets de la classe L poss`dent bien les deux m´thodes e e e sp´cifi´es par l’interface Assoc. p) . o` n est un entier pas e u trop grand.1 Adressage direct Cette technique tr`s efficace ne peut malheureusement s’appliquer que dans des cas tr`s e e particuliers. Il y a un d´tail ´trange : les e e e e m´thodes sp´cifi´es par une interface sont obligatoirement public.val = val .

Dans le cas de l’exemple de collision de la figure 1. On parle alors de r´solution e e e des collisions par chaˆ ınage. // Tableau interne de listes d’associations. on obtient la situation de la figure 2. La question est alors : o` ranger l’information associ´e ` la cl´ k3 ? u e a e 2. Nous proposons une nouvelle impl´mentation H des tables d’associations Assoc. v) de la table qui sont ee tels que h(k) vaut l’indice i. e c’est-`-dire de cl´s qui sont des indices dans un tableau. 10 9 8 7 6 5 4 3 2 1 0 0 0 h(k2 ) Univers U des cl´s e k2 k1 k3 k0 h(k1 ) = h(k3 ) h(k0 ) m−1 1 de l’´l´ment d’information v3 de cl´ k3 . . de la e fonction de hachage. en encape sulant cette fois une table de hachage. TABLE DE HACHAGE 69 2. mais dans t[h(k)]. On remarque que les ´l´ments de la table t sont tout bˆtement des listes ee e d’association. class H implements Assoc { final static int SIZE=1024 .hashCode()) % t. 1 – Une collision dans une table de hachage. m − 1} appel´e fonction de hachage. En effet. comme dans l’adressage direct (cela n’a d’ailleurs aucun sens si k n’est pas un entier) . Mais nous devons affronter d`s ` pr´sent une difficult´. L’id´e est de ranger l’´l´ment de cl´ k non pas dans une case de e e ee e tableau t[k]. entier pas trop grand.abs(key. La figure 1 e e illustre la survenue d’une telle collision entre les cl´s k1 et k3 distinctes qui sont telles que e h(k1 ) = h(k3 ).} .2 Table de hachage L’id´e fondamentale de la table de hachage est de se ramener au cas de l’adressage direct.length . Nous reviendrons en 3 sur le choix. ` vrai a e a dire entier de l’ordre du nombre d’´l´ments d’informations que l’on compte g´rer.2. . . H() { t = new AList [SIZE] } .3 R´solution des collisions par chaˆ e ınage La solution la plus simple pour r´soudre les collisions consiste ` mettre tous les ´l´ments d’ine a ee formation dont les cl´s ont mˆme valeur de hachage dans une liste. Soit m. supposons que la collision survient lors de l’ajout e e Fig. // Assez grand ? private AList [] t . il devient e a e e d´raisonnable d’exclure le cas de cl´s (distinctes) k et k ′ telles que h(k) = h(k ′ ). alors qu’il existe d´j` dans la table une cl´ k1 avec ee e ea e h(k1 ) = h(k3 ). Pr´cisons un peu le probl`me. private int hash(String key) { return Math. relativement d´licat. la liste t[i] regroupant tous les ´l´ments d’information (k. On se donne ee e une fonction h : U → {0. .

} else { r. a e e . afin de produire un indice valide. . 2 – R´solution des collisions par chaˆ e ınage. } } public void put(String key. .getCell(key. t[h]) . t[h]) .val . m[ avec une probabilit´ 1/m. AList r = AList. On u ee suppose que le coˆt du calcul de la fonction hachage est en O(1). La classe H d´clare impl´menter l’interface Assoc et le fait effectivement ce que le come e pilateur v´rifie. int val) { int h = hash(key) . en rempla¸ant p e c par t[h].70 CHAPITRE III.abs est malheureusement n´cessaire.getCell(key.val = val . val. et que hachage est uniforme. t[h]) . } } } Le code de la fonction de hachage hash est en fait assez simple. car pour n n´gatif. u c’est-`-dire que la valeur de hachage d’une cl´ vaut h ∈ [0 . e Estimons le coˆt de put et de get pour une table qui contient N ´l´ments d’information. Un objet de la nouvelle classe H est donc un argument valide pour la e m´thode count de la classe Freq. ASSOCIATIONS — TABLES DE HACHAGE Fig. i f (r == null) { t[h] = new AList(key. AList r = AList. 10 9 8 7 6 5 4 3 2 1 0 0 1 k2 v2 Univers U des cl´s e k2 k1 k3 k0 k3 v3 k1 v1 k0 v0 public int get(String key) { int h = hash(key) . i f (r == null) { return 0 . e e e Il est surtout important de remarquer : Le code est en fait presque le mˆme que celui de la classe L (page 67). e e La valeur absolue Math. } else { return r. l’op´rateur e e e (( reste de la division euclidienne )) % renvoie un r´sultat n´gatif (mis`re). parce qu’il utilise la m´thode e de hachage des chaˆ ınes fournie par Java (toute la complexit´ est cach´e dans cette m´thode) e e e dont il r´duit le r´sultat modulo la taille du tableau interne t.

pour notre impl´mentation simple de H qui dimensionne le tableau t e initialement.0 . Pour la premi`re. et c’est un peu vrai. et une recherche fructueuse 1 + α/2 − 1/(2m) cellules. au prix de l’allocation d’un tableau de taille de l’ordre de n. private int nbKeys = 0 . Il n’en reste pas e a a e moins. Plus pr´cis´ment une recherche infructueuse dans la table parcourt e e en moyenne α cellules de listes. par exemple lors du comptage des c e e mots d’un texte. en supposant que le coˆt de calcul de la e e e u fonction de hachage est proportionnel ` la longueur des mots. e e Le coˆt du redimensionnement doit ˆtre proportionnel au nombre d’informations stock´es u e e dans la table au moment de ce redimensionnement. D´finissons d’abord une constante alpha qui est notre borne sup´rieure du facteur de charge.3. quand e u toutes les cl´s entrent en collision. et c’est le principal. e e et une variable d’instance nbKeys qui compte le nombre d’associations effectivement pr´sentes e dans la table. Peut-ˆtre faut il remarquer que le coˆt d’une recherche dans le cas le pire est O(n). alors nous avons atteint un coˆt (en moyenne) de put et get en temps constant. Une e fa¸on plus concr`te de voir les choses est de consid´rer que. de redie mensionner dynamiquement la table de hachage afin de maintenir le facteur de charge dans des limites raisonnables.1) La taille des tableaux internes doit suivre une progression g´om´trique au cours du temps. e ee o` α = n/m est le facteur de charge (load factor ) de la table (n est le nombre de cl´s ` ranger et u e a m est la taille du tableau). e Sous ces deux hypoth`ses. nous constatons que la longueur a des mots d’un texte ordinaire de N mots est faible et ind´pendante de N .2]. faisons confiance a ` la m´thode hashCode des String.1 Compl´ment : redimensionnement dynamique e Dans un deuxi`me temps. contentons nous de remarquer que α est tout simplement la longueur moyenne des listes d’associations t[h]. Il peut sembler que nous nous u sommes livr´s ` une suite d’approximations et d’`-peu-pr`s. il suffit de deux u conditions (comme pour push dans le cas des piles. en constatant que si la taille du tableau interne est de l’ordre de n. coˆts u auquels on ajoute le coˆt du calcul de la fonction de hachage. la recherche d’un ´l´ment se fait en moyenne en temps Θ(1 + α). que les tables de hachage sont efficaces en pratique. essentiellement sous r´serve que pour une ex´cution donn´e.2 La seconde hypoth`se e e traduit simplement que nous disposons d’une (( bonne )) fonction de hachage.2. Ce r´sultat est d´montr´ dans [2. Mais employer les tables de hachage suppose de faire confiance e au hasard (hachage uniforme) et donc de consid´rer plus le cas moyen que le cas le pire. final static double alpha = 4. on ins`re et recherche de nombreux mots uniform´ment hach´s. e . 2. voir II. final static int SIZE = 16 . Pour atteindre un coˆt amorti en temps constant pour put. les valeurs de hachage des cl´s se r´partissent e e e e e uniform´ment parmi les indices du tableau interne correctement dimensionn´. 2 Un autre argument est de dire qu’il existe de l’ordre de N = K ℓ mots de taille inf´rieure ` ℓ. Dans ce cas le coˆt du calcul de la fonction de hachage est en O(log N ). u e e e section 12. il est plus convenable. r´put´ e u e e ind´pendant de n pour n ≪ N . et ce sera aussi plus pratique. on u e e peut voir la table de hachage comme un moyen simple de diviser le coˆt des listes d’association u d’un facteur n. nous pouvons interpr´ter le r´sultat de complexit´ en moyenne d’une recherche en e e e Θ(1 + α). mais aussi que le e e coˆt du calcul de la fonction de hachage ne soit pas trop ´lev´. et que donc le e e e coˆt moyen donne une tr`s bonne indication du coˆt rencontr´ en pratique. TABLE DE HACHAGE 71 Ces deux hypoth`ses sont r´alistes. u e u e Dans un premier temps.2. o` K est le e a u nombre de caract`res possibles. Dans cet esprit pragmatique.

int new_sz = 2*old_sz . } } else { r. // Nouvelle taille AList [] oldT = t .length * alpha < nbKeys) { resize() . il est mˆme assez coˆteux. i++) { for (AList p = oldT[i] . le nouveau e tableau est directement rang´ dans le champ t de this et une r´f´rence sur l’ancien tableau est e ee conserv´e dans la variable locale oldT. En effet. i f (t. p = p. effectu´ apr`s ajout d’une nouvelle associae e e e tion. // garder une r´f´rence sur l’ancien tableau e e t = new AList [new_sz] . t[h]) . t[h]) . e public void put(String key. C’est aussi une bonne id´e de proc´der ainsi afin e e e e e que le redimensionnement ait effectivement lieu et que le code correspondant soit test´. afin de ne pas mobiliser e e une quantit´ cons´quente de m´moire a priori. ASSOCIATIONS — TABLES DE HACHAGE Nous avons aussi chang´ la valeur de la taille par d´faut de la table. le cas ´ch´ant.key) . le temps de parcourir les paires cl´-information contenues e e dans les listes de l’ancien tableau pour les ajouter dans le nouveau tableau t. i f (r == null) { t[h] = new AList(key.72 CHAPITRE III. p != null . t[h]) . e e e ´ Le facteur de charge α est donc n´cessairement inf´rieur ` un. Plus pr´cis´ment. la table de hachage est un tableau de paires cl´-information.length d´passe alpha. Pour cette raison. puis. si cette case est e a occup´e par une information de cl´ k ′ diff´rente de k. la valeur de hachage h n’est valide que relativement ` la longueur de tableau t.t. mais il reste bien proportionnel au nombre e u d’informations stock´es — sous r´serve d’un calcul en temps constant de la fonction de hachage.val. val. on continue la recherche en suivant une e e e .length). p. AList r = AList. // Allouer le nouveau tableau /* Ins´rer toutes les paires cl´-information de oldT e e dans le nouveau tableau t */ for (int i = 0 .4 Adressage ouvert Dans le hachage ` adressage ouvert. t[h] = new AList (p. int val) { int h = hash(key) .key. } } Notez que le redimensionnement est. nbKeys++ . private void resize() { // Ancienne taille int old_sz = t.getCell(key. Le redimensionnement n’est pas gratuit. i < old_sz . e La m´thode de redimensionnement resize. ` ajouter ` la classe H double la taille du tableau e a a interne t. } } } Il faut noter que la fonction de hachage hash qui transforme les cl´s en indices du tableau t e d´pend de la taille de t (de fait son code emploie this .length . Etant donn´e une cl´ k on e e a e e recherche l’information associ´e ` k d’abord dans la case d’indice h(k). les ´l´ments d’informations sont stock´s directement a ee e dans le tableau.next) { int h = hash(p.val = val . a 2. e e C’est la m´thode put qui tient ` jour le compte nbKey et appelle le m´thode resize quand e a e le facteur de charge nbKeys/t.

val) . La relative complexit´ de e e e e getSlot justifie cette organisation. jusqu’` e e e a trouver une case libre ou une case contenant la paire (k. La m´thode put est modifi´e pour g´rer le e e e e compte nbKeys des informations effectivement pr´sentes dans la table.6. private int nbKeys = 0 . Dans le constructeur. La m´thode getSlot. v). quand la table est pleine e e — notez l’emploi de la boucle do. une fois entr´e dans la table. public void put(String key. modulo m taille de la table. Comme dans le cas du chaˆ ınage. Dans les deux cas. qui utilise le e hachage avec adressage ouvert. this. v ′ ). une cl´ reste ` la mˆme place dans le tableau et est e e a e acc´d´e selon la mˆme s´quence. Exercice 1 Modifier le code de la classe O afin de redimensionner automatiquement le tableau interne. Dans le premier cas il existe un ´l´ment de cl´ k dans la table. voir B. qui est justement la valeur qui permet ` getSlot d’identifier e a a les cases (( vides )). au besoin en ´crasant la valeur v ′ anciennement associ´e ` k. Pour ajouter une information (k. on dispose d’une case o` ranger (k. on proc`de exactement de la mˆme mani`re. ee e dans le second il n’en existe pas. e e final static double alpha = 0. TABLE DE HACHAGE 73 s´quence d’indices pr´d´finie. est appel´e par les deux m´thodes put et get.length * alpha < nbKeys) resize() . La m´thode getSlot peut ´chouer.key = key . v). Solution. La s´quence la plus simple consiste ` examiner successivement e a les indices h(k). Nous d´finissons d’abord une classe des paires cl´-information.val = val . C’est le sondage lin´aire e (linear probing). ce qui rend la question du dimensionnement du tableau interne plus critique que dans le cas du chaˆ ınage.2.2). i f (p == null) { nbKeys++ . ` condition de ne pas supprimer d’informations. } } Les objets O poss`dent en propre un tableau d’objets Pair .val = val . e e class Pair { String key . i f (t. Le code de la classe O est donn´ e e par la figure 3. d`s que le facteur de charge d´passe une valeur critique alpha.3. jusqu’` trouver une case contenant une information dont la cl´ e e e a e vaut k ou une une case libre. charg´e de trouver la case o` ranger une association e e u en fonction de la cl´. h(k) + 1.4. h(k) + 2 etc.5 .3. } } . t[h] = new Pair(key. ce que nous e e e e a supposons. Pour coder une nouvelle impl´mentation O de la table d’association Assoc. int val . Pair(String key. et appeler resize si e besoin est. Selon cette u e e a technique. } else { p. les cases du tableau new Pair [SIZE] sont implicitement initialis´es ` null (voir B. int val) { int h = getSlot(key) . int val) { this. Pair p = t[h] . nous allons ´crire une m´thode priv´e resize charg´e e e e e d’agrandir la table quand elle devient trop charg´e.

i f (h >= t.val = val . 3 – Impl´mentation d’une table de hachage ` adressage ouvert e a class O implements Assoc { private final static int SIZE = 1024 . throw new Error ("Table pleine") . on a trouv´ */ e e i f (t[h] == null || key.hashCode()) % t. // On a fait le tour complet } public int Pair p = i f (p == return } else { return } } get(String key) { t[getSlot(key)] . do { /* Si t[h] est (( vide )) ou contient la cl´ key. public void put(String key.val .74 CHAPITRE III. Pair p = t[h] . } } } . } /* M´thode de recherche de la case associ´e ` key */ e e a private int getSlot(String key) { int h0 = hash(key) . /* Sinon. int h = h0 . ASSOCIATIONS — TABLES DE HACHAGE Fig. } else { p. // Assez grand ? private Pair [] t .length) h = 0 . // Tableau interne de paires O() { t = new Pair[SIZE] . val) . } while (h != h0) .length . p. null) { 0 . } private int hash(String key) { return Math. i f (p == null) { t[h] = new Pair(key.abs(key.equals(t[h]. passer ` la case suivante */ a h++ . int val) { int h = getSlot(key) .key)) return h .

o` α est le facteur de charge et sous r´serve de hachage uniforme. Pour que le sondage puisse parcourir toute la table on prend h′ (k) > 0 et e e h′ (k) premier avec m taille de la table. la probabilit´ pour que la case i soit choisie e e e a ` la prochaine insertion est la 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 suivante 0 1 2 3 4 P (0) = 1/19 P (12) = 3/19 P (2) = 2/19 P (13) = 1/19 P (3) = 1/19 P (14) = 1/19 P (8) = 5/19 P (16) = 2/19 P (9) = 1/19 P (18) = 2/19 Comme on le voit. prendre la pr´caution de ranger le nouveau tableau dans e la variable d’instance t avant de commencer ` calculer les valeurs de hachage dans le nouveau a tableau. le nombre moyen de sondages pour un hachage e . sous des hypoth`ses de distribution uniforme et e d’ind´pendance des deux fonctions de hachage.4] D. ce qui accentuera le le e e e regroupement des cases 4–7. k < old_sz . Knuth montre que. m − 1} et h′ : U → {1. La meilleure solution consiste ee e e e a ` utiliser un double hachage. e e e Consid´rons par exemple la table ci-dessous. un cas qui se pr´sente en pratique avec le hachage modulo e e (voir 3). section 6. mais les formules donnent toujours un majorant. . k++) { Pair p = oldT[k] . . Bref. etc. TABLE DE HACHAGE 75 La m´thode resize fait appel ` getSlot pour transf´rer les informations de l’ancienne ` la e a e a nouvelle table. On d´montre [6. des adresses d’objets qui se suivent dans la m´moire. . . section 6. comme dans le cas du chaˆ ınage. t = new Pair[new_sz] . } } } Il faut. C’est le meilleur moyen de garantir des ajouts compatibles avec les m´thodes e put et get. r − 1} (r < m).length . e Plusieurs solutions ont ´t´ propos´es pour ´viter ce probl`me. pour un facteur de charge de 50 % on examine en moyenne pas plus de trois cases. private void resize() { int old_sz = t. dans des e applications plus techniques. Ensuite le sondage est effectu´ selon la s´quence h(k) + h′ (k). h(k) + 3h′ (k). o` les cases encore libres sont en blanc : e u 1 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 En supposant que h(k) soit distribu´ uniform´ment. On note aussi que oldT[k] peut valoir null et qu’il faut en tenir compte. . int new_sz = 2*old_sz . ou.2. Ce ph´nom`ne se r´v`le rapidement quand des cl´s successives sont e e e e e hach´es sur des entiers successifs. Pair [] oldT = t . e e h(k) + 2h′ (k). Les regroupements ne sont plus ` craindre essentiellement a parce que l’incr´ment de la s´quence est lui aussi devenu une fonction uniforme de la cl´. . On se donne deux fonctions de hachage h : U → {0. Le sondage lin´aire provoque des ph´nom`nes de regroupement (en plus des collisions). i f (p != null) t[getSlot(p. la case 8 a la plus grande probabilit´ d’ˆtre occup´e. Pour ce faire on peut prendre m ´gal ` une puissance e a ′ (k) toujours impair. En e e e particulier en cas de collision selon h. .key)] = p .4] qu’une recherche infructueuse entraˆ en moyenne l’examen e ıne d’environ 1/2 · (1 + 1/(1 − α)2 ) cases et une recherche fructueuse d’environ 1/2 · (1 + 1/(1 − α)). il n’y a aucune raison que les sondages se fassent selon le mˆme incr´ment. for (int k = 0 . Ces r´sultats ne sont stricto u e e sensu plus valables pour α proche de un. ou m premier et h′ (k) strictement inf´rieur ` m (par exemple de deux et h e a pour des cl´ enti`res h(k) = k mod m et h′ (k) = 1 + (k mod (m − 2)) e e Dans [6. . quand les cl´s sont par exemple les valeurs successives d’un compteur.

5 Tables de hachage de la libraire Il aurait ´t´ ´tonnant que la libraire de Java n’offre pas de table de hachage. On ´crit donc une derni`re impl´mentation.put et t.65 100.util.4). Insistons sur le fait que les valeurs de ce tableau sont ind´pendantes de n.00 99 % 4.val) . mˆme avec dix milliards de cl´s ! Notons ee e e que pour le sondage lin´aire cet ordre de grandeur est atteint pour des tables ` moiti´ pleines. L’espace m´moire e e e employ´ devient alors respectivement 6 + 4 · n et 6 + 5/2 · n. il suffit e e en moyenne de deux essais pour retrouver un ´l´ment. H() { t = new HashMap <String. class Lib implements Assoc { private HashMap <String.39 2. e e ee e e e . e 3 Ces valeurs proviennent d’un mod`le simplifi´. e import java.get sont celles des HashMap. } } public void put(String key. mais on peut coder avec moins de m´moire e e (par exemple en g´rant deux tableaux. Par cons´quent.01 5. i f (val == null) { return 0 . } } La table de hachage t est construite avec la taille initiale et le facteur de charge par d´faut e (respectivement 16 et 0.* .Integer> t . un pour les cl´s. e e Nous constatons que le chaˆ ınage consomme un tableau de taille n/4 plus n cellules de listes ` a trois champs.76 CHAPITRE III.00 90 % 2.3). ASSOCIATIONS — TABLES DE HACHAGE double est environ − ln(1 − α)/α en cas de succ`s et ` 1/(1 − α) en cas d’´chec. tant cette struce ee ture est utile. Le gain des deux derni`res techniques est donc minime. } else { return val . en tenant compte de deux cases e suppl´mentaires par objet (voir II.put(key. Examinons alors la m´moire occup´e pour n associations. e e 2. et ont ´t´ v´rifi´es exp´rimentalement.2. et le double hachage 3 + 5/4 · n + 4 · n (tableau et paires encore).00 Comme on le voit α = 80 % est un excellent compromis.75). un pour les valeurs). Les m´thodes t.get(key) . nous ´galisons plus e ou moins les temps de recherche.00 80 % 2.V> param´tr´e par les e e e e classes des cl´s K et des informations V (voir II. e a e Tandis que pour le chaˆ ınage on peut aller jusqu’` un facteur de charge d’environ 4. La classe HashMap est une classe g´n´rique HashMap<K. soit finalement une ´conomie de e e m´moire cons´quente pour le double hachage. a En fixant ces valeurs de facteur de charge pour les trois techniques. avec un facteur charge de 80 %.56 10. qui se e ` comportent comme les nˆtres. A ceci pr`s que cl´s et informations sont obligatoirement des o e e objets et que l’appel t. int val) { t. Tandis que le sondage lin´aire consomme 3 + 2 · n + 4 · n e e (tableau et paires).Integer> () .3 Le tableau e a e ci-dessous donne quelques valeurs num´riques : e Facteur de charge Succ`s e Echec 50 % 1. de notre interface Assoc. } public int get(String key) { Integer val = t. e e e e tr`s courte. soit 3 + n/4 + 5 · n cases de m´moire en Java.get(key) renvoie null quand la cl´ key n’est pas dans la table t.

75.5. e En voici pour preuve (figure 5) l’histogramme des effectifs des longueurs des listes de collisions a ` la fin de l’exp´rience H (tableau de taille m = 4096. e La table L bas´e sur les listes d’associations. taille initiale 16) Toutes les tables de hachage sont redimensionn´es automatiquement. compte tenu des ee e performances atteintes pour une difficult´ de r´alisation particuli`rement faible (surtout si on e e e utilise la biblioth`que).6 Performance Nous nous livrons ` une exp´rience consistant ` appliquer le programme Freq ` une s´rie de fia e a a e chiers contenant du source Java. e En derni`re analyse.val) dans put). c’est donc surtout cette derni`re qui se montre performante ici. On note la conversion automae tique d’un Integer en int (return val dans le corps de get) et d’un int en Integer (appel t.0. nombre d’associations n = 16092). e . pour les quatre impl´mentations des tables d’associations. facteur de charge 0. e La table H bas´e sur le hachage avec chaˆ e ınage (facteur de charge 4. Cette e figure montre par exemple qu’il y a un peu plus de 800 listes de collisions de longueur trois. Les r´sultats (figure 4) e e Fig. 4 – temps d’ex´cution de la m´thode count e e 14 12 10 t (sec) 8 6 4 2 0 ×× 3 ×+ + 3 ×+ 2 2 × 2 3 3 + 2 ×× + 2 3 ×+ 3 22 3 ××+ 3 ×+ + 2 2 3 ×+ 2 3 ×+×+ 3 2 2 3 ×2×2 + + 3 ’L’ 3 3 × ×+ + 3 2 2 ’H’ + × + 3 2 + 3 2 ’O’ 2 3 +2× 3×× × 2 + ’Lib’ × 3 + 2 × + 3 2 0 100000 200000 300000 400000 500000 600000 Nombre total de mots font clairement apparaˆ que les listes d’associations sont hors-jeu et que les tables de hachage ıtre ont le comportement lin´aire attendu et mˆme que toutes les impl´mentations des tables de e e e hachages se valent. facteur de charge 0. taille initiale 16) La table O bas´e sur le hachage ouvert (sondage lin´aire.2. TABLE DE HACHAGE 77 fait que nous exploitons directement dans notre m´thode get. page 66).put(key. l’efficacit´ des tables de hachage repose sur l’uniformit´ de la fonction e e e de hachage des chaˆ ınes de Java. taille e e initiale 16) La table Lib bas´e sur les HashMap de la librairie (probablement chaˆ e ınage. Nous mesurons le temps cumul´ d’ex´cution de la m´thode count e e e (celle qui lit les mots un par un et accumule les comptes dans la table d’association Assoc. En conclusion l’int´rˆt en pratique des tables de hachage est tr`s important. 2. au moins dans le cadre de cet exp´rience.

Si les caract`res sont quelconques on a B = 216 en Java et B = 28 e en C . on peut interpr´ter chaque caract`re comme un chiffre d’une base B et la chaˆ e e e ıne comme un entier ´crit dans cette base. e cela signifie que si on se donne une probabilit´ P sur U et que l’on choisit al´atoirement chaque e e cl´ k dans U . 5 – R´partition des collisions e 900 800 700 600 500 400 300 200 100 0 0 1 2 3 4 5 6 7 8 9 10 11 12 3 3. si on prend m = 2r . e P (k) = {k|h(k)=j} 1 m En pratique. . si les caract`res des cl´s sont limit´s ` l’alphabet minuscule. m − 1}. l’interversion de deux caract`res passera inaper¸ue. Ce qui veut dire. Formellement. Par exemple. on veut souvent que des cl´s voisines donnent des valeurs de hachages tr`s distinctes. car on peut toujours se ramener ` ce cas. . et conduit ` de nombreuses a collisions si les cl´s sont par exemple des adverbes (se terminant toutes par ment). Dans le mˆme e e contexte. Une technique courante consiste ` prendre pour h(k) le reste de la division de k par m : a h(k) = k mod m Mais dans ce cas. . si on prend m = B − 1. an−1 est l’entier a0 · e a ıne B n−1 + a1 · B n−2 + · · · + an−1 . par exemple. ASSOCIATIONS — TABLES DE HACHAGE Fig. si les cl´s sont des chaˆ a e ınes de caract`res. e e e a Nous pouvons donc revenir aux fonctions de hachage sur les entiers. . on a a · B + b ≡ b · B + a (mod m) En pratique. Une bonne fonction de hachage doit se rapprocher le plus possible d’une r´partition uniforme. . etc. h(k) ne ae d´pendra que des r derniers bits de k. e e Le cas que nous traitons ci-dessous est celui o` l’univers U est un sous-ensemble des entiers u naturels. C’est ` dire que la chaˆ a0 a1 . une bonne valeur pour m est un nombre premier tel que B k ± a n’est pas divisible par m. on peut prendre B = 26 . . on connaˆ rarement la probabilit´ P et on se limite ` des heuristiques. pour des entiers k et a petits. En partiıt e a culier. on ait pour tout j avec 0 ≤ j ≤ m − 1.1 Choix des fonctions de hachage En th´orie e Rappelons qu’une fonction de hachage est une fonction h : U → {0.78 CHAPITRE III. certaines valeurs de m sont ` ´viter. En effet. e c comme (a · B + b) − (b · B + a) = (a − b)(B − 1). Par exemple. que le d´but des chaˆ e e ınes longues (vues comme des entiers en base B = 2p ) ne compte plus.

an−1 renvoie a0 · ıne 31n−1 + a1 · 31n−2 + · · · + an−2 · 31 + an−1 . page 67).2 En pratique Continuons de consid´rer les chaˆ e ınes comme des entiers en base 216 . page 69). c’est ` dire e a d’objets d’une classe Pair . k++) h = 31 * h + this. Or.2. l’appel hashCode() de la chaˆ a0 a1 .1). ils ne tiennent plus dans un int d`s que la chaˆ est de taille sup´rieure ` 2. tandis que hashCode renvoie essentiellement l’adresse en m´moire de l’objet ! Il e faut red´finir ces deux m´thodes. puis la m´thode equals de e e la mˆme cl´ (pour par exemple trouver sa place dans une liste de collision. puis calculer h. ou plus exactement red´finir e nos propres m´thodes hashCode.3. puisqu’elle est d´finie dans la classe des chaˆ e ınes et ne prend ` pas une taille de tableau en argument. Le ıne e e a multiplicateur 31 est un nombre premier. CHOIX DES FONCTIONS DE HACHAGE Une autre technique consiste ` prendre a h(k) = ⌊m(Ck − ⌊Ck⌋)⌋ 79 o` C est une constante r´elle telle que 0 < C < 1. e ıne e a On ne peut donc pas (pour un coˆt raisonnable. Supposons donc que nous voulions nous servir de paires d’entiers comme cl´s.3). . return h . mais sur le contenu des chaˆ ınes. Knuth recommande le nombre d’or. 3. √ C = ( 5 − 1)/2 ≈ 0. Dans le cas du hachage modulo un ıne nombre premier. Cette m´thode a l’avantage de pouvoir u e e s’appliquer ` toutes les valeurs de m. c’est-`-dire son code en Unicode (voir B. En effet equals par d´faut exprime l’´galit´ physique des cl´s e e e e e (voir B. Notre table de hachage H appelle d’abord la m´thode hashCode e e d’une cl´ (pour trouver un indice dans le tableau interne.charAt(k) effectue un m´lange entre une valeur courante e de h (qui est la valeur de hachage du pr´fixe de la chaˆ e ıne) et la valeur de hachage d’un caract`re e de la chaˆ qui est le caract`re lui-mˆme. et ce n’est pas un hasard. L’exp´rience nous a montr´ e e que ce m´lange simple fonctionne en pratique.3). sur un ensemble de cl´s qui sont les mots que l’on e e trouve dans des sources Java (voir en particulier la figure 5). c e e a Car il faut parfois construire nos propres fonction de hachage. A toute chaˆ elle associe un int qui est ensuite r´duit ıne e en indice. Mais en fait ce n’est pas tr`s pratique : e la fonction de hachage (m´thode hashCode) des chaˆ e ınes de Java est ind´pendante de la tailles e des tableaux internes des tables.3.length . La table e e de hachage de la librairie proc`de similairement. Il est mˆme conseill´ dans ce cas de prendre m = 2r a e e pour faciliter les calculs. mais nous allons nous en tenir ` elle. les m´thodes e e e e par d´faut ne conviennent pas. Selon la documentation. for (int k = 0 .charAt(k) .2. Autrement dit le code de hashCode pourrait ˆtre e celui-ci : public int hashCode() { int h = 0 . Ces entiers sont tr`s e vite grands. comme nous avons d´j` parfois red´fini la m´thode toString e e ea e e (voir B.1. k < this. 618. il reste possible de calculer modulo m.3. . Le calcul h = 31 * h + this . . La classe String ne proc`de pas autrement. Pour le choix de la constante C. mˆme si tous les objets poss`dent bien e e e des m´thodes hashCode et equals (comme ils poss`dent une m´thode toString). il existe des entiers en pr´cision arbitraire) u e d’abord transformer la chaˆ en entier. son hashCode et son equals se basent e non pas sur l’adresse en m´moire de la chaˆ e ıne. } Dans nos tables de hachage nous avons ensuite simplement r´duit cet entier modulo la taille du e tableau interne. Pour des cl´s plus g´n´rales cette e e e fa¸on de m´langer est critiqu´e [8].

hashCode()). Pair (int x. e e e .80 CHAPITRE III.x && this. } ´ Pour la conversion de type.x == p.y .equals(p2 ) renvoie true) e e entraˆ l’´galit´ des code de hachage (p1 . int y) { this. Or. y .2. voir B. la signature de la m´thode equals des e e Object (celle que nous voulons red´finir) est : e public boolean equals(Object o) Nous ´crivons donc (dans la classe Pair ) : e public boolean equals(Object o) { Pair p = (Pair)o .y == p.2. this.y = y . ASSOCIATIONS — TABLES DE HACHAGE class Pair { int x. Il importe en fait de e e e e e respecter toute la signature de la m´thode d’origine. ´choue si o n’est pas un Pair e return this. e e public int hashCode() { return x * 31 + y .3. ce que la documenıne e e tation de la biblioth`que appelle le contrat g´n´ral de hashCode. pour que les tables de hachage fonctionnent correctement il faut que l’´galit´ selon equals (l’appel p1 . // Conversion de type. } } Pour red´finir hashCode nous utilisons tout simplement le m´langeur multiplicatif. } La m´thode red´finie est d´clar´e public comme la m´thode d’origine. Evidemment.x = x .hashCode() == p2 .

C’est un entier positif ou nul. ` droite un graphe non orient´. . les files de priorit´. arbres binaires. appel´s aussi graphes orient´s ((( digraph )) en anglais. etc. e 1 D´finitions e Pour pr´senter les arbres de mani`re homog`ne.1 Graphes Un graphe G = (S. Les nœuds sont souvent repr´sent´s par des points e e dans le plan. Un chemin de s ` t est une suite (s = s0 . qui s’applique dans une grande vari´t´ de e ee situations. Le nœud s0 est l’origine du chemin et le nœud sn son extr´mit´. si ) soit un arc. Ces d´finitions sont pr´cis´es dans la section 1. Nous pr´senterons donc les graphes. e e e le tri par tas et l’algorithme baptis´ (( union-find )). t) par une ligne orient´e joignant s ` t. 1 – A gauche un graphe. pour (( directed e e graph ))). les e e arbres enracin´s et les arbres ordonn´s. On dit que l’arc a part e a de s et va ` t. Au lieu de couples de nœuds. puis successivement. e e 1. e e L’entier n est la longueur du chemin. et un arc a = (s. on consid`re e e 81 . . Les arbres binaires de recherche seront trait´s dans le chapitre suivant. quelques termes emprunt´s aux graphes e e e e s’av`rent utiles.Chapitre IV Arbres Ce chapitre est consacr´ aux arbres. Plusieurs notions distinctes se cachent en fait sous cette terminologie : arbres libres. l’un des concepts algorithmiques les plus importants de e l’informatique. . e e e e Nous pr´sentons plusieurs applications des arbres : les arbres de d´cision. Un circuit est un chemin de longueur non nulle dont l’origine co¨ ıncide avec l’extr´mit´. pour a a 1 i n. Les arbres servent ` repr´senter un ensemble de donn´es structur´es hi´rarchia e e e e quement. . L’ensemble A est une partie de S × S. arbres enracin´s. e e 1 2 1 2 3 4 3 4 5 6 5 6 ` Fig. les arbres libres. a e ` oe A cˆt´ de ces graphes. (si−1 . A) est un couple form´ d’un ensemble de nœuds S et d’un ensemble A e d’arcs. sn = t) de nœuds tels que. il existe la variante des graphes non orient´s.

e 1. La e e hauteur d’un arbre T est la longueur maximale d’un chemin reliant sa racine ` une feuille. mais ne l’est plus si l’on retire une arˆte quelconque. e (3) G est connexe. La proposition suivante est laiss´e en exercice. Un a arbre r´duit ` un seul nœud est de hauteur 0. e e a Un chemin est simple si tous ses nœuds sont distincts. appel´ sa racine. et x est un descendant a e de y. e a 1 2 5 6 7 3 8 9 4 Fig.82 CHAPITRE IV. mais ne l’est plus si l’on ajoute une arˆte quelconque. et Card(A) = Card(S) − 1. e (5) G est connexe. Les conditions suivantes sont e ´quivalentes : e (1) G est un arbre libre. (6) G est sans circuit. nous pr´sentons des familles d’arbres de plus en plus contraints. (2) Deux nœuds quelconques de S sont connect´s par un chemin simple unique.2 Arbres libres Dans la suite de chapitre. e e appel´es arˆtes. e Fig. Un arbre libre est un graphe non orient´ e e e e non vide. e (4) G est sans circuit. t} de nœuds. Un nœud e sans enfant est une feuille. Pour tout nœud x.3 Arbres enracin´s e Un arbre enracin´ ou arbre ((( rooted tree )) en anglais) est un arbre libre muni d’un nœud e distingu´. 2 – Un arbre (( libre )). connexe et sans circuit. e . Les concepts de chemin et circuit se transposent sans peine ` ce contexte. Un graphe est connexe si deux quelconques de ses nœuds sont reli´s par un chemin. un nœud d’arit´ strictement positive est appel´ nœud interne. ARBRES des paires {s. Tout nœud y sur ce chemin est un ancˆtre de x. e La famille la plus g´n´rale est form´e des arbres libres. Soit T un arbre de racine r. Le sous-arbre de racine x est l’arbre contenant tous les descendants de x. et x est un ` e e enfant (ou un fils ou une fille) de y. et Card(A) = Card(S) − 1. A) un graphe non orient´ non vide. L’arit´ d’un nœud est le nombre de ses enfants. Un graphe non orient´ est donn´ par un ensemble de ces paires. 3 – Un arbre (( enracin´ )). il existe un chemin e e simple unique de r ` x. Proposition 1 Soit G = (S. 1. L’avant-dernier nœud y sur l’unique chemin reliant r a x est le parent (ou le p`re ou la m`re) de x.

. ` chaque e a nœud. et d’une partition des nœuds restants e e en un ensemble d’arbres. e e e e dans un programme.2 2. ou gestion des partitions Comme premier exemple de l’emploi des arbres et des forˆts. se pr´sente comme un arbre e e ordonn´ (voir figure 4). Nous donnons d’abord une solution du probl`me Union-Find. on e e veut r´soudre les deux probl`mes que voici : e e trouver la classe d’un ´l´ment (find ) ee faire l’union de deux classes (union). . Rappelons ee a e e e e qu’une partition d’un ensemble E est un ensemble de parties non vides de E. Une e c e premi`re solution consiste ` repr´senter la partition par un tableau classe. Enfin. {(7). 4 – L’arbre ordonn´ de la table des mati`res d’un livre.2 2. par une liste attach´e au nœud. Un autre solution est d’associer. On montre ainsi e e facilement que si tout nœud interne d’un arbre est d’arit´ au moins 2. nous consid´rons un probl`me e e e c´l`bre.3 3 Fig.2 1. sections. un tableau de fils.1 1. deux ` deux a ´ disjointes et dont la r´union est E. l’arbre de la figure 3 correspond ` la d´finition a e T = (1. (9)}). alors l’arbre a strictement e plus de feuilles que de nœuds internes. il faut imaginer la fa¸on de repr´senter une partition. appel´ le probl`me Union-Find. on verra plus loin une autre repr´sentation au moyen d’arbres binaires. n − 1}.1 2 2. etc.1 1. . puis nous donnerons quelques e exemples d’application. UNION-FIND. e 1. Une forˆt est un ensemble d’arbres. . e 2 Union-Find. Chaque classe est e a e . (8). (3. C’est une solution moins souple si le nombre de fils est destin´ ` e a changer. e Avant de traiter ce probl`me.2. . e 1 1. 2. et ` ce jour pas encore enti`rement r´solu. on part d’une partition o` chaque classe est r´duite ` un singleton.1 Une solution du probl`me e En g´n´ral. {(5).2. appel´ sa racine.2. Par exemple. Les enfants d’un nœud d’un arbre ordonn´ sont souvent repr´sent´s. puis on traite e e u e a une suite de requˆtes de l’un des deux types ci-dessus.4 Arbres ordonn´s e Un arbre ordonn´ est un arbre dans lequel l’ensemble des enfants de chaque nœud est totae lement ordonn´. Un arbre sur un ensemble fini de nœuds e e est un couple form´ d’un nœud particulier. structur´ en chapitres. e e Par exemple. Etant donn´ une partition de l’ensemble {0. {(2. (4)}) Cette d´finition r´cursive est utile dans les preuves et dans la programmation. OU GESTION DES PARTITIONS 83 Les arbres admettent aussi une d´finition r´cursive. un livre. (6)}).

Il apparaˆ avantageux de repr´senter la partition par ee e ıt e une forˆt. et classe[x] contient le num´ro de la classe de l’´l´ment x e e ee (cf. On convient que. Fusionner deux classes revient alors ` changer de repr´sentant e a e pour les ´l´ments de la classe fusionn´e. {3. et l’entier pere[x] est le p`re du nœud x. figure 5). {0. 7. Ce tableau est initialis´ ` l’identit´ par ea e static void initialisation() { for (int i = 0. } Chercher le repr´sentant de la classe contenant un ´l´ment donn´ revient ` trouver la racine de e ee e a l’arbre contenant un nœud donn´. 6}. i < pere. Figure 7). e e e a 5 0 1 7 2 6 4 9 8 3 Fig. La racine de l’arbre e e est le repr´sentant de sa classe. 9}}. 7. x pere[x] 0 0 1 1 2 5 3 4 4 7 5 5 6 0 7 7 8 2 9 7 Fig. Chaque classe de la partition constitue un arbre de cette forˆt. 6}.length . x classe[x] 0 2 1 3 2 1 3 4 4 4 5 1 6 2 7 4 8 1 9 4 Fig. Chaque nœud est e e e repr´sent´ par un entier. Ceci se fait par la m´thode suivante : e e static int trouver(int x) . {0. consiste ` choisir un e e a repr´sentant dans chaque classe. 5. 9}}. que nous d´taillons maintenant. {1}. 4. ea e On suppose donc d´fini un tableau e int[] pere = new int[n]. dans ce cas. 5 – Tableau associ´ ` la partition {{2. i++) pere[i] = i. {3.84 CHAPITRE IV. 8}. 5. 8}. ea Trouver la classe d’un ´l´ment se fait en temps constant. Une racine r n’a pas de e e e parent. pere[r] = r. 6 – Forˆt associ´e ` la partition {{2. ARBRES identifi´e par un entier par exemple. e e a Une forˆt est repr´sent´e par un tableau d’entiers pere (cf. Une deuxi`me solution. puisqu’il faut parcourir tout le tableau pour rep´rer les ´l´ments dont il faut e ee changer la classe. La figure 6 montre la forˆt associ´e ` une partition. 4. 7 – Tableau associ´ ` la forˆt de la figure 6. {1}. mais fusionner deux classes prend ee un temps O(n).

} 5 1 7 2 0 4 9 8 6 3 Fig. 8 – La forˆt de la figure 6 apr`s l’union pond´r´e de 5 et 0. qui doit ˆtre initialis´ ` 1 : e ea int[] taille = new int[n]. o` h e e u est la hauteur l’arbre (la plus grande des hauteurs des deux arbres). int s = trouver(y). return x. int y) e e { int r = trouver(x). taille[r] += taille[s].2. La nouvelle m´thode d’union s’´crit alors : e e static void unionPond´r´e(int x. En fait. Pour mettre en œuvre cette strat´gie. OU GESTION DES PARTITIONS { while (x != pere[x]) x = pere[x]. int y) { int r = trouver(x). if (r == s) return. UNION-FIND. if (taille[r] > taille[s]) { pere[s] = r. int s = trouver(y). on peut am´liorer e l’efficacit´ de l’algorithme par la r`gle suivante (voir figure 8) : e e R`gle. on utilise un tableau suppl´mentaire qui m´morise la e e e taille des arbres. Lors de l’union de deux arbres. e e ee Il n’est pas difficile de voir que chacune de ces deux m´thodes est de complexit´ O(h). la racine de l’arbre de moindre taille devient fils de la e racine de l’arbre de plus grande taille. } else { . } 85 L’union de deux arbres se r´alise en ajoutant la racine de l’un des deux arbres comme nouveau e fils ` la racine de l’autre : a static void union(int x. if (r != s) pere[r] = s.

taille[s] += taille[r]. appliqu´e cette fois-ci lors de la m´thode trouver permet une nouvelle e e e e am´lioration consid´rable de la complexit´. a ee ee Preuve. avec 1 ee a a m n/2. x = y. e e e . e La figure 9 montre la transformation d’un arbre par une compression de chemin. e static int trouverAvecCompression(int x) { int r = trouver(x). 2 + ⌊log2 m⌋) . Une deuxi`me strat´gie. Apr`s ˆtre remont´ du nœud x ` sa racine r. } L’ensemble des deux strat´gies permet d’obtenir une complexit´ presque lin´aire. Pour n = 1. while (x != r) { int y = pere[x]. L’implantation de cette r`gle se fait simplement.86 pere[r] = s. Par r´currence sur n. ARBRES Lemme 2 La hauteur d’un arbre ` n nœuds cr´´ par union pond´r´e est au plus 1 + ⌊log2 n⌋. cette valeur est major´e par 1 + ⌊log2 n⌋. } return r. e 1 1 5 9 3 5 4 10 9 3 8 4 7 8 7 10 2 11 2 11 Fig. Si un arbre est obtenu par e a union pond´r´e d’un arbre ` m nœuds et d’un arbre ` n − m nœuds. 9 – (( Trouver )) 10 avec compression du chemin. } } L’int´rˆt de cette m´thode vient de l’observation suivante : ee e CHAPITRE IV. sa hauteur est major´e par e max(1 + ⌊log2 (n − m)⌋. Elle est bas´e sur la r`gle de compression de chemins e e e e e suivante : R`gle. Comme log2 m log2 (n/2) = log2 n − 1. pere[x] = r. il n’y a rien ` prouver. Chacun des nœuds 10 et 4 devient fils de 1. on refait le parcours en faisant de chaque e e e e a nœud rencontr´ un fils de r.

soit l’union disjointe d’un nœud appel´ sa racine. les connexions entre deux ordinateurs). le 1 au 6 et le 7 au 8. m) 2 pour m n et n < 265536 et par cons´quent. r. ∅)). On prend ensuite les arˆtes. ARBRES BINAIRES 87 Th´or`me 3 (Tarjan) Avec l’union pond´r´e et la compression des chemins. le 2 au 3.e. Au d´part. (∅. e e La distance d’un nœud x ` la racine ou la profondeur de x est ´gale ` la longueur du chemin a e a de la racine ` x. et on fusionne les arbres contenant les extr´mit´s e e de l’arˆte si ces extr´mit´s appartiennent ` des arbres diff´rents. mais imaginez la mˆme question pour un r´seau dont la taille serait e a e e de l’ordre de plusieurs millions. le 5 au 4. o` α est l’inverse e u d’une sorte de fonction d’Ackermann. On prend ensuite les arˆtes e a e (i. Ad ). chaque nœud constitue ` lui seul un arbre. comme un algorithme lin´aire en n + m.3. une suite de n − 1 e e ee (( unions )) et de m (( trouver )) (m n) se r´alise en temps O(n + mα(n. Il y a au plus 2i nœuds ` distance i. 2. Proposition 4 Soit A un arbre binaire ` n nœuds. l’arbre binaire sur la gauche de la figure 10 est (∅. e e ıt a e 2. e e e a e 3 Arbres binaires La notion d’arbre binaire est assez diff´rente des d´finitions pr´c´dentes. et on fusionne les arbres a e contenant les extr´mit´s de l’arˆte si ces extr´mit´s appartiennent ` des arbres diff´rents. on a α(n. e 1 2 3 2 3 1 Fig. 3. e e chaque nœud constitue ` lui seul un arbre. Il est e e utile de repr´senter un arbre binaire non vide sous la forme d’un triplet A = (Ag . log2 (n + 1). le 7 au 5. m)). 3. ∅)). 10 – Deux arbres binaires diff´rents. de hauteur h. ∅). Pourtant. .2 Applications de l’algorithme Union-Find Un premier exemple est la construction d’un arbre couvrant un graphe donn´. 1. Au d´part. donc n a 2h+1 − 1. 1. ∅). et d’un arbre binaire appel´ sous-arbre droit. La hauteur d’un arbre binaire est ´gale ` la plus grande des distances des feuilles a e a a ` la racine. e Par exemple. le 6 au 3. En fait. alors que l’arbre sur la droite de la figure 10 est ((∅. e d’un arbre binaire appel´ sous-arbre gauche. l’algorithme pr´c´dent e e e se comporte. d’un point de vue pratique. e Tarjan a montr´ qu’il n’est pas lin´aire et on ne connaˆ pas ` ce jour d’algorithme lin´aire. e e e e e a e Un second exemple concerne les probl`mes de connexion dans un r´seau. Huit ordinateurs sont connect´s ` travers un r´seau. Un arbre binaire e e e e sur un ensemble fini de nœuds est soit vide. Cet exemple montre qu’un arbre binaire n’est pas simplement un arbre ordonn´ dont tous les nœuds sont d’arit´ au plus 2. L’ordinateur 1 est connect´ e e a e e au 3. Est-ce que les ordinateurs 4 et 6 peuvent communiquer ` travers le r´seau ? Certes. Voici un exemple de e e tel probl`me. Comment r´soudre ce probl`me efficacement ? C’est la mˆme e e e solution que pr´c´demment ! On consid`re le r´seau comme un graphe dont les nœuds sont les e e e e ordinateurs. ((∅. Alors h + 1 a Preuve. 2. il n’est pas tr`s difficile de r´soudre a e e e ce probl`me ` la main.

ceux de Ad et la racine. la r´currence e a e s’applique et on a f (Ag ) = n(Ag ) + 1 et f (Ad ) = n(Ad ) + 1. Ensuite. e . et donc n(A) = n(Ag ) + n(Ad ) + 1. L’expression donne aussi bn ∼ π −1/2 · 4n · n−3/2 + O(4n · n−5/2 ). 11 – Les premiers arbres binaires. b3 = 5. Preuve. Fig. r. Comme Ag et Ad sont des arbres complets de hauteur inf´rieure ` celle de A. b2 = 2. Le r´sultat est vrai pour l’arbre binaire de hauteur 0. On montre aussi que. On obtient finalement f (A) = f (Ag ) + f (Ad ) = (n(Ag ) + 1) + (n(Ad ) + 1) = n(A) + 1. Consid´rons un arbre binaire complet e e A = (Ag . Notons bn le nombre d’arbres ` n nœuds. f (A) le nombre de feuilles et n(A) le nombre de nœuds internes de l’arbre binaire complet A. le nombre de feuilles est ´gal au nombre de e nœuds internes. 3 et 4 nœuds. CHAPITRE IV. r.2 Arbres binaires et mots Nous pr´sentons quelques concepts sur les mots qui servent ` plusieurs reprises. il y a un nombre pair de nœuds ` a chaque niveau. Ad ). Ad ). la r´solution de cette ´quation donne e e bn = 1 2n n+1 n = (2n)! n!(n + 1)! Les nombres bn sont connus comme les nombres de Catalan. 2. Il s’agit de montrer que f (A) = n(A) + 1. 3. 3. Les feuilles de A sont celles de Ag et de Ad et donc f (A) = f (Ag ) + f (Ad ). plus 1. ils e seront employ´s dans des algorithmes de compression.1 Compter les arbres binaires La figure 11 montre les arbres binaires ayant 1. b4 = 14. D’abord. Comme les bn sont positifs. dans un arbre binaire complet. a Comme tout arbre A non vide s’´crit de mani`re unique sous forme d’un triplet (Ag . on e e a pour n 1 la formule n−1 bn = i=0 bi bn−i−1 La s´rie g´n´ratrice B(x) = e e e n n 0 bn x v´rifie donc l’´quation e e xB 2 (x) − B(x) + 1 = 0 . pour simplifier. Les nœuds internes de A sont ceux de Ag . On a donc b0 = b1 = 1. ils e a mettent en ´vidence des liens entre les parcours d’arbres binaires et certains ordres. sauf au niveau de la racine. ARBRES Proposition 5 Dans un arbre binaire complet.88 Un arbre binaire est complet si tout nœud a 0 ou 2 fils. Notons.

1.2 Ordres sur les mots Un ordre total sur l’alphabet s’´tend en un ordre total sur l’ensemble des mots de multiples e mani`res. comme 0110 ou abracadabra. Le code c(A) d’un arbre binaire A se d´finit d’ailleurs simplement par r´currence : on a e e c(∅) = {ε} et si A = (Ag . c. comme {0. o` p est un mot. r. v = pbv ′ . f ) d’un arbre A binaire est ´tiquet´e par 0 si f est fils gauche de p. 1} ou {a. 00. Le code de l’arbre A est l’ensemble des ´tiquettes des chemins issus de la e e e racine.2. Un mot est une suite de lettres. L’´tiquette du chemin qui m`ne de la racine a un nœud est le mot form´ des e e ` e ´tiquettes de ses arˆtes. alors e e uv = abracadabra. et les parcours de droite ` gauche. 11. 110}. bijective.1 Mots 89 Un alphabet est un ensemble de lettres. |0110| = 4 et |abracadabra| = 11. abr. not´e |u|. d´termin´ par leur ordre d’apparition dans cette ´num´ration. l’ordre lexicographique et l’ordre des mots crois´s ((( rae e e dix order )) ou (( shortlex )) en anglais). 001} sont pr´fixiels. 0. ou ordre du dictionnaire. e 3. e e e e Le produit de concat´nation est not´ comme un produit.3 Parcours d’arbre Un parcours d’arbre est une ´num´ration des nœuds de l’arbre. L’ordre lexicographique. est d´fini par u <lex v si seulement si u e ′ . tout ensemble fini pr´fixiel de e e e mots form´s de 0 et 1 est le code d’un arbre binaire. abrac sont des pr´fixes propres de abraca. et a < b. 1. 10. 010. r}. 10. ARBRES BINAIRES 3.2. 0. 11} et {ε. Si u = abra et v = cadabra. La correspondance est. Ad ).2. on a bar <mc car <mc barda <mc radar <mc abracadabra 3. de longueur 0. le fils gauche d’un nœud pr´c`de le fils droit (et vice-versa pour un a e e parcours de droite ` gauche). Etant e donn´s deux mots. de plus. 01. 00. est le nombre de lettres e de u : ainsi. d. 01. ε. les ensembles e e e {ε. e 0 0 0 1 0 1 1 1 0 Fig. Chaque parcours d´finit un e e e ordre sur les nœuds. L’ordre des mots crois´s est d´fini par u <mc v si et seulement si |u| < |v| ou e e |u| = |v| et u <lex v.3. Le mot vide. Ainsi. Nous consid´rons deux ordres. Un mot p est pr´fixe (propre) d’un mot u s’il existe un mot v (non vide) e tel que u = pv. 101. Ensuite. alors c(A) = 0c(Ag ) ∪ {ε} ∪ 1c(Ad ). La longueur d’un mot u. Par exemple. 12 – Le code de l’arbre est {ε. le mot obtenu par concat´nation est le mot form´ des deux mots juxtapos´s. 3.3 Codage des arbres binaires Chaque arˆte (p. e e e e On distingue les parcours de gauche ` droite. et par e e e 1 si f est fils droit. Dans un a a parcours de gauche ` droite. on distingue les parcours en profondeur et en largeur. est not´ ε. a . Par exemple. 000. Un ensemble de mots e P est pr´fixiel si tout pr´fixe d’un mot de P est lui-mˆme dans P . b. et r´ciproquement. Cet ensemble est clairement pr´fixiel. a et b sont des est pr´fixe de v ou u et v peuvent s’´crire u = pau e e u lettres.

Gauche. c. e e a Le parcours suffixe correspond ` l’oppos´ de l’ordre lexicographique si l’on convient que 1 < 0. f. sont en ordre croissant pour l’ordre des mots crois´s. suivi de 1. e. 110. h. e e e e R`gle. 01. g. ´crit en binaire. On d´finit trois parcours en profondeur privil´gi´s qui sont e e e le parcours pr´fixe : tout nœud est suivi des nœuds de son sous-arbre gauche puis des e nœuds de son sous-arbre droit. g et le parcours suffixe donne d. ε. k. b. en abr´g´ NGD (Nœud. e. Le parcours pr´fixe d’un arbre correspond ` l’ordre lexicographique sur le code de l’arbre. Nœud. d. Pour l’arbre de la figure 13. h. on associe un nombre form´ du code du chemin e menant ` x. Si A est l’arbre vide. e e le parcours suffixe. h. 1. 13 – Un arbre binaire. h. i. Droite). et e(r) est le nom de r. les nombres obtenus sont e . i. suffixe) sont d´finis comme suit. mais elle est un peu e ` plus astucieuse ! A chaque nœud x de l’arbre. b. Ad ). e. i. k. c. ARBRES Le parcours en largeur ´num`re les nœuds niveau par niveau. si A = (Ag . a e Qu’on se rassure. Ce code compl´t´ est interpr´t´ comme la partie fractionnaire d’un a ee ee nombre entre 0 et 1. a 0 b 0 d 0 h 1 e f 1 i 0 k 0 1 c 1 g Fig. g. Nœud). Droite. alors e e pref(A) = inf(A) = suff(A) = ε . k. e Le parcours pr´fixe donne les nœuds dans l’ordre a. On remarque que les codes des nœuds e correspondants. 10. en abr´g´ GND (Gauche. b. a. k. e e le parcours infixe : tout nœud est pr´c´d´ des nœuds de son sous-arbre gauche et suivi des e e e nœuds de son sous-arbre droit. le parcours en largeur e e de l’arbre 13 donne la s´quence a. c. f. e e Les ordres correspondant sont appel´s ordres pr´fixe. Consid´rons l’arbre de la e e e figure 13. alors pref(A) = e(r)pref(Ag )pref(Ad ) inf(A) = inf(Ag )e(r)inf(Ad ) suff(A) = suff(Ag )suff(Ad )e(r) R`gle. infixe et suffixe. De mani`re e formelle. Droite). L’ordre du parcours en largeur correspond ` l’ordre des mots crois´s sur le code de e a e l’arbre. r. 101. il y a aussi une interpr´tation pour le parcours infixe. les parcours pr´fixe (infixe. Ainsi. 010. d. g. f. i. 0. le parcours infixe e donne la suite d. e. C’est en fait une r`gle g´n´rale. 11. c. Les nœuds sont nomm´s par des lettres. b. ou postfixe : tout nœud est pr´c´d´ des nœuds de son sous-arbre gauche e e e puis des nœuds de son sous-arbre droit. 00. f. a. en abr´g´ GDN (Gauche.90 CHAPITRE IV.

Le fils gauche e e correspond ` une r´ponse n´gative. 3.1011 = 11/16 k . et d’autres en e a O(n log n) comme le tri fusion ou le tri par tas. Le mod`le utilis´ pour repr´senter ee a a e e e un calcul est un arbre de d´cision. 3) a2 > a3 (3. par insertion ou ` bulles.0101 = 5/16 i . e Les feuilles repr´sentent les permutations des ´l´ments ` effectuer pour obtenir la s´quence e ee a e tri´e.4 Une borne inf´rieure pour les tris par comparaisons e Voici une application surprenante des arbres ` l’analyse de complexit´.1101 = 13/16 L’ordre induit sur les mots est appel´ l’ordre fractionnaire. 1. e 91 3. il faut bien pr´ciser le mod`le de calcul que l’on consid`re. d’autres en O(n3/2 ) comme le tri Shell. Chaque nœud pose une question. e e Avant de r´soudre cette question. e R`gle. e e e e Un tri par comparaison est un algorithme qui trie en n’utilisant que des comparaisons. On peut se demander s’il est possible de trouver un algorithme de tri de complexit´ inf´rieure dans le pire des cas. certains dont la complexit´ dans le pire des cas est en O(n2 ) comme les tris e par s´lection. ARBRES BINAIRES donn´s dans la table suivante e a . On peut supposer que les ´l´ments ` trier sont deux-`-deux distincts.3.11 = 3/4 d . ee . 1) (1. L’ordre infixe correspond ` l’ordre fractionnaire sur le code de l’arbre. a e e a e a1 > a2 a2 > a3 (1.1 = 1/2 b . 1) Fig. 3.001 = 1/8 e . a Nous prouvons ici : Th´or`me 6 Tout algorithme de tri par comparaison effectue Ω(n log n) comparaisons dans le e e pire des cas pour trier une suite de n ´l´ments. Le nombre de comparaisons ` effectuer pour d´terminer cette permutation est ´gale ` la e a e e a longueur du chemin de la racine ` la feuille.101 = 5/8 g .111 = 7/8 h . 14 – Exemple d’arbre de d´cision pour le tri. 1. 2) (3. 2) (2. 3) a1 > a3 a1 > a3 (2. Il existe de nombreux a e algorithmes de tri. e a La programmation de ces parcours sera donn´e au chapitre V.011 = 3/8 f . le fils droit ` une r´ponse positive (figure 14). 2. 2.01 = 1/4 c . que nous verrons page 100. Chaque comparaison entre ´l´ments d’une s´quence ` trier e ee e a est repr´sent´e par un nœud interne de l’arbre.

E) ∪ (E. Seconde remarque. Il voit une d´finition inductive. -. +. * et /). Une fois ce travail accompli. e L’œil neuf ne voit pas cette d´finition comme celle de l’´criture usuelle (notation infixe) des e e expressions. etc. il faut ´crire 1+(2*3) et (1+2)*3. Par exemple. Nous avons en effet e utilis´ implicitement une information suppl´mentaire : toutes les valeurs ` trier appartiennent ` e e a a l’intervalle [0. ou bien une op´ration e1 op e2 . La hauteur de l’arbre est donc minor´e par log(n!). e . les expressions sont des feuilles qui contiennent e e un entier ou des nœuds internes ` deux fils. le tri est termin´ : il y a T[0] notes e ´gales ` 0. /. E) ∪ (E. il ne contredit pas notre r´sultat. o` e1 et e2 sont des expressions arithm´tiques et op est un e u e op´rateur (+. ARBRES Preuve. Deux pr´cisions pour clore cette parenth`se sur les tris. Or QuickSort est un algorithme en O(n log n) en moyenne. Cet exemple montre qu’il faut bien r´fl´chir aux conditions particuli`res avant e e e de choisir un algorithme. suivi de T[1] notes ´gales ` 1. l’ensemble e e des expressions est solution de cette ´quation r´cursive : e e E = Z ∪ (E. 15 – Deux arbres de syntaxe abstraite + 1 2 * 3 1 + 2 * 3 pour bien se faire comprendre. est le plus efficace en pratique. Supposons par exemple que l’on veuille classer les e notes (des entiers entre 0 et 20) provenant d’un paquet de 400 copies. et d’ailleurs il manque les parenth`ses. Il suffit alors de lire les notes une par une et d’incr´menter e a e le compteur correspondant. on constate exp´rimentalement que l’algorithme de tri rapide (QuickSort). La fa¸on la plus simple c et la plus efficace consiste ` utiliser un tableau T de taille 21. e dont la complexit´ dans le pire des cas est en O(n2 ). 20].92 CHAPITRE IV. les deux arbres de la figure 15 disent clairement ıt´ quels sont les arguments des op´rations + et * dans les deux cas. Tout arbre de d´cision pour trier n ´l´ments a n! feuilles. Tout d’abord. E) ∪ (E. Or log(n!) = O(n log n) e par la formule de Stirling. *. dont chaque entr´e T[i] sert a e a ` compter les notes ´gales ` i. e Fig. Une expression e e e arithm´tique e est : e un entier. 4 4.1 Arbres de syntaxe abstraite Les expressions sont des arbres Consid´rons une d´finition des expressions arithm´tiques avec un œil neuf. -. Cet algorithme est manifestement lin´aire et e a e a e ne fait aucune comparaison ! Pourtant. E) Cette d´finition inductive est une d´finition d’arbre. repr´sentant toutes les pere ee e mutations possibles. Alors qu’en notation infixe. le r´sultat pr´c´dent e e e e e n’est plus garanti si l’on change de mod`le. Voir une expression comme un arbre ´vite toutes les a e ambigu¨ es de la notation infixe. Comment e est-ce possible ? Tout simplement parce que notre r´sultat ne concerne que la complexit´ dans e e le pire des cas.

4. se construit par : ea add(mkInt(1).e1 = e1 . } Et l’expression d´j` vue. on ´crit : e new Exp (new Exp(1). Un exemple d’op´ration (( compliqu´e )) sur les expressions arithm´tiques est le calcul de e e e leur valeur. il faut repr´senter ces expressions par des arbres de syntaxe abstraite. La technique d’impl´mentation la plus simple est de r´aliser tous ces nœuds par e e e des objets d’une seule classe Exp qui ont tous les champs n´cessaires. 1 Une technique plus ´l´gante ` base d’h´ritage des objets est possible. DIV=4 . SUB=2. DIV.2 Impl´mentation des arbres de syntaxe abstraite e ´ Ecrivons une classe Exp des cellules d’arbre des expressions. e2) . mais aussi source d’erreurs. Exp(int i) { tag = INT . Exp e2) { tag = op . 4. ` e e c’est-`-dire ici la notation infixe. mul(mkInt(2). MUL.e2 = e2 . int tag . MUL. this.1 Le champ tag contient un entier cens´ ˆtre l’une de cinq constantes ee conventionnelles. Exp e2) { return new Exp (e1. // Utilis´ si tag == INT e int asInt . static Exp div(Exp e1. } . Le terme e e (( abstraite )) se justifie par opposition a la syntaxe (( concr`te )) qui est l’´criture des expressions. plus un champ tag qui indique la nature du nœud. On atteint ici la limite de ce qu’autorise la surcharge des constructeurs. La production des arbres de syntaxes abstraite ` partir de la a a syntaxe concr`te est l’analyse grammaticale (parsing). } } Ainsi pour construire l’arbre de gauche de la figure 15. } Exp(Exp e1. this. DIV} e Exp e1. . SUB. L’op´ration n’est compliqu´e que si nous essayons de l’effectuer directement sur les e e e notations infixes. ee a e . mkInt(3))) Ce qui est plus concis. qui e ont deux fils. ADD. class Exp { final static int INT=0. new Exp (new Exp(2). et les quatre op´rations. MUL=3. Exp e2) { return new Exp (e1. ADD=1. e2) . int op. ADD. Nous devons principalement distinguer cinq sortes de nœuds. ARBRES DE SYNTAXE ABSTRAITE 93 D`s qu’un programme doit faire des choses un tant soit peu compliqu´es avec les expressions e e arithm´tiques. // Utilis´s si tag ∈ {ADD. sinon plus clair. Les entiers. car sur un arbre Exp c’est tr`s facile. . static Exp mkInt(int i) { return new Exp (i) . qui sont des feuilles. une question cruciale qui est ´tudi´e dans e e e le cours suivant INF 431. new Exp(3))) C’est non seulement assez lourd. Il est plus commode de d´finir cinq m´thodes statiques e e pour construire les divers nœuds. asInt = i . } static Exp add(Exp e1. e2 .

} . case SUB: return calc(e. String cmd = arg[k] . e 4. stack. } else i f (cmd.e1) * calc(e. en construisant un arbre au lieu de calculer e une valeur.push(sub(e1. on aurait aussi pu e renvoyer une valeur (( bidon )) par return 0.asInt . } } return stack. i f (cmd. } else i f (cmd.pop() . e1 = stack.e1) . e ee L’id´e est d’abord d’interpr´ter la notation postfixe comme un arbre. } CHAPITRE IV. en ne produisant que des notations ea e c e infixes compl`tement parenth´s´es (exercice II. il faut surtout remarquer le lien tr`s fort entre la d´finition e e inductive de l’arbre et la structure r´cursive de la m´thode.equals("*")) { e2 = stack.e2)) .length .e1) + calc(e. case DIV: return calc(e.e2) . puis d’afficher cet arbre. ce qui est facile avec la classe des piles de la biblioth`que (voir II. e1 = stack. ARBRES L’instruction throw finale est n´cessaire.e2)) .2).94 static int calc(Exp e) { switch (e. La programmation sur les arbres de e e syntaxe abstraite est naturellement r´cursive.pop() . e e Pour la premi`re op´ration il ne faut se poser aucune question.parseInt(arg[k]))) .e2) . le programme est rejet´ par le compilateur.pop() .equals("+")) { e2 = stack. case MUL: return calc(e. Une erreur e est une erreur. } throw new Error ("calc : arbre Exp incorrect") . stack. e e en tenant compte des r`gles usuelles qui permettent de ne pas mettre toutes les parenth`ses. car le compilateur n’a pas de moyen de savoir que le e champ tag contient obligatoirement l’une des cinq constantes conventionnelles.pop() . Nous avons donc besoin d’une pile d’arbres. e2 . e static Exp postfixToExp(String [] arg) { Stack<Exp> stack = new Stack<Exp> () . } else { stack.e1) / calc(e. e1 = stack. } else i f (cmd. En son absence.e2)) . case ADD: return calc(e.2).push(mkInt(Integer. mieux vaut tout arrˆter que de faire semblant de rien.e2) .push(add(e1.1. e Dans cet exemple typique. mais c’est nettement moins conseill´.pop() .pop() .2.equals("/")) { e2 = stack.push(mul(e1.pop() .equals("-")) { e2 = stack.push(div(e1.pop() . for (int k = 0 .3). k < arg. Nous pouvons maintenant faire mieux.e2)) .tag) { case INT: return e. k++) { Exp e1. nous reprenons le calcul des e e expressions donn´es en notation postfixe (voir II.calc(e. stack. stack.e2) . e1 = stack.3 Traduction de la notation postfixe vers la notation infixe Nous avons d´j` trait´ cette question de fa¸on incompl`te.pop() . en cas d’arbre incorrect. Pour satisfaire le compilateur.

expToInfix(out. Par exemple e. Exp e. case MUL: case DIV: i f (lvl > 2) out. Le code utilise une particularit´ de l’instruction switch : e on peut grouper les cas (ici des additifs et des multiplicatifs).7. il faut donc pae e renth´ser 1-(2+3)). 2) . Il y a cinq positions possibles : au e e a e sommet de l’arbre. les parenth`ses autour d’un entier ne sont jamais utiles. on a ree cours ` l’expression conditionnelle (voir B.e1.print(’(’) .e2. e. return . les additifs (+ et -) et les multiplicatifs (* et /). e Ceci nous conduit ` regrouper les positions possible en trois classes a (1) Sommet de l’arbre et ` gauche des additifs : ne rien parenth´ser. et ` gauche ou ` droite d’un op´rateur additif ou multiplicatif. 2 et 3.tag) { case INT: out. out.5. On voit alors que les additifs sont ` parenth´ser a e pour les classes strictement sup´rieures ` 1. return .5. Cela ne pose pas de difficult´ partie e e e culi`re : pour afficher une op´ration il faut d’abord afficher le premier argument (r´cursion) puis e e e l’op´rateur et enfin le second argument (r´cursion encore). a static void expToInfix(PrintWriter out. } throw new Error ("expToInfix : arbre Exp incorrect") . e.tag == MUL ? ’*’ : ’/’) . i f (lvl > 2) out. e. out. expToInfix(out.tag est ´gal ` ADD et ’-’ autrement — et ici (( autrement )) signifie n´cessairement que e a e e. 1) . int lvl) { switch (e. e e La sortie est un PrintWriter qui poss`de une m´thode print exactement comme System. et les multiplicatifs pour les classes strictement e a sup´rieures ` 2.print(e. Ensuite.e2.print(e. i f (lvl > 1) out. les op´rateurs d’une classe e e donn´e ont le mˆme comportement vis ` vis du parenth´sage. a e ` (2) A droite des additifs et ` gauche des multiplicatifs : ne parenth´ser que les additifs.tag == ADD ? ’+’ : ’-’) .1).print(e.e1.tag == ADD ? ’+’ : ’-’ vaut a ’+’ si e. expToInfix(out. 3) . } La m´thode expToInfix m´lange r´cursion et affichage.print(’)’) . L’application des op´rateurs multiplicatifs doit ˆtre parenth´s´e ` droite des op´rateurs e e ee a e multiplicatifs (mˆme raisonnement que pour les additifs). Ce qui conduit directement ` la m´thode suivante qui prend en dernier argument e a a e un entier lvl qui rend compte de la position de l’arbre e ` afficher dans la sortie out. On examine a a e ensuite l’´ventuel parenth´sage d’un op´rateur. ARBRES DE SYNTAXE ABSTRAITE 95 Examinons la question d’afficher un arbre Exp sous forme infixe sans abuser des parenth`ses.tag est ´gal ` SUB puisque nous sommes dans un cas regroup´ du switch ne concernant que e a e ADD et SUB.print(’(’) .asInt) . e e e L’application des op´rateurs additifs doit ˆtre parenth´s´e quand elle apparaˆ comme see e ee ıt cond argument d’un op´rateur additif (1-2+3 s’interpr`te comme (1-2)+3.2).out e e mais est bien plus efficace (voir B. (3) A e e On identifie les trois classes par 1.print(’)’) . . Pour afficher l’op´rateur. ou comme argument d’un op´rateur multiplicatif (consid´rer (1+2)*3 e e e et 1*(2+3)). case ADD: case SUB: i f (lvl > 1) out. expToInfix(out. e Tout d’abord. 2) . on distingue deux e classes d’op´rateurs. a e ` droite des multiplicatifs : parenth´ser tous les op´rateurs. return .4. e.

ordonn´s ou non. out. il faut vider le tampon par out. Plusieurs implantations d’une file de priorit´ sont envisageables : par tableau ou e e par liste. L’implantation d’une file de priorit´ est un exemple e e d’utilisation d’arbre. le coˆt de la suppression dans une liste non ordonn´e est calcul´ en u e e supposant l’´l´ment d´j` trouv´. ee ea e Tab.println() . et ce dernier niveau est rempli (( ` gauche )). e e En d’autres termes.4. De mani`re plus formelle. e voir B. 1) . Le passage devant le guichet.1 Tas Un arbre binaire est tass´ si son code est un segment initial pour l’ordre des mots crois´s. e . La table 1 pr´sente la complexit´ des e e e op´rations des files de priorit´s selon la structure de donn´es choisie. ee e ee se fait en fonction de son niveau de priorit´. expToInfix(out. dans un tel arbre.96 CHAPITRE IV. e e 5. Les files de priorit´ sont des files d’attente o` les ea e e u ´l´ments ont un niveau de priorit´. Nous allons utiliser des tas.1 + / 9 6 .5.2. La figure 16 montre un e a arbre tass´. % java Exp 6 3 2 . } Les PrintWriter sont bufferis´s. Reprenons l’exemple de la figure II. une file de priorit´ est un type abstrait de donn´es op´rant sur un e e e e ensemble ordonn´. e.’*’ 6/(3-2+1)*(9-6) Ce qui est meilleur que l’affichage ((6/((3-2)+1))*(9-6)) de l’exercice II.flush() . on peut remplacer (( le plus grand ´l´ment )) par (( le plus petit ´l´ment )) en prenant u ee ee l’ordre oppos´.4. tous les niveaux sont enti`rement remplis ` l’exception e a peut-ˆtre du dernier niveau.out) . out. ou le traitement de l’´l´ment. 5 Files de priorit´ e Nous avons d´j` rencontr´ les files d’attente. et muni des op´rations suivantes : e e trouver le plus grand ´l´ment ee ins´rer un ´l´ment e ee retirer le plus grand ´l´ment ee Bien sˆr. ARBRES Voici finalement la m´thode main de la classe Exp qui appelle l’affichage infixe sur l’arbre e construit en lisant la notation postfixe public static void main (String [] arg) { PrintWriter out = new PrintWriter (System. 1 – Complexit´ des implantations de files de priorit´.flush() avant de finir. e e e Implantation Tableau non ordonn´ e Liste non ordonn´e e Tableau ordonn´ e Liste ordonn´e e Tas a Trouver max O(n) O(n) O(1) O(1) O(1) Ins´rer e O(1) O(1) O(n) O(n) O(log n) Retirer max O(n) O(1)a O(1) O(1) O(log n) Dans cette table. et c’est pourquoi elle trouve naturellement sa place ici. Exp e = postfixToExp(arg) .

5. Ceci entraˆ par transitivit´. avec la num´rotation de ses nœuds. 16 – Un arbre tass´. ea 23 15 12 4 8 2 Fig.´ 5. 0 23 1 15 3 12 7 4 8 8 9 2 4 5 5 6 2 7 6 1 Fig.2 Implantation d’un tas Un tas s’implante facilement ` l’aide d’un simple tableau. e e a L’arbre de la figure 16 est un tas. que le contenu de chaque e e a ıne. e nœud est sup´rieur ou ´gal ` celui de ses descendants. le ee e tableau est : i ai 0 23 1 15 2 7 3 12 4 5 5 6 6 1 7 4 8 8 9 2 Le fait que l’arbre soit tass´ conduit ` un calcul tr`s simple des relations de filiation dans l’arbre e a e (n est le nombre de ses nœuds) : racine parent du nœud i fils gauche du nœud i fils droit du nœud i nœud i est une feuille nœud i a un fils droit : : : : : : nœud 0 nœud ⌊(i − 1)/2⌋ nœud 2i + 1 nœud 2i + 2 2i + 1 n 2i + 2 < n . Ces num´ros sont des indices dans un tableau (cf e e a e figure 17). e 5 6 7 1 97 Un tas (en anglais (( heap ))) est un arbre binaire tass´ tel que le contenu de chaque nœud e soit sup´rieur ou ´gal ` celui de ses fils. 17 – Un arbre tass´. Dans notre exemple. e e L’´l´ment d’indice i du tableau est le contenu du nœud de num´ro i. de gauche ` droite. FILES DE PRIORITE Proposition 7 La hauteur d’un arbre tass´ ` n nœuds est ⌊log2 n⌋. Les nœuds d’un arbre tass´ sont a e num´rot´s en largeur.

Un tas est naturellement pr´sent´ comme une classe. Tant que le contenu e e ` du p`re est plus petit que v. l’´l´ment est ajout´ comme ee ee e contenu d’un nouveau nœud ` la fin du dernier niveau de l’arbre. On range les donn´es dans un tableau interne. e a e e On continue ensuite avec le fils. e e e elle. 18 – Un tas. a e Ensuite. Un constructeur e permet de faire l’initialisation n´cessaire. le contenu de ce nœud. le contenu v de la racine est compar´ ` la plus grande des valeurs de ses fils e ea (s’il en a). le contenu du p`re est descendu vers le fils. avec remont´e de la valeur 21 apr`s insertion. A la fin. elle est remont´e et remplace le contenu du p`re. le contenu du nœud le plus ` droite du e a dernier niveau est transf´r´ vers la racine. on remplace e e par v le dernier contenu abaiss´ (voir figure 18). et enfin au tas de la figure 20 a 0 16 1 15 3 8 7 2 8 4 9 7 4 14 10 10 5 9 2 11 6 3 7 2 3 8 8 4 9 7 1 15 4 14 5 9 0 10 2 11 6 3 Fig. Si cette valeur est sup´rieure ` v. Par exemple.98 CHAPITRE IV. ARBRES L’insertion d’un nouvel ´l´ment v se fait en deux temps : d’abord. soit v. e e La suppression se fait de mani`re similaire. La complexit´ de chacune de ces op´rations est major´e par la hauteur de l’arbre qui est. e 0 23 1 15 3 12 7 4 8 8 9 2 4 5 10 21 5 6 2 7 6 1 Fig. supprimer(). et la circulation des valeurs pendant la suppression de 16. Voici le squelette : e class Tas { int[] a. pour que l’arbre reste tass´. et ce nœud est supprim´. . 19 – Un tas. inserer(). D’abord. fournissant les trois m´thodes maxie e e mum(). Ensuite. int nTas = 0. Ceci garantit que l’arbre ee e reste tass´. est compar´ au contenu de son p`re. logarithmique en la taille. la suppression de 16 dans l’arbre de gauche de la figure 19 conduit d’abord ` l’arbre de droite de cette figure.

} De mˆme pour la suppression : e void supprimer() { --nTas. FILES DE PRIORITE 0 15 1 14 3 8 7 2 8 4 9 7 4 10 5 9 2 11 6 3 99 Fig. while (!estRacine(i) && cle(parent(i)) < v) { cle(i) = cle(parent(i)). int i = 0.. } cle(i) = v. e e void ajouter(int v) { int i = nTas. cle(0) = cle(nTas).} Avant de donner l’implantation finale.} void supprimer() {.. e Tas(int n) { nTas = 0. 20 – Le tas de la figure 19 apr`s suppression de 16. int v = cle(0). } void ajouter(int v) {. if (existeFilsD(i) && cle(filsD(i)) > cle(filsG(i))) . a = new int[n]. voici une premi`re version ` l’aide de m´thodes qui e a e refl`tent les op´rations de base.. while (!estFeuille(i)) { int j = filsG(i). ++nTas. } int maximum() { return a[0].´ 5. i = parent(i)..

while (i > 0 && a[(i-1)/2] <= v) { a[i] = a[(i-1)/2]. void ajouter(int v) { int i = nTas. int i = 0. la complexit´ de la m´thode supprimer est en O(log n).length. a e e On peut se servir d’un tas pour trier : on ins`re les ´l´ments ` trier dans le tas. Ceci donne une m´thode de tri appel´e tri par tas ((( heapsort )) en anglais). if (j +1 < nTas && a[j+1] > a[j]) ++j. } On notera que. i < n. puisque la hauteur d’un tas ` n nœuds est ⌊log2 n⌋.1. } a[i] = v. if (v >= cle(j)) break. i = j. } } L` encore. cle(i) = cle(j). i = (i-1)/2. i = j. } CHAPITRE IV. ARBRES Il ne reste plus qu’` remplacer ce pseudo-code par les instructions op´rant directement sur le a e tableau. for (int i = n . } cle(i) = v. Tas t = new Tas(n). le nombre de comparaisons a utilis´e par la m´thode ajouter est en O(log n). ++nTas. a[i] = a[j]. if (v >= a[j]) break. i >= 0. e e void supprimer() { int v = a[0] = a[--nTas]. while (2*i + 1 < nTas) { int j = 2*i + 1. i++) t. puis on les e ee a extrait un par un.100 j = filsD(i). for (int i = 0. e e static int[] triParTas(int[] a) { int n = a. --i) . } a[i] = v.ajouter(a[i]).

e e ee .3 Arbres de s´lection e Une variante des arbres tass´s sert ` la fusion de listes ordonn´es.´ 5. e e ` Un algorithme (( na¨ )) op`re de mani`re la suivante. c’est-`-dire ` la mise en place de la structure particuli`re. l’une e ea des k listes ` fusionner (voir figure 21). puis on cherche le maxiee mum de ces nombres. a[i] = v. e e ıt La solution que nous proposons est bas´e sur un tas qui m´morise partiellement l’ordre. on appelle n fois chacune e des m´thodes ajouter et supprimer. A chaque ´tape. } 101 La complexit´ de ce tri est. e 5. en O(n log n). t. le maximum des cl´s de ses deux fils (ou le plus grand ´l´ment d’une liste). e ıf e Il semble plausible que l’on puisse gagner du temps en (( m´morisant )) les comparaisons que e l’on a faites lors du calcul d’un maximum. Ce nombre est ins´r´ dans la liste r´sultat. Cette situation se pr´sente par exemple lorsqu’on veut fusionner des e donn´es provenant de capteurs diff´rents. Chaque nœud de l’arbre a 98 49 49 18 98 98 89 38 32 25 12 9 49 45 23 21 11 6 18 12 9 1 9 8 4 3 2 98 85 78 65 52 36 49 35 31 25 21 13 58 53 42 26 10 89 67 55 44 32 13 Fig. Le premier terme correspond au (( pr´traitement )). On est en pr´sence de k e a e e suites de nombres ordonn´es de fa¸on d´croissante (ou croissante). La recherche du maximum de k ´l´ments coˆte k − 1 comparaisons. on consid`re le premier ıf e e e e ´l´ment de chacune des k listes (c’est le plus grand dans chaque liste). Chaque feuille est en fait une liste. En effet.supprimer(). lorsque l’on calcule le maximum suivant. et on veut les fusionner en e c e une seule suite croissante. e a a e L’arbre de s´lection est un arbre tass´ ` k feuilles. comme cl´. et supprim´ de la liste dont il ee e e provient. On e e verra que l’on peut effectuer la fusion en temps O(k + n log k). seule une donn´e sur les k donn´es compar´es a chang´. Si la somme des ee u longueurs des k listes de donn´es est n. e contient. l’algorithme na¨ est donc de complexit´ O(nk). FILES DE PRIORITE { int v = t. dans le pire des cas. } return a. En effet. L’ordre des k −1 autres ´l´ments reste le e e e e ee mˆme.maximum(). et on peut ´conomiser des comparaisons si l’on connaˆ cet ordre au moins partiellement. 21 – Un arbre de s´lection pour 8 listes. La hauteur de l’arbre est log k.

Le codage arithm´tique e e e a e repose aussi sur la fr´quence des lettres. et la compression avec perte. on distingue plusieurs cat´gories : les algoe rithmes statistiques codent les caract`res fr´quents par des codes courts. on trouve le nouveau maximum (voir figure 22). avec la e e ` nouvelle valeur du fils mis-`-jour. il existe des algorithmes (( num´riques )). pour la compression d’images et de sons. dans la liste. ARBRES En particulier. e ee temps k. a La mise-`-jour se fait donc en O(log k) op´rations. L’algorithme de Huffman est un algorithme statistique. le valeur de la cl´. 6 6. Ces algorithmes seront ´tudi´s dans le cours 431 ou en e e e majeure. par son suivant.102 CHAPITRE IV. on aboutit ` la liste dont la valeur e a de tˆte est ce maximum. Ensuite. et si une chaˆ apparaˆ une deuxi`me fois. ou si elle est automatiquement corrig´e par le r´cepteur. La compression sans perte est importante par exemple e dans la compression de textes. o` les donn´es d´compress´es sont identiques aux donn´es u e e e e de d´part. Enfin. En e e revanche. La mise en place initiale de l’arbre est en a e 89 49 49 18 85 89 89 38 32 25 12 9 49 45 23 21 11 6 18 12 9 1 9 8 4 3 2 85 78 65 52 36 49 35 31 25 21 13 58 53 42 26 10 89 67 55 44 32 13 Fig. A la racine. sur les nœuds d’un chemin menant ` la racine. Cette extraction est suivie d’un mise-`-jour : ee a En descendant le chemin dont les cl´s portent le maximum. elle est remplac´e e e ıne ıt e e par son indice dans la table.1 Codage de Huffman Compression des donn´es e La compression des donn´es est un probl`me algorithmique aussi important que le tri. L’´l´ment est remplac´. On e e distingue la compression sans perte. 22 – Apr`s extraction du plus grand ´l´ment et recalcul. Le e e codage arithm´tique remplace un texte par un nombre r´el entre 0 et 1 dont les chiffres en e e ´criture binaire peuvent ˆtre calcul´s au fur et ` mesure du codage. on e ee e remonte vers la racine en recalculant. le maximum des ´l´ments en tˆte des k listes (en gris´ sur la figure 21) se trouve ee e e log k fois dans l’arbre. une perte peut ˆtre accept´e si elle n’est pas e e perceptible. et les caract`res rares e e e par des codes longs . L’algorithme de Ziv-Lempel est bas´ sur un dictionnaire. les algorithmes bas´s sur les dictionnaires enregistrent toutes les chaˆ e ınes de caract`res trouv´es dans une table. de donn´es scientifiques ou du code compil´ de programmes. . L’extraction d’un plus a grand ´l´ment se fait donc en temps constant. e e Parmi les algorithmes de compression sans perte. pour chaque nœud rencontr´.

pr´sent´ bri`vement dans la e e e e section 6. 23 – Le code des feuilles est {00. 101. 010. Le code des feuilles est pr´fixe. l’ensemble {0. Dans ce cas. La figure 23 reprend l’arbre de la figure 12. Un code pr´fixe P e e est complet si tout mot est pr´fixe d’un produit de mots de P . ee e Proposition 8 Un ensemble fini de mots sur {0.1 Codes pr´fixes et arbres e Un ensemble P de mots non vides est un code pr´fixe si aucun des mots de P n’est pr´fixe e e propre d’un autre mot de P . les fr´quences sont mises ` jour apr`s chaque compression de caract`re. . 110}. au sens d´crit c a e e e ci-dessous. La version du codage de Huffman que nous d´taillons dans cette section est le codage e dit (( statique )) : les fr´quences n’´voluent pas au cours de la compression. 1} est un code pr´fixe complet si et seulement e s’il est le code des feuilles d’un arbre binaire complet.2 Algorithme de Huffman L’algorithme de Huffman est un algorithme statistique. on choisit un code pr´fixe. 0 0 0 1 0 1 1 1 0 Fig. e a e e pour chaque fois optimiser le codage.3. 101. 1100.2. Le choix de ces codes se fait d’une part en fonction des fr´quences e d’apparition de chaque lettre. Rappelons qu’un arbre binaire est dit complet si tous ses nœuds internes ont arit´ 2. En revanche. ou dans les fax. Une version plus sophistiqu´e est le codage dit (( adaptatif )). Par exemple. Les caract`res du texte clair sont e cod´s par des chaˆ e ınes de bits. e e C’est ce qui se passe dans les modems. et le code reste fixe. 100. Pour illustrer ce fait. CODAGE DE HUFFMAN 103 6. prenons le mot 1011010101011110110011 : il est pr´fixe du e produit 101 · 101 · 0 · 101 · 0 · 111 · 10 · 1100 · 111 L’int´rˆt de ces d´finitions vient des propositions suivantes. 6.6. Pour cela. 1} est un code pr´fixe si et seulement s’il est e le code des feuilles d’un arbre binaire. le code des feuilles de l’arbre de la figure 24 est complet. mais e aussi de fa¸on ` rendre le d´codage facile. Le code de l’exemple ci-dessus e est complet. 111. mais il n’est pas e complet. de sorte que les lettres fr´quentes aient des codes courts. e Proposition 9 Un ensemble fini de mots sur {0. 1101} est un code pr´fixe.

11}. 2 lettres b et r. It´ration On fusionne deux des arbres dont la fr´quence est minimale. Chaque e e feuille correspond ` une lettre du texte. e a e e Le probl`me qui se pose est de minimiser la taille du texte cod´. ce qui donne le tableau de codage suivant : a b c d r → → → → → 00 010 011 10 11 Le codage consiste simplement ` remplacer chaque lettre par son code. Si l’on choisit un e e autre codage. Avec le code donn´ dans le e e e tableau ci-dessus. ´ventuellement ` un reste pr`s qui est un pr´fixe d’un mot du code. ARBRES 0 0 a 0 b 1 1 c 0 d 1 1 r Fig. en ce sens que la taille du code produit est minimale parmi la taille de tous les codes produits ` a l’aide de codes pr´fixes complets. comme par exemple a → 0 b → 10 c → 1101 d → 1100 r → 111 on observe que le mˆme mot se code par 01011101101011000101110 et donc avec 23 bits. Lorsque l’on est dans une feuille. c’est-`-dire e a l’´tiquette du chemin de la racine ` la feuille. Ainsi. 011. Bien e sˆr. on est sˆr de pouvoir toujours d´coder un a u e message. on y trouve le caract`re correspondant. Ici. et a pour valeur la fr´quence d’apparition de la a e lettre dans le texte. abracadabra devient a 000101100011001000010110. Dans le cas de l’arbre de la figure 24. 010. Le code de la lettre est le code de la feuille. 10.2 Construction de l’arbre L’algorithme de Huffman construit un arbre binaire complet qui donne un code optimal. Le fait que le code soit pr´fixe permet un d´codage instantan´ : il e e e suffit d’entrer la chaˆ caract`re par caract`re dans l’arbre. le texte abracadabra. une fois cod´. est compos´ de 25 bits. 24 – Le code des feuilles est {00. on associe e a les lettres aux feuilles de la gauche vers la droite. 6. et de se laisser guider par les ıne e e ´tiquettes. Quand le code est complet. la taille du r´sultat ne d´pend que de la fr´quence d’apparition de chaque lettre dans le u e e e texte source.104 CHAPITRE IV. e e . il y a 5 lettres a.2. et 1 fois la lettre c et d. e Initialisation On construit une forˆt d’arbres binaires form´s chacun d’une seul feuille. et on e e recommence ` la racine. Le codage d’un texte par un code pr´fixe (complet) se fait de la mani`re suivante : ` chaque e e a lettre est associ´e une feuille de l’arbre. Le nouvel arbre a pour e e fr´quence la somme des fr´quences de ses deux sous-arbres.

Ensuite. CODAGE DE HUFFMAN Arrˆt On termine quand il ne reste plus qu’un seul arbre qui est l’arbre r´sultat. 27 – L’arbre est fusionn´ avec la feuille de r. e a 5 a 2 b 2 r 2 1 d 1 c Fig. la somme des fr´quences de ses feuilles. La premi`re ´tape conduit ` la cr´ation e e a e des 5 arbres (feuilles) de la figure 25.6. etc. notamment a e s’il s’agit de donn´es binaires. on peut choisir lequel des deux arbres devient sous-arbre gauche ou droit. Le r´sultat est repr´sent´ dans la figure 29. e 5 a 2 b 1 d 4 2 1 c 2 r Fig. Voici une e u e illustration de cet algorithme sur le mot abracadabra. L’une d’elle consiste ` grouper les lettres par deux (digrammes) ou mˆme par bloc plus grands. e e e 5 a 2 b 2 r 1 c 1 d Fig. 26 – Les feuilles des lettres c et d sont fusionn´es. les deux sous-arbres de fr´quence minie male ne sont peut-ˆtre pas uniques. e Notons qu’il y a beaucoup de choix dans l’algorithme : d’une part. On peut prouver e a que cet algorithme donne un code optimal. e e e . et l` encore. il y a des choix pour la fusion. Les feuilles des lettres c et d sont fusionn´es (figure 26). e 5 a 2 b 6 4 2 1 d 1 c 2 r Fig. 28 – La feuille de b est fusionn´e avec l’arbre. e puis cet arbre avec la feuille b (figure 27). Les techniques de compression avec dictionnaire (compression de e Ziv-Lempel) ou le codage arithm´tique donnent en g´n´ral de meilleurs taux de compression. Il en existe de nombreuses variantes. e e 105 La fr´quence d’un arbre est. bien sˆr. 25 – Les 5 arbres r´duits chacun ` une feuille.

i.table). Seuls les caract`res qui figurent dans le texte. calculFrequences(s). o e class Huffman { final static int N = 26. o`.1. On part du principe que les 26 lettres de l’alphabet peuvent figurer dans le texte (dans la pratique. e e e vont ˆtre repr´sent´s dans l’arbre. public static void main(String[] args) { String s = args[0]. } } La m´thode main d´crit l’op´ration : on calcule les fr´quences des lettres. a e e 6. e e 6. not´e freq[c].106 11 5 a 6 2 b 1 d 4 2 1 c 2 r CHAPITRE IV.println(). La cl´ de la s´lection est la fr´quence des lettres et la fr´quence cumul´e e e e e e dans les arbres. pour x 0 et y > 0 pere[x] = y si x est fils droit de e u y et pere[x] = -y si x est fils gauche de y. Une autre m´thode rencontr´e est l’emploi de deux files. e e Cette repr´sentation des donn´es est bien adapt´e au probl`me consid´r´. On le repr´sente ici par un tableau pere. 29 – L’arbre apr`s la derni`re fusion. // nombre de caract`res e static int[] pere = new int[M]. de fr´quence non nulle.e. R´aliser une e e e e implantation ` l’aide de cette deuxi`me repr´sentation est un bon exercice. M = 2*N . on prendra plutˆt un alphabet de 128 ou de 256 caract`res). et un e e tableau freq qui contient les fr´quences des lettres dans le texte.2. static int[] freq = new int[M]. Chaque caract`re c a une fr´quence d’apparition dans e e le texte. mais est assez e e e e ee ´loign´e des repr´sentations d’arbres que nous verrons dans le chapitre suivant. afficherCode(s. ARBRES Fig.out. System.2. et aussi les fr´quences cumul´es e e e dans les arbres.4 Implantation La classe Huffman a deux donn´es statiques : le tableau pere pour repr´senter l’arbre.3 Choix de la repr´sentation des donn´es e e Si le nombre de lettres figurant dans le texte est n. l’arbre de Huffman est de taille 2n − 1. e e e La cr´ation de l’arbre est contrˆl´e par un tas (mais un (( tas-min )). c’est-`-dire un tas avec e oe a extraction du minimum). creerArbre(). avec le tableau des e e e . String[] table = faireTable(). et le nombre de e e e e lettres de fr´quence non nulle. On cr´e ensuite un tas de taille appropri´e.

i < N. for (int i = 0. dont la fr´quence est la somme des fr´quences des deux arbres e e extraits. ++i) if (freq[i] >0) tas.ajouter(i). Il faudrait bien sˆr modifier cette m´thode si e e u e on prenait un alphabet ` 256 caract`res. return n. } } Le calcul de la table de codage se fait par un parcours d’arbre : static String code(int i) { . static void creerArbre() { int n = nombreLettres().charAt(i) . i < s. pere[x] = -i. i++) freq[s.supprimer().ajouter(i).minimum(). } static int nombreLettres() { int n = 0. L’op´ration principale est creerArbre() qui cr´e l’arbre de Huffman. tas. qui donne l’unicode du caract`re en position i. Comme il y a n feuilles dans l’arbre. a e static void calculFrequences(String s) { for (int i = 0.’a’ donne e e bien le rang du i-`me caract`re dans l’alphabet.6. tas. e e e e les lettres sont ins´r´es dans le tas . pere[y] = i. et un nouvel arbre. La e e e e table de codage est ensuite calcul´e et appliqu´e ` la chaˆ ` compresser. for (int i = N. ensuite. e e a ıne a Voyons les diverses ´tapes. freq[i] = freq[x] + freq[y]. } La m´thode de cr´ation d’arbre utilise tr`s exactement l’algorithme expos´ plus haut : d’abord. tas. CODAGE DE HUFFMAN 107 fr´quences comme cl´s. Le calcul des fr´quences et du nombre de lettres de fr´quence non e e e nulle ne pose pas de probl`me. for (int i = 0.charAt(i) .length().’a’]++. freq). i < N+n-1. il y a n − 1 cr´ations de ee e nœuds internes. On notera toutefois l’utilisation de la m´thode charAt de la classe e e String.supprimer(). les deux ´l´ments de fr´quence minimale sont ee ee e extraits.minimum(). ++i) { int x = tas. l’expression s. Tas tas = new Tas(2*n-1. int y = tas. Comme on a suppos´ que l’alphabet ´tait e e e a–z et que les unicodes de ces caract`res sont cons´cutifs. est ins´r´ dans le tas. i++) if (freq[i] > 0) n++. i < N.

} a[i] = v. int[] freq. while (i > 0 && freq[a[(i-1)/2]] >= freq[v]) { a[i] = a[(i-1)/2]. } int minimum() { return a[0]. nTas = 0. ea e e class Tas { int[] a. Les m´thodes des e e e tas d´j` vues se r´crivent tr`s facilement dans ce cadre. return table. } static String[] faireTable() { String[] table = new String[N].abs(pere[i])) + ((pere[i] < 0) ? "0" : "1").freq = freq. } Il reste ` examiner la r´alisation du (( tas-min )).108 CHAPITRE IV. int[] freq) { this. le maximum est remplac´ par le minimum (ce qui est n´gligeable). return code(Math. mais une valeur qui leur est associ´e (la fr´quence de la lettre). a = new int[taille]. int i = 0. et d’autre part. for (int i = 0. Il y a deux diff´rences avec les tas d´j` vus : a e e ea d’une part. ARBRES if (pere[i] == 0) return "". } void ajouter(int v) { int i = nTas. // contient les caract`res e int nTas = 0. // contient les fr´quences des caract`res e e Tas(int taille. i < N. } void supprimer() { int v = a[0] = a[--nTas]. e e ce ne sont pas les valeurs des ´l´ments eux-mˆmes (les lettres du texte) qui sont utilis´es comme ee e e comparaison. i++) if (freq[i] >0) table[i] = code(i). ++nTas. while (2*i + 1 < nTas) { . i = (i-1)/2.

e e e Pour chaque lettre x. puis la fr´quence de la lettre est augment´e e e e et l’arbre de Huffman est recalcul´. e Une m´thode plus simple est de transmettre la suite de fr´quence. Le principe est le suivant : e Au d´part. 12. pour e e que le destinaire puisse d´compresser le message. if (freq[v] <= freq[a[j]]) break. e e de la figure 30 ont des fr´quences diff´rentes. 13. Le code d’une lettre s’adapte ` sa a fr´quence.6. Transmettre la table telle quelle est redondant e puisque l’on transmet tous les chemins de la racine vers les feuilles. } a[i] = v.3 Algorithme de Huffman adaptatif Les inconv´nients de la m´thode de compression de Huffman sont connus : e e Il faut lire le texte enti`rement avant de lancer la compression. a[i] = a[j]. la longueur de son code diminue. } } 109 Une fois la table du code calcul´e. 30 – Deux suites de fr´quences qui produisent le mˆme arbre. 41. donc e e e a e plus courtes ` transmettre) qui donne le mˆme code de Huffman. avec la valeur litt´rale des lettres repr´sent´es aux feuilles. encore faut-il la transmettre avec le texte comprim´. e Il faut aussi transmettre le code trouv´. toutes les lettres ont mˆme fr´quence (nulle) et le code est uniforme. i = j. les deux arbres a e 123 80 41 e 25 13 d 12 b 14 c 1 d 1 b 39 43 a 3 e 2 2 c 4 7 12 5 a Fig. 14. Cette deuxi`me m´thode admet un raffinement (qui montre jusqu’o` peut e e u aller la recherche de l’´conomie de place) qui consiste ` non pas transmettre les valeurs exactes e a des fr´quences. le code de x est envoy´. e Une version adaptative de l’algorithme de Huffman corrige ces d´fauts. le code d’une lettre change en cours de transmission : quand la fr´quence (relative) e d’une lettre augmente. Par exemple. CODAGE DE HUFFMAN int j = 2*i + 1. e Evidemment. Il est plus ´conomique de faire e un parcours pr´fixe de l’arbre. on obtient la repr´sentation 01[a]01[b]001[d]1[c]1[r]. mais le mˆme code. quitte au destinataire de e e reconstruire le code. pour l’arbre de la figure 29. Le destinataire du message compress´ mime le codage de l’exp´diteur : il maintient e e e . 1. Il est bien plus ´conomique de e e e e transmettre la suite de nombres 5. 3 que la suite initiale 43. if (j +1 < nTas && freq[a[j+1]] < freq[a[j]]) ++j. 2. Par e e e e exemple. 1. 6. mais une s´quence de fr´quences fictives (` valeurs num´riques plus petites.

Il n’est pas non plus n´cessaire de transmettre la table de codage a a e puisqu’elle est recalcul´e par le destinataire. Il existe. e e e Avant l’incr´mentation. C’est dans ce cas que l’algorithme adaptatif s’av`re particuli`rement utile. Bien entendu. et il sait donc ` tout moment quel a e a est le code d’une lettre. Le code correspondant est envoy´. mais e a les fr´quences moyennes rencontr´es dans les textes de la langue consid´r´e. L’arbre de Huffman est de plus muni d’une liste de parcours (un ordre total) qui a les deux propri´t´s suivantes : ee le parcours est compatible avec les fr´quences (les fr´quences sont croissantes dans l’ordre e e du parcours). Supposons que e e e .110 CHAPITRE IV. dans un arbre de Huffman. et on remonte vers la e e e e racine en incr´mentant les fr´quences dans les nœuds rencontr´s. Soit x le caract`re lu dans e e e e le texte clair. on peut toujours trouver un ordre de parcours e poss´dant ces propri´t´s. comme l’algorithme statique. L’arbre e ee e e de Huffman ´volue avec chaque lettre cod´e. Les deux inconv´nients du codage de Huffman (statique) sont corrig´s par cette version. e e e Partant de la feuille du caract`re. deux nœuds fr`res sont toujours cons´cutifs. Aux nœuds de l’arbre sont stock´es les e e fr´quences cumul´es. e Il existe par ailleurs une version (( simplifi´e )) du codage de Huffman o` les fr´quences utilis´es e u e e pour la construction de l’arbre ne sont pas les fr´quences des lettres du texte ` comprimer. Il e e n’est pas n´cessaire de calculer globalement les fr´quences des lettres du texte puisqu’elles sont e e mises ` jour ` chaque pas. e e On d´montre que. un arbre de Huffman qu’il met ` jour comme l’exp´diteur. chaque nœud est permut´ avec le dernier nœud (celui de plus e e grand num´ro) de mˆme fr´quence dans l’ordre de parcours. e e e La deuxi`me op´ration garantit que l’ordre reste compatible avec les fr´quences. sur un arbre. e L’algorithme adaptatif op`re. par exemple. les nœuds sont num´rot´s dans un tel ordre. c’est-`-dire la somme des fr´quences des feuilles du sous-arbre dont le e e a e nœud est la racine (voir figure 31). une version adaptative de l’algorithme de compression arithm´tique. 31 – Un arbre de Huffman avec un parcours compatible. le e e ee taux de compression s’en ressent si la distribution des lettres dans le texte ` comprimer s’´carte a e de cette moyenne. puis les a e deux op´rations suivantes sont r´alis´es. e e Notons enfin que le principe de l’algorithme adaptatif s’applique ` tous les algorithmes a de compression statistiques. ARBRES 32 11 9 11 a 7 10 21 10 11 8 4 5 3 5 c 6 6 e 5 5 f 2 3 b 1 2 d Fig. Dans la figure 31. sa fr´quence est incr´ment´e. avec leurs fr´quences. Aux feuilles de e l’arbre se trouvent les lettres. de la mani`re suivante. Il correspond ` une feuille de l’arbre.

33 – Avant permutation des nœuds 8 et 9 (` gauche) . 32 – Apr`s incr´mentation de d (` gauche). et apr`s permutation des nœuds (` droite). et les contes de Grimm en faisaient partie. De mˆme. Le code a ´mis est alors 1001. le taux e de compression obtenu par les deux m´thodes est sensiblement ´gal. e e Pour terminer. et arbre final (` droite). Son num´ro d’ordre est 4. e e a e a 32 11 33 11 9 11 a 7 10 21 10 8 12 21 10 11 8 6 6 e 6 5 7 10 11 9 a 4 5 f 3 5 c 6 6 e 2 3 b 6 5 2 3 b 1 3 d 5 4 f 5 3 c 1 3 d Fig. et le plus grand nœud de fr´quence 5 a num´ro d’ordre e e e e 5. de mˆme fr´quence (figure 33). Le p`re de la feuille d a pour e e e e e fr´quence 5. Les deux nœuds sont ´chang´s (figure 32). Ces statistiques ont ´t´ obtenues ` une ´poque o` il y avait encore peu de donn´es disponibles ee a e u e sous format ´lectronique. la racine e e e e e est incr´ment´e. La fr´quence de la feuille d est incr´ment´e. avant d’incr´menter le nœud de num´ro e e e e e 8. CODAGE DE HUFFMAN 32 11 32 11 111 9 11 a 7 10 21 10 9 11 a 11 8 7 10 21 10 11 8 4 5 3 5 c 6 6 e 5 5 f 4 5 f 3 5 c 6 6 e 2 3 b 6 5 2 3 b 1 3 d 1 3 d Fig. Comme on le voit. a a la lettre suivante du texte clair ` coder.. Ensuite. il est ´chang´ avec le nœud de num´ro 9. voici une table de quelques statistiques concernant l’algorithme de Huffman. dans l’arbre de la figure 31. soit une lettre d.6. car les donn´es sont assez e e e ..

e Taille (bits) Huffman Taille code Total Adaptatif Contes de Grimm 700 000 439 613 522 440135 440164 CHAPITRE IV.112 homog`nes. ARBRES Texte technique 700 000 518 361 954 519315 519561 Si on utilise un codage par digrammes (groupe de deux lettres). e e . les statistiques sont les suivantes : Contes de Grimm 700 000 383 264 10 880 394 144 393 969 Texte technique 700 000 442 564 31 488 474 052 472 534 Taille (bits) Huffman Taille code Total Adaptatif On voit que le taux de compression est l´g`rement meilleur.

Arbre d) { filsG = g. fils droit). Un arbre r´duit ` une feuille. contenu de la racine. new Arbre( new Arbre( new Arbre(null. Il suffit de pr´ciser la e e e e nature du contenu d’un nœud. puis les arbres ´quilibr´s. est cr´´ par ee new Arbre(null. null). l’un appel´ le fils gauche. filsD. Nous nous int´ressons aux arbres contenant e e des informations. filsD = d. 6. Arbre filsG. 113 . nous traitons d’abord les arbres binaires de recherche. contenu = c. e e 1 Implantation des arbres binaires Un arbre binaire qui n’est pas vide est form´ d’un nœud. int c. appel´e son contenu. 8. null) L’arbre de la figure 1 est cr´´ par ee new Arbre( new Arbre( new Arbre(null.Chapitre V Arbres binaires Dans ce chapitre. l’autre le fils droit. } } L’arbre vide est. Chaque nœud porte une information. Un arbre non vide e est donc enti`rement d´crit par le triplet (fils gauche. et de deux sous-arbres e binaires. comme d’habitude. On obtient alors la d´finition. sa racine. x. 3. null). Arbre(Arbre g. 5. nous supposons que le contenu est un entier. de contenu e e e a x. Pour simplifier. repr´sent´ par null. Cette e e d´finition r´cursive se traduit en une sp´cification de programmation. e class Arbre { int contenu.

Arbre d) { return new Arbre(g. Avant de poursuivre. (a = null) Comme pour les listes.filsG. 20. new Arbre( new Arbre(null. cle(a). d)) = d composer(filsGauche(a). d)) = c filsGauche(composer(g. null).contenu. new Arbre(null. cle. 28. Quatre fonctions cae ea e ract´risent les arbres : composer. } static Arbre filsDroit(Arbre a) { return a. new Arbre(null. d)) = g filsDroit(composer(g. 25. filsDroit(a)) = a. ARBRES BINAIRES 28 Fig. filsDroit. 1 – Un arbre binaire portant des informations aux nœuds. null))) 20 5 3 8 6 12 13 21 25 CHAPITRE V. } Les quatre fonctions sont li´es par les ´quations suivantes : e e cle(composer(g. int c. } static Arbre filsGauche(Arbre a) { return a. filsGauche. a e . c. } static int cle(Arbre a) { return a. c. d). 21.filsD. ces quatre fonctions sont ` la base d’op´rations non destructives. 13.114 null) 12. c. reprenons le sch´ma d´j` utilis´ pour les listes. Elles s’implantent facilement : e static Arbre composer(Arbre g. null))). c.

sauf que ce sont.filsG) + taille(a. Nous illustrons ce style de programmation par les parcours d’arbre d´finis page 89.print(a.filsD).print(a. System. s’obtient par la formule t(a) = 0 si a est vide 1 + t(ag ) + t(ad ) sinon. cette fois-ci. avec une pile.filsG).filsD).out. System. c’est-`-dire le e a nombre t(a) de ses nœuds.contenu + " ").print(a.contenu + " "). Nous reprenons les classes Pile e e e et File du chapitre I. parcoursPr´fixe(a. des piles ou des files d’arbres.filsG).out. } static void parcoursSuffixe(Arbre a) { if (a == null) return. parcoursInfixe(a. D’o` la m´thode u e u e static int { if (a == return return 1 } taille(Arbre a) null) 0. IMPLANTATION DES ARBRES BINAIRES 115 La d´finition r´cursive des arbres binaires conduit naturellement ` une programmation e e a r´cursive. Le parcours pr´fixe e e s’´crit lui aussi simplement de mani`re it´rative. e } static void parcoursInfixe(Arbre a) { if (a == null) return. .filsD). On ´crit alors e static void parcoursPr´fixeI(Arbre a) e { if (a == null) return. parcoursSuffixe(a. + taille(a. } Le parcours en largeur d’un arbre binaire s’´crit simplement avec une file. Voici quelques exemples : la taille d’un arbre. Des formules semblables donnent le nombre de feuilles ou la hauteur d’un arbre.filsD).out. Les trois parcours en e profondeur s’´crivent : e static void parcoursPr´fixe(Arbre a) e { if (a == null) return. parcoursInfixe(a. e parcoursPr´fixe(a.1. comme pour les listes. parcoursSuffixe(a. o` sont not´s ag et ad les sous-arbres gauche et droit de a.contenu + " "). System.filsG).

} } CHAPITRE V.out. int c.print(a. Un nœud contient aussi la r´f´rence ` son e ın´ ee a fils aˆ e. System.ajouter(a. p.116 Pile p = new Pile().1 Implantation des arbres ordonn´s par arbres binaires e Rappelons qu’un arbre est ordonn´ si la suite des fils de chaque nœud est ordonn´e.valeur(). if (a. Il est donc e e naturel de repr´senter les fils dans une liste chaˆ ee.ajouter(a.ajouter(a. } } . if (a.filsG != null) f.filsG != null) p.out.filsG). Arbre d) { filsAine = g. while (!f. Arbre(Arbre g. if (a. f. } } static void parcoursLargeurI(Arbre a) { if (a == null) return. ARBRES BINAIRES 1.filsD). et celle ` son fr`re cadet.supprimer().ajouter(a. class ArbreOrdonne { int contenu. while (!p.filsD). System. p. sous r´serve de rebaptiser filsAine le champ filsG et frereCadet le champ e filsD. File f = new File().filsG). frereCadet.contenu + " ").valeur(). la structure des arbres binaires a ın´ a e convient parfaitement. c’est-`-dire ` la tˆte de la liste de ses fils. frereCadet = d.ajouter(a).supprimer().filsD != null) p.estVide()) { a = f. if (a. Ainsi.filsD != null) f. contenu = c.ajouter(a). f. En d’autres termes.print(a.estVide()) { a = p. ın´ a a e ee celle ` son fils aˆ e. chaque nœud contient deux r´f´rences.contenu + " "). Arbre filsAine.

e figure 3). e e e 117 1 2 2 3 4 5 6 10 10 7 8 3 1 5 6 7 8 9 4 9 Fig. pour tout nœud s de l’arbre. par un seul arbre binaire.2. Dans un arbre binaire de recherche. . e Un arbre binaire a est un arbre binaire de recherche si. le parcours infixe fournit les contenus des nœuds en e ordre croissant. si on change la valeur 13 en 11. pour tout nœud s de a. 15 12 8 10 13 14 16 17 20 21 Fig. ARBRES BINAIRES DE RECHERCHE Cette repr´sentation est aussi appel´e (( fils gauche — fr`re droit )) (voir figure 2). En fait. Chaque nœud contient une donn´e prise a e e dans un certain ensemble.. Nous supposons dans cette section que cet ensemble est totalement ordonn´. et que le contenu du fils droit de s soit sup´rieur e e au contenu de s. le contenu du fils gauche de s soit strictement inf´rieur au contenu de s. d’une forˆt ordonn´e d’arbres ordonn´s. Ainsi. dans l’arbre de la figure 3. cette repr´sentation s’´tend e e a ` la repr´sentation. et que les e contenus des nœuds du sous-arbre droit de s sont strictement sup´rieurs au contenu de s (cf. Une cons´quence directe de la d´finition est la r`gle suivante : e e e R`gle. 2 – Repr´sentation d’un arbre ordonn´ par un arbre binaire. Ceci est le cas par exemple pour les entiers et pour les mots. e e e e 2 Arbres binaires de recherche Les arbres binaires servent ` g´rer des informations. on n’a plus un arbre de recherche. 3 – Un arbre binaire de recherche.. e e Noter que la racine de l’arbre binaire n’a pas de fils droit. Une petite mise en garde : contrairement ` ce que l’analogie avec les tas pourrait laisser a croire. il ne suffit pas de supposer que. les contenus des nœuds du sous-arbre gauche de s sont strictement inf´rieurs au contenu de s.

son pr´d´cesseur dans le parcours infixe est le nœud e e e e le plus ` droite dans son sous-arbre gauche. qui n’a pas de fils droit. On voit que lorsque l’arbre est bien ´quilibr´. e La m´thode s’´crit r´cursivement comme suit : e e e . Pour chercher si un ´l´ment x figure dans un arbre A. Comme toujours. sinon il y a e e e e deux cas selon que x < c et x > c. c’este e e a `-dire lorsque la hauteur est proche du logarithme de la taille. Ce nœud n’est pas n´cessairement une feuille. et null sinon. par liste ordonn´s ou non. mais certainement pas dans le sous-arbre droit Ad . Cette m´thode n’est pas sans rappeler la recherche dichotomique. et par arbre binaire de recherche. ee e il y a le choix entre une m´thode r´cursive. e e 2. S’il y a ´galit´. et e e e e e une m´thode it´rative. on commence par e e ee comparer x au contenu c de la racine de A. cheminant dans l’arbre. les op´rations sont r´alisables de e e mani`re particuli`rement efficace.1 Recherche d’une cl´ e Nous commen¸ons l’implantation des op´rations de dictionnaire sur les arbres binaires de c e recherche par l’op´ration la plus simple. nous ´crivons une m´thode qui retourne l’arbre e ee e e dont la racine porte l’´l´ment cherch´ s’il figure dans l’arbre. Tab. dans l’arbre de la figure 3. ainsi que des op´rations destrucu ee e ea e e tives. la racine poss`de un fils gauche. une implantation souvent efficace d’un type abstrait de donn´es.118 CHAPITRE V. appel´ dictionnaire. e R`gle.. en commen¸ant par e e e c la m´thode r´cursive. mais a e il n’a pas de fils droit. comme on le verra. Plusieurs implantations d’un dictionnaire sont envisageables : par tableau. u ee e e Ces coˆts supposent l’´l´ment supprim´ d´j` trouv´. ARBRES BINAIRES Une seconde r`gle permet de d´terminer dans certains cas le nœud qui pr´c`de un nœud e e e e donn´ dans le parcours infixe. Nous pr´sentons les deux. la recherche. e e Implantation Tableau non ordonn´ e Liste non ordonn´e e Tableau ordonn´ e Liste ordonn´e e Tas Arbre de recherche a b Rechercher O(n) O(n) O(log n) O(n) O(n) O(h) Ins´rer e O(1)a O(1)a O(n) O(n) O(log n) O(h) Supprimer O(n) O(1)b O(n) O(1)b O(n) O(h) Ces coˆts supposent que l’on sait que l’´l´ment ins´r´ n’est pas dans le dictionnaire. alors x figure peut-ˆtre dans le sous-arbre gauche Ag e de A. Ainsi.. Si un nœud poss`de un fils gauche. 1 – Complexit´ des implantations de dictionnaires e L’entier h d´signe la hauteur de l’arbre. Plutˆt que d’´crire une m´thode bool´enne e o e e e qui teste la pr´sence d’un ´l´ment dans l’arbre. Si x < c. par tas. On ´limine ainsi de la recherche tous e les nœuds du sous-arbre droit. qui op`re sur un ensemble totalement e e e ordonn´ ` l’aide des op´rations suivantes : ea e rechercher un ´l´ment ee ins´rer un ´l´ment e ee supprimer un ´l´ment ee Le dictionnaire est simplement une table d’associations (chapitre III) dont les informations sont inexistantes. on a trouv´ la r´ponse . La table 1 rassemble la complexit´ e e des op´rations de dictionnaire selon la structure de donn´es choisie. Les arbres binaires de recherche fournissent. calqu´e sur la d´finition r´cursive des arbres. et son pr´d´cesseur est le e e e nœud de contenu 14.

null). x. a. return chercher(x. ce qui est logique.contenu) a = a.2. Ceci inclut le cas o` l’arbre est vide.contenu. Arbre a) { while(a != null && x != a. return new Arbre(b.contenu) return chercher(x. Arbre a) { if (a == null) return new Arbre(null.filsG.filsG).filsD = inserer(x.filsG = inserer(x. a.contenu) a. } On voit que la condition de continuation dans la m´thode it´rative chercherI est la n´gation de e e e la condition d’arrˆt de la m´thode r´cursive.filsG).filsD. on ne e ea l’ajoute pas une deuxi`me fois.contenu) return a. if (x < a. a. } 119 Cette m´thode retourne null si l’arbre a ne contient pas x. return a. Nous sommes confront´s au ee a e mˆme choix que pour les listes : soit on construit une nouvelle version de l’arbre (version non e destructive).filsD).contenu) { Arbre b = inserer(x.contenu) a. if (x < a. e u Voici la m´thode it´rative.contenu) if (x < a. ARBRES BINAIRES DE RECHERCHE static Arbre chercher(int x. soit on modifie l’arbre existant (version destructive). } . Arbre a) { if (a == null || x == a. a. null). a. Nous pr´sentons une m´thode e e r´cursive dans les deux versions. Dans les deux cas. return a. e e static chercherI(int x.filsD). e static Arbre inserer(int x. si l’entier figure d´j` dans l’arbre. e e e 2.filsD). } Voici la version non destructive. static Arbre inserer(int x. x. Arbre a) { if (a == null) return new Arbre(null. else a = a. if (x < a.2 Insertion d’une cl´ e L’adjonction d’un nouvel ´l´ment ` un arbre modifie l’arbre.filsG). a. Voici la version destructive. else if (x > a. a.

on cherche le pr´d´cesseur t de s. e e La figure 5 illustre la (( remont´e )) : le nœud s qui porte la cl´ 16 n’a qu’un seul enfant.filsD). en rempla¸ant la cl´ par une e c e . e e si le nœud s poss`de deux fils. et on ´limine t. Cet e e enfant devient l’enfant du p`re de s. ARBRES BINAIRES else if (x > a. } 2. a. ce n’est pas toujours le nœud qui porte la cl´ ` supprimer qui sera enlev´.contenu) { Arbre b = inserer(x. 5 – Suppression de la cl´ 16 par remont´e du fils. mais seulement sa cl´. } return a. return new Arbre(a. On remplace le contenu de s par le contenu de t. Comme on le verra. e e 15 12 8 10 13 17 14 16 18 19 20 21 8 10 13 12 14 17 18 19 15 20 21 Fig. 4 – Suppression de la cl´ 13 par ´limination d’une feuille.filsG. On ne supprime pas le nœud. le nœud de cl´ 20.3 Suppression d’une cl´ e La suppression d’une cl´ dans un arbre est une op´ration plus complexe. Soit s le nœud qui porte la cl´ x ` supprimer. Elle s’accompagne e e de la suppression d’un nœud. alors on l’´limine . La cl´ ` supprimer se trouve ` la racine a e ea a de l’arbre.120 CHAPITRE V. e La suppression de la feuille qui porte la cl´ 13 est illustr´e dans la figure 4 e e 15 12 8 10 13 14 16 17 20 21 8 10 12 14 15 20 16 17 21 Fig. e e Le cas d’un nœud ` deux fils est illustr´ dans la figure 6. e si le nœud s poss`de un seul fils. b).contenu. Trois cas sont ` ea e e a a consid´rer selon le nombre de fils du nœud x : e si le nœud s est une feuille. a. on ´limine s et on (( remonte )) ce fils. Celui-ci n’a pas de fils e e e droit.

a.filsD == null) . donc il a z´ro ou un fils. a. Ainsi. il n’a pas de fils droit. et sa suppression est couverte par les e deux premiers cas. if (x == a. La troisi`me.filsG = supprimer(f. } La derni`re m´thode est toute simple : e e static Arbre dernierDescendant(Arbre a) { if (a. 15 12 8 10 13 14 16 17 20 21 8 10 12 13 16 17 14 20 21 Fig. Arbre f = dernierDescendant(a. if (a.contenu) a. Pour conserver l’ordre sur les cl´s. elle calcule le e ee e e pr´c´desseur d’un nœud qui a un fils gauche. la cl´ 14 est e e e mise ` la racine de l’arbre. effectue la suppression selon les cas ea e ´num´r´s ci-dessus. a. ou la cl´ du successeur.contenu. dernierDescendant est une m´thode auxiliaire . recherche le nœud e e e portant la cl´ ` supprimer .2. ARBRES BINAIRES DE RECHERCHE 121 autre cl´. 6 – Suppression de la cl´ 15 par substitution de la cl´ 14 et suppression de ce nœud.filsG).filsG). return a. Arbre a) { if (a == null) return a. suppressionRacine. e e static Arbre supprimer(int x. e e static Arbre supprimerRacine(Arbre a) { if (a.filsG).contenu. La premi`re. Nous sommes alors ramen´s au probl`me de la suppression du nœud a e e du pr´d´cesseur et de sa cl´. if (x < a.filsD). Comme le pr´d´cesseur est le nœud le plus ` droite du sous-arbre e e e e e a gauche. il n’y a que deux choix : la cl´ du pr´d´cesseur dans e e e e e l’ordre infixe.filsG == null) return a. suppression. a.filsD == null) return a. la deuxi`me.filsD = supprimer(x. } La m´thode suivante supprime la cl´ de la racine de l’arbre.filsG = supprimer(x.contenu) return supprimerRacine(a).contenu = f. Nous choisissons la premi`re solution. a. e e Trois m´thodes coop`rent pour la suppression.filsD. else a.filsG.

} else { // cas (ii) Arbre p = avantDernierDescendant(b). En fait. l’appel ` supprimer ` la derni`re ligne de supprimerRacine conduit au nœud a a e pr´d´cesseur de la racine de l’arbre.filsG.filsD). a.contenu) if (x < a. p.122 return a. } return a. appel´ f.filsD..filsD == null) { // cas (i) a.filsD. Arbre b = a.contenu = f. ARBRES BINAIRES La r´cursivit´ crois´e entre les m´thodes supprimer et supprimerRacine est d´routante au premier e e e e e abord. if (b.contenu) a = a. Arbre a) { Arbre b = a. il n’appelle pas e e e une deuxi`me fois la m´thode supprimerRacine.filsD == null) return a. c e static Arbre supprimer(int x. } Et voici le calcul de l’avant-dernier descendant : static Arbre avantDernierDescendant(Arbre a) { while (a. chacune des trois m´thodes peut s´par´ment ˆtre ´crite e e e e e e de fa¸on r´cursive.filsG == null) return a..filsG. a. } Voici la deuxi`me. if (a != null) a = supprimerRacine(a). Comme ce nœud n’a pas deux fils. if (a.filsG = b. Arbre f = p.filsG.filsD.contenu = b.filsD. return dernierDescendant(a. else a = a. En fait. return b. e static Arbre supprimerRacine(Arbre a) { if (a.contenu.filsG.filsG.filsD.filsD = f.contenu. Elle d´monte enti`rement e e e e e la (( m´canique )) de l’algorithme. .filsD != null) a = a. } CHAPITRE V. while (a != null && x != a. e e Il est int´ressant de voir une r´alisation it´rative de la suppression.

l’avant-dernier descendant n’existe pas. not´ p. Deux cas peuvent se produire : (i) la racine de b n’a pas de fils droit. u et pour d´terminer le pr´d´cesseur de la racine de a. e ıtre 2. l’insertion et la suppression dans un arbre binaire de recherche se font en complexit´ O(h). La premi`re partie e e e e e permet de se ramener au cas o` la racine de l’arbre a a deux fils. e a 21 b 12 8 10 22 23 24 25 8 10 22 23 12 24 25 Fig. que la recherche. au moyen de la m´thode e e avantDernierDescendant. ou un de ses descendants (notons que dans e e le cas (i). et b est remplac´e e ee a e par son sous-arbre gauche (qui peut d’ailleurs ˆtre vide). Les deux cas sont illustr´s sur les figures 7 et 8. sur nos implantations. on cherche le nœud le plus ` droite dans e e e a l’arbre b. version it´rative. e u . la cl´ de la racine de b est transf´r´e ` la racine de a. ou (ii) la racine de b a un fils droit. } 123 D´crivons plus pr´cis´ment le fonctionnement de la m´thode supprimerRacine. ARBRES BINAIRES DE RECHERCHE return a. 7 – Suppression de la racine. cas (i). de b sur la branche droite de b. 8 – Suppression de la racine. La cl´ de f est transf´r´e ` la e e ee a racine de a. cas (ii). version it´rative. e a 21 b 12 8 10 13 14 p 16 f 20 18 22 23 24 25 8 10 13 b 12 14 p 16 18 22 23 20 24 25 Fig. o` h est la hauteur de l’arbre. e Dans le premier cas. On note b le fils gauche de a. Le sous-arbre droit f de p n’est pas vide par d´finition. Dans le deuxi`me cas.2. et f est remplac´ par son sous-arbre gauche — ce qui fait disparaˆ la racine de f . ce qui explique le traitement s´par´ op´r´ dans e e ee ce cas). on cherche e e l’avant-dernier descendant.4 Hauteur moyenne Il est facile de constater. Cela peut ˆtre b lui-mˆme.

1 produisent toutes les deux l’arbre de la figure 9. A l’aide de ces u transformations on tend ` rendre l’arbre le plus r´gulier possible dans un sens qui est mesur´ a e e par un param`tre d´pendant en g´n´ral de la hauteur. et parce que les e e e principes utilis´s dans leur gestion se retrouvent dans d’autres familles plus complexes. et l’arbre r´duit ` une feuille. pour un arbre ` n nœuds. qui les ont pr´sent´s en 1962. Une famille d’arbres satisfaisant une e e e e condition de r´gularit´ est appel´e une famille d’arbres ´quilibr´s. e a . ARBRES BINAIRES Le cas le pire. Dans les langages comme Java ou C++. ou par la suite 2. Proposition 10 Lorsque tous les arbres binaires ` n nœuds sont ´quiprobables. sont des arbres AVL. Au risque de paraˆ vieillots. la hauteur e moyenne d’un arbre binaire de recherche obtenu par insertion des entiers d’une permutation dans un arbre initialement vide est O(n log n). e Rappelons qu’une feuille est un arbre de hauteur 0. . 3. . Lorsqu’un ordre total existe sur les ´l´ments de ces e e ee ensembles. les coˆts de la recherche. o` h est la hauteur de l’arbre. la deuxi`me aux a e e e permutations. les arbres 2-3. La premi`re des propositions s’applique aux arbres. ils sont en g´n´ral g´r´s. n} sont ´quiprobables. Pour ´viter que les arbres puissent prendre ces formes. on utilise des op´rations e e ` plus ou moins simples. 3 et 2. En ce qui concerne la hauteur moyenne. a Proposition 11 Lorsque toutes les permutations de {1. les hauteurs des sous-arbres gauche et droit diff`rent d’au plus 1. L’arbre vide. 1.124 CHAPITRE V. 3. des modules de gestion d’ensembles sont pr´programm´s. est O(n). La diff´rence provient du fait que plusieurs permutations peuvent donner le mˆme arbre. en interne. de l’insertion et de la suppression ea e u dans un arbre binaire de recherche sont de complexit´ O(h). 2 1 3 Fig. 3. pour un arbre ` n nœuds. est O(n). a e ee e ou (( filiformes )). 3 Arbres ´quilibr´s e e Comme nous l’avons d´j` constat´. . pour tout nœud de l’arbre.1 Arbres AVL La famille des arbres AVL est nomm´e ainsi d’apr`s leurs inventeurs. e e ee 3. 9 – L’arbre produit par la suite d’insertions 2. Plusieurs esp`ces de tels e e e e e e arbres ont ´t´ d´velopp´s. . notamment les arbres AVL. Le cas e u le pire. ee e e ainsi qu’une myriade de variantes. la hauteur a e √ moyenne d’un arbre binaire ` n nœuds est en O( n). mais peu coˆteuses en temps. les arbres rouge et noir. nous d´crivons ces arbres e e ıtre e plus en d´tail parce que leur programmation peut ˆtre men´e jusqu’au bout. e Un arbre binaire est un arbre AVL si. et que l’arbre vide a la hauteur −1. a deux cas sont ` consid´rer. 1. de transformation d’arbres. Par e e exemple les permutations 2. Adel’son-Velskii et e e Landis. par des arbres rouge et noir. Ce cas est atteint par des arbres tr`s d´s´quilibr´s. 1.

F (h+2) = F (h+1)+F (h) e pour h 0. 10 – Un arbre AVL. ARBRES EQUILIBRES 4 3 1 0 0 2 1 0 Fig. ins´rer ou supprimer une donn´e e e e dans un tel arbre. avec les hauteurs aux nœuds. Les premiers arbres de Fibonacci sont donn´s dans la figure 11. En d’autres termes. Il en r´sulte que 1 + n F (h) > √5 (Φh+3 − 1). soit en passant au logarithme u √ en base Φ. Alors N (h) = 1 + N (h − 1) + N (h − 2) car un arbre minimal aura un sous-arbre de hauteur h − 1 et l’autre sous-arbre de hauteur seulement h−2. Un autre exemple est fourni par les arbres de Fibonacci. l’insertion et la suppression (sous r´serve d’un ´ventuel r´´quilibrage) se font en temps logarithmique. Soit N (h) le nombre minimum 1+h α log2 (2 + n) Preuve. 11 – Les premiers arbres de Fibonacci. dans chaque nœud. e Fig. donc 1 F (h) = √ (Φh+3 − Φ−(h+3) ) 5 √ 1 e o` Φ = (1 + 5)/2. . Plus pr´cis´ment. 1 + h. ee Proposition 12 Soit A un arbre AVL ayant n nœuds et de hauteur h. La suite F (h) = 1+N (h) v´rifie F (0) = 2. on e e ee e e a la propri´t´ que voici.´ ´ 3. L’int´rˆt des arbres AVL r´sulte du fait que leur hauteur est toujours logarithmique en ee e fonction de la taille de l’arbre. F (1) = 3. la recherche. Par exemple. On a toujours n 2h+1 − 1. donc log2 (1 + n) de nœuds d’un arbre AVL de hauteur h. 1 0 2 0 125 L’arbre de la figure 10 porte. qui sont des arbres binaires An tels que les sous-arbres gauche et droit de An sont respectivement An−1 et An−2 . la hauteur de son sous-arbre. C’est le nombre d’op´rations qu’il faut donc pour rechercher. la hauteur est comprise entre 17 et 25.44. h + 3 < logΦ ( 5(2 + n)) < log2 (2 + n)/ log2 Φ + 2. pour un arbre AVL qui a 100000 nœuds. Alors log2 (1 + n) avec α = 1/ log2 ((1 + √ 5)/2) 1.

W )). X) un arbre dont le sous-arbre gauche poss`de un sous-arbre droit. Les rotations gauche (droite) ne sont donc d´finies e e que pour les arbres binaires non vides dont le sous-arbre gauche (resp. V ). ARBRES BINAIRES Nous introduisons maintenant une op´ration sur les arbres appel´e rotation. droit) n’est pas vide. W ) un arbre binaire tel que B = (U. e q p U V W V W rotation droite U p q Fig. (V. X) devient A′′ = (B ′ . p. W ) → (U. p. et montre comment elle est compos´e e e d’une rotation gauche du sous-arbre gauche suivie d’un rotation droite. q. En d’autres termes. q. 12 – Rotation. r. la rotation droite de A′′ donne en effet A′ . V ). En revanche. r. W )) et la rotation droite est l’op´ration inverse. La e rotation gauche est l’op´ration e ((U. p. si les symboles e d’op´ration p et q sont les mˆmes. e ee Pour rem´dier ` cela. q. e e e Les rotations ont la propri´t´ de pouvoir ˆtre implant´es en temps constant (voir ci-dessous). q. p. (V. si A est un arbre binaire de recherche. Plus pr´cis´ment. q. D’abord. Soit A = (B.1. la propri´t´ AVL ee n’est pas conserv´e par rotation. r. Les rotations e e sont illustr´es sur la figure 12. (W. r. 13 – Les rotations ne pr´servent pas la propri´t´ AVL. W ). p. et l’arbre A = (B. les rotations expriment que l’op´ration est associative. p. tout e arbre obtenu ` partir de A par une suite de rotations gauche ou droite d’un sous-arbre de A a reste un arbre binaire de recherche. on consid`re une double rotation qui est en fait compos´e de deux e a e e rotations. X) .126 3. p. q p W U V rotation droite U p q rotation gauche V W Fig. ee e e et de pr´server l’ordre infixe. q. p. q. V ). soit e e A = ((U. comme le montre la figure 13. Remarquons en passant que pour l’arbre d’une expression arithm´tique. La figure 14 d´crit une double rotation droite. (V. q. W )). W )) donne B ′ = ((U. On voit qu’une double rotation droite . une rotation gauche e e a de B = (U. X)) V´rifions qu’elle est bien ´gale ` la composition de deux rotations. V ).1 Rotations CHAPITRE V. La e double rotation droite est l’op´ration e A = ((U. (V. r. X) → A′ = ((U.

b. not´ b. ae static Arbre rotationG(Arbre a) { Arbre b = a.contenu. et augmente celle de X. e e e r p q U V W r q rotation gauche U p W V X rotation droite X double rotation droite U V W X p q q Fig. La rotation gauche dese tructive est aussi simple ` ´crire. return new Arbre(c.filsG. b. 14 – Rotations doubles. ARBRES EQUILIBRES 127 diminue la hauteur relative des sous-arbres V et W .filsD.1. a.filsG).1. b. 3.contenu.2 Implantation des rotations Voici une implantation non destructive d’une rotation gauche.´ ´ 3. on obtient l’arbre du milieu qui n’est plus ee e AVL. un nœud portant l’´tiquette e 50 est ins´r´ dans l’arbre de gauche. a.filsD). } La fonction suppose que le sous-arbre gauche. } // destructive Les double rotations s’´crivent par composition. Apr`s insertion.filsG = a.filsD = b.3 Insertion et suppression dans un arbre AVL L’insertion et la suppression dans un arbre AVL peuvent transformer l’arbre en un arbre qui ne satisfait plus la contrainte sur les hauteurs. a ee . La double rotation gauche est d´finie de mani`re sym´trique. e 3.filsG. n’est pas vide.filsD. b. static Arbre rotationG(Arbre a) // non destructive { Arbre b = a. Une double rotation autour de la racine suffit ` r´´quilibrer l’arbre. Arbre c = new Arbre(a. Dans la figure 15. return b.

. L’algorithme est le suivant : u Algorithme. e a e a class { int int Avl Avl contenu. } . Pour une feuille par exemple. mais pr´c´d´e d’une rotation gauche e e e de G si h(g) < h(d) (on note g et d les sous-arbres gauche et droit de G).H(d)). Pour l’arbre vide. } static void calculerHauteur(Avl a) { a. e c e On peut montrer en exercice qu’il suffit d’une seule rotation ou double rotation pour r´´quiee librer un arbre AVL apr`s une insertion. Si h(G) − h(D) = −2 on op`re de fa¸on sym´trique. hauteur = 1 + Math. contenu = c. ARBRES BINAIRES 61 double rotation 37 44 50 71 83 Fig. Cette propri´t´ est g´n´rale.filsD)). On suppose que |h(G) − h(D)| = 2.128 71 44 37 61 83 insertion de 50 37 44 61 50 71 83 CHAPITRE V.max(H(a.filsG). G et D ses sous-arbres gauche et droit. la hauteur vaut −1. qui est repr´sent´ par null et qui n’est donc pas un objet. int c. on fait une rotation droite. } static int H(Avl a) { return (a == null) ? -1 : a.. Avl d) { filsG = g. 15 – Insertion suivie d’une double rotation. Apr`s une insertion (respectivement une suppression). Si h(G) − h(D) = 2.max(H(g).hauteur = 1 + Math. filsG. Soit A un arbre. il suffit ee e e e de r´´quilibrer l’arbre par des rotations ou double rotations le long du chemin qui conduit ` la ee a feuille o` l’insertion (respectivement la suppression) a eu lieu. hauteur. filsD.1. filsD = d. e e La m´thode H sert ` simplifier l’acc`s ` la hauteur d’un arbre. H(a. Avl(Avl g. nous munissons chaque nœud d’un champ suppl´mentaire qui contient e la hauteur de l’arbre dont il est racine.4 Implantation : la classe Avl Pour l’implantation. ce champ a la valeur 0. e ee 3. Cette propri´t´ n’est plus vraie pour une suppression. } .hauteur.

Avl a) null) a.filsG.filsD). } La m´thode principale implante l’algorithme de r´´quilibrage expos´ plus haut. b. return rotationG(a). a. b.H(a.filsG.contenu) .filsD)). e ee e static Avl equilibrer(Avl a) { a.filsD.filsG) < H(a.filsG) .filsD).filsG). } return a. b. H(a.filsD = inserer(x. return equilibrer(a).filsD. //seul changement } La suppression s’´crit comme suit e static Avl { if (a == return if (x == supprimer(int x.filsD = rotationD(a.´ ´ 3. return rotationD(a). Avl c = new Avl(a.filsD) < H(a.contenu) a.max(H(a. On utilise la version non e e destructive qui r´´value la hauteur.filsD)) a. on obtient e e static Avl inserer(int x. a. Ces m´thodes et les suivantes font toutes partie de la classe ee e Avl.filsG)) a.contenu. On reprend simplement les m´thodes d´j` ´crites pour un arbre binaire a e e eae de recherche g´n´ral.filsG = rotationG(a. if (x < a. return new Avl(c. en prenant soin de r´´quilibrer a e e ee l’arbre ` chaque ´tape. } //else version sym´trique e if (H(a. null).H(a.filsG).filsD) == 2) { if (H(a.contenu.filsG.filsG) .filsD) == -2) { if (H(a. if(H(a.filsD. Pour l’insertion. a. } Il reste ` ´crire les m´thodes d’insertion et de suppression. Les rotations sont reprises de la section pr´c´dente. static Avl rotationG(Avl a) { Avl b = a. else if (x > a.hauteur = 1 + Math. Avl a) { if (a == null) return new Avl(null. a. ARBRES EQUILIBRES 129 La m´thode calculerHauteur recalcule la hauteur d’un arbre ` partir des hauteurs de ses souse a arbres.contenu) a.filsG).filsG = inserer(x. x. L’usage de H permet de traiter de mani`re unifi´e le cas o` l’un de ses sous-arbres e e u serait l’arbre vide.filsD).filsG).

return equilibrer(a). e Nous consid´rons ici des arbres de recherche qui ne sont plus binaires. Si chaque e e e ee e e acc`s ` un nœud requiert un acc`s disque. si chaque nœud a de l’ordre de 1000 e e e fils. Chaque nœud interne d’un tel arbre contient. un disque en g´n´ral. qui sont rang´es dans un arbre. la hauteur d’un tel arbre — qui mesure le nombre d’acc`s disques n´cessaire — est alors tr`s faible. ` minimiser les acc`s au disque.filsD == null) return a. 4092 ou 8192 octets). a. on a int´rˆt ` avoir des nœuds dont le nombre de fils e a e ee a est voisin de la taille d’une page. L’int´rˆt des arbres ´quilibr´s est qu’ils permettent des modifications en temps logarithmique. ARBRES BINAIRES static Avl dernierDescendant(Avl a) // inchang´e e { if (a.filsD == null) return equilibrer(a.2 B-arbres et arbres a-b Dans cette section.filsD == null) return null. e Les B-arbres ou les arbres a-b servent. En effet. Dans cette cat´gorie e d’arbres. dans ce contexte. En effet. mais d’arit´ plus e e grande. La e page est l’unit´ de transfert entre m´moire centrale et disque. en plus des r´f´rences vers ses sous-arbres. a. // seul changement } CHAPITRE V. Avl b = dernierDescendant(a. Or.filsG). a. } 3. nous d´crivons de mani`re succinte les arbres a-b. e e ee e les nœuds internes pouvant avoir un nombre variable de fils (ici entre a et b). et les e e e e donn´es sont donc accessibles seulement sur la m´moire de masse.filsG == null && a.contenu = b. ee . on trouve aussi les B-arbres et en particulier les arbres 2-3-4.filsG). e Les donn´es sont en g´n´ral rep´r´es par des cl´s. il suffit d’un arbre de hauteur 3 pour stocker un milliard de cl´s. Les arbres rouge et noir (ou bicolores) sont semblables. if (a. a e Un disque est divis´ en pages (par exemple de taille 512. 2048. et de les manipuler de concert. a. il survient un autre probl`me.filsG).filsD = supprimer(x. if (x < a. a. Il s’agit d’une des e e variantes d’arbres ´quilibr´s qui ont la propri´t´ que toutes leurs feuilles sont au mˆme niveau. ` savoir e e e a l’acc`s proprement dit aux donn´es. en moyenne. Il est donc rentable de grouper e e les donn´es par blocs.contenu. De plus. ee e e Lorsque l’on manipule de tr`s grands volumes de donn´es.filsG = supprimer(x.filsD).contenu) a.filsG = supprimer(a.filsG). if (a. les donn´es ne tiennent pas en m´moire vive. else a.130 return supprimerRacine(a). return equilibrer(a). return dernierDescendant(a.filsD). // seul changement } static Avl supprimerRacine(Avl a) { if (a.filsD). environ autant de temps que 200 000 instructions.filsG == null) return equilibrer(a.contenu. un e e e e seul acc`s disque peut prendre.

si un nœud interne poss`de d + 1 sous-arbres e e e e A0 . alors il est muni de d balises k1 . les cl´s aux nœuds internes ne servent qu’` la navigation. e e u e k1 k2 ··· kd cl´s e k1 A0 cl´s e ]k1 .1 Arbres a-b Soient a 2 et b 2a − 1 deux entiers. o` chaque ci est une cl´ du sous-arbre Ai (cf. Plus pr´cis´ment. on d´termine le sous-arbre Ai appropri´ en e e e e d´terminant lequel des intervalles ] − ∞. . . Les nœuds internes contiennent les balises. Il en r´sulte que pour chercher une cl´ c. e ee e Proposition 13 Si un arbre a-b de hauteur h contient n feuilles. 17 – Un arbre 2-4. e a les autres nœuds internes ont au moins a et au plus b fils. kd telles que c0 k1 < c1 ··· kd < cd pour toute s´quence de cl´s (c0 . k2 ]. e et les feuilles contiennent les cl´s. . e la racine a au moins 2 fils (sauf si l’arbre est r´duit ` sa racine) et au plus b fils. Un arbre a-b est un arbre de recherche tel que les feuilles ont toutes la mˆme profondeur. kd ]. . Un nœud a entre 2 et 4 fils. e e 3. . ]kd . e e a 8 20 40 6 4 7 10 11 12 14 12 14 18 21 21 26 30 25 30 36 41 42 50 Fig. k2 ] A1 cl´s e ]kd−1 . ∞[ contient la cl´. 16 – Un nœud interne muni de balises et ses sous-arbres. Ad .. . L’arbre de la figure 17 repr´sente un arbre 2-4. c’est-`-dire des valeurs de cl´ qui permettent de d´terminer le sous-arbre o` se a e e u trouve l’information cherch´e. . figure 16). Les B-arbres sont comme les arbres a-b avec b = 2a − 1 mais avec une interpr´tation e diff´rente : les informations sont aussi stock´es aux nœuds internes. k1 ]. .2. . . Les arbres 2-3 sont les arbres obtenus quand a et b prennent leurs valeurs minimales : tout nœud interne a alors 2 ou 3 fils. dans les arbres e e que nous consid´rons. cd ). .. ARBRES EQUILIBRES 131 des balises. L’int´rˆt des arbres a-b est justifi´ par la proposition suivante. ]k1 . kd ] Ad−1 cl´s e > kd Ad Fig. . alors que. alors log n/ log b h < 1 + log(n/2)/ log a .]kd−1 . . . .´ ´ 3.

Il r´sulte de la proposition pr´c´dente que la hauteur d’un arbre a-b ayant n feuilles est en e e e O(log n). Si un nœud a b + 1 fils. le nœud aux balises 11. Au total. 12. Il s’agit de fusionner un nœud avec un nœud fr`re lorsqu’il n’a plus assez de fils. Il est clair que l’insertion d’une nouvelle cl´ peut au pire faire ´clater les nœuds sur le e e chemin de son lieu d’insertion ` la racine — et la racine elle-mˆme. Mais si son fr`re (gauche ou droit) a beaucoup de fils. 3. Le coˆt est donc born´ a e u e logarithmiquement en fonction du nombre de feuilles dans l’arbre. donc 2ah−1 n bh .2. Pour l’insertion. le deuxi`me e e c e les fils de droite. Un nœud a entre 2 et 4 fils. et une balise appropri´e : la balise est la plus petite valeur de la cl´ ` ins´rer et e ea e de la cl´ ` sa droite. On groupe ces deux op´rations sous a e e la forme d’un partage (voir figure 19). D’abord. l’emplacement de la feuille o` l’insertion doit avoir lieu. ARBRES BINAIRES Preuve. de la cl´ 15 dans l’arbre 17 produit l’arbre de la figure 18. L’insertion a e a a e e 12 8 6 4 7 10 11 12 14 14 15 15 18 21 20 40 21 26 30 25 30 36 41 42 50 Fig. 18 – Un arbre 2-4. ea Reste le probl`me du r´´quilibrage qui se pose lorsque le nombre de fils d’un nœud d´passe e ee e le nombre autoris´. Noter que b + 1 2a.3 Suppression dans un arbre a-b Comme d’habitude. 14 de l’arbre 17 est ´clat´ en deux. ce qui fait augmenter e e e la hauteur de l’arbre. 3. Cette insertion se fait par un double e ´clatement.2 Insertion dans un arbre a-b La recherche dans un arbre a-b repose sur le mˆme principe que celle utilis´e pour les arbres e e binaires de recherche : on parcourt les balises du nœud courant pour d´terminer le sous-arbre e dans lequel il faut poursuivre la recherche. Il y a donc au plus bh feuilles. La complexit´ des algorithmes d´crit ci-dessous (insertion et suppression) sera donc e e elle aussi en O(log n). Mais alors. e e e la racine de l’arbre 17 a un nœud de trop. et donc chaque nouveau nœud aura au moins a fils. c’est-`-dire si son nombre de fils descend au dessous e a de a.132 CHAPITRE V. Les balises sont ´galement partag´es.2. La racine elle-mˆme est ´clat´e. et la racine en a au moins 2. la suppression est plus complexe. on commence par d´terminer. la fusion des deux nœuds risque de e conduire ` un nœud qui a trop de fils et qu’il faut ´clater. et la balise centrale restante est transmise au nœud p`re. pour e e e qu’il puisse ` son tour proc´der ` l’insertion des deux fils ` la place du nœud ´clat´. On ins`re alors une u e nouvelle feuille. Tout nœud a au plus b fils. Tout nœud autre que la racine a au moins a fils. . par e une recherche. alors il est ´clat´ en deux nœuds qui se partagent e e e les fils de mani`re ´quitable : le premier nœud re¸oit les ⌊(b + 1)/2⌋ fils de gauche. il y a au moins 2ah−1 feuilles.

ARBRES EQUILIBRES 133 8 20 6 4 7 10 11 12 21 21 26 30 25 30 36 4 6 7 8 21 20 10 21 25 26 30 30 36 Fig. e e e – Sinon. Alors le nombre total d’´clatements. et la fusion avec l’un des deux produit un nœud e ayant 2a − 1 b fils. Ceci signifie donc qu’en moyenne. ıne L` encore. Si un nœud poss`de e e a − 1 fils examiner les fr`res adjacents. e – Si l’un des fr`res poss`de au moins a + 1 fils. 1. et ce. 20 – La suppression de 13 entraˆ la fusion des deux nœuds de droite. les fr`res adjacents ont a fils. l’arbre est encore a-b. puis la balise figurant sur le chemin de la feuille ` la racine. e Plus pr´cis´ment. 8 20 6 6 7 10 11 13 21 21 25 6 6 7 10 8 20 21 21 25 Fig.´ ´ 3. le coˆt est major´ par un logarithme du nombre de feuilles. a Si les nœuds ainsi modifi´s ont toujours a fils. l’algorithme est le suivant : e e Supprimer la feuille. e ee quel que soit sa taille ! Des r´sultats analogues valent pour des valeurs de a et b plus grandes. de partage et de fusions est au e plus 3n/2. faire un partage avec ce fr`re. e . Il faut noter qu’il s’agit a u e du comportement dans le cas le plus d´favorable. On peut en effet d´montrer e e Proposition 14 On consid`re une suite quelconque de n insertions ou suppressions dans un e arbre 2-4 initialement vide. 19 – La suppression de 12 est absorb´e par un partage. 5 op´rations suffisent pour r´´quilibrer l’arbre.

134 CHAPITRE V. ARBRES BINAIRES .

Dans le premier cas. 1. .Chapitre VI Expressions r´guli`res e e 1 1. . Dans l’´criture m = m1 m2 . m1 est le pr´fixe du e ee e e mot m. mais on omet souvent cet op´rateur et on note donc souvent la concat´nation e e e m1 · m2 par une simple juxtaposition m1 m2 . e L0 = { ǫ } Ln+1 = Ln · L L⋆ = i∈N Li C’est-`-dire qu’un mot m de L∗ est la concat´nation de n mots (n ≥ 0) m1 . dans le second cas. est l’ensemble des suites finies de caract`res. L’ensemble des mots e e e sur Σ. La concat´nation s’´tend naturellement aux ensembles de mots. a Par exemple si Σ est l’ensemble des lettres usuelles. on peut d´finir le langage L1 des mots e fran¸ais. La concat´nation e e e de deux mots m1 et m2 est le mot obtenu en mettant m1 ` la fin de m2 . ou une suite non vide de chiffres qui ne commence pas par z´ro )).1 Langages r´guliers e Les mots On se donne un ensemble Σ de caract`res. e e 135 . Les expressions e e r´guli`res (ou rationnelles) sont une notation tr`s commode pour d´finir des langages de mots e e e e du style de L1 et L2 ci-dessus. Parmi les mots de Σ∗ e on distingue le mot vide not´ ǫ. D´finissons tout de suite quelques notations sur les mots et les langages. Un langage est un sous-ensemble e e de Σ∗ . not´es p. tandis que m2 est le suffixe du mot m. Le mot vide est l’unique mot de longueur z´ro. sont d´finies ainsi : e e e e Le mot vide ǫ est une expression r´guli`re. On note · l’op´rateur a e de concat´nation. parfois d´nomm´ alphabet.2 Syntaxe des expressions r´guli`res e e Les expressions r´guli`res ou motifs. on peut recourir ` une d´finition du style (( un entier (d´cimal) est c a e e le chiffre z´ro. La concat´nation est une op´ration associative qui e e poss`de un ´l´ment neutre : le mot vide ǫ. e L1 · L2 = {m1 · m2 | m1 ∈ L1 ∧ m2 ∈ L2 } Enfin on note L⋆ le langage obtenu en concat´nant les mots de L. Ou encore si Σ est l’ensemble des chiffres de 0 ` 9. c’est-`-dire un ensemble particulier de mots. on peut tout simplement tene ter de d´finir L1 comme l’ensemble de tous les mots pr´sents dans le dictionnaire de l’Acad´mie e e e fran¸aise. . not´ Σ∗ . o` les mi a e u sont tous des mots de L. on peut d´finir le langage L2 des c a e repr´sentations en base 10 des entiers naturels. mn . on note L1 · L2 le langage e e obtenu en concat´nant tous les mots de L1 avec les mots de L2 .

EXPRESSIONS REGULIERES Un caract`re c (un ´l´ment de l’alphabet Σ) est une expression r´guli`re.136 ´ ` CHAPITRE VI. c}. c’est-`-dire comme l’arbre de gauche ou comme l’arbre de droite de la figure 2. la r´p´tition. On doit sans doute remarquer qu’en r´alit´. e e . Il y a deux ıt e e sortes de feuilles. Comme e e e Fig. il en va de mˆme pour l’alternative. nous avons ´crit ab*|ba* et non pas e e e ⋆ | ba⋆ . b. e e a e Ainsi. mot vide et caract`res . on peut comprendre abc comme a(bc) ou e e ıt´ comme (ab)c. En effet. enfin la e e r´p´tition se comporte comme la mise ` la puissance. e ee e e Si p1 et p2 sont des expressions r´guli`res. e e e e e e On reconnaˆ ici la d´finition d’un arbre. nos e ıt´ e e e priorit´s ne l`vent pas toutes les ambigu¨ es. Comme d’habitude pour lever les ambigu¨ es e e ıt´ dans l’´criture lin´aire d’un arbre. a ou plus informatiquement comme l’arbre de la figure 1. alors l’alternative p1 | p2 est une expression e e r´guli`re. le motif ab*|ba* est ` comprendre comme ((a)(b*))|((b)(a*)). elle-mˆme plus prioritaire que l’alternative) et ` des parenth`ses. car l’op´ration de concat´nation des mots ıt´ e e est associative. on a recours ` des priorit´s (` savoir. la r´p´tition est plus e e a e a e e prioritaire que la concat´nation. e Cela l`ve toutes les ambigu¨ es d’entr´e de jeu. si n´cessaire e e e nous le repr´senterons donc par exemple comme (). et une sorte de nœud unaire. a Au fond cette ambigu¨ e n’a ici aucune importance. alors la concat´nation p1 ó p2 est une expression e e e r´guli`re. e Avec un peu d’habitude. et mˆme d’un arbre de syntaxe abstraite. e e a Dans ce polycopi´ nous exprimons les expressions r´guli`res sous la forme (( programme )) e e e et non pas d’expression math´matiques Concr`tement. e e Si p1 et p2 sont des expressions r´guli`res. e e Si p est une expression r´guli`re. pour Σ = {a. on comprend nos ´critures comme des arbres de syntaxe abstraites. 1 – L’arbre de syntaxe abstraite de l’expression ab*|ba* | ó a * b b ó * a on peut noter l’analogie avec les expressions arithm´tiques : les op´rations de concat´nation e e e et d’alternative ont les mˆme priorit´s respectives que la multiplication et l’addition. Il en r´sulte que la concat´nation des motifs est ´galement associative. alors la r´p´tition p* est une expression r´guli`re. deux sortes de nœuds binaires. Ce parti pris entraˆ ab ıne que le motif vide ne peut plus ˆtre donn´ par ǫ. Pour se souvenir des priorit´s adopt´es e e Fig. concat´nation et altere e native . 2 – Deux arbres possibles pour abc ó a b ó ó c a ó b c nous allons le voir imm´diatement.

C’est tout simplement une d´finition e e e e inductive. mn } est un langage fini contenant n mots. Les repr´sentations d´cimales des entiers sont ´galement un langage r´gulier e e e e d´fini par : e 0|(1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)* Exercice 1 Montrer qu’un langage qui contient un nombre fini de mots est r´gulier. et en r`gles d’inf´rence e e e proprement dites qui sont les cas inductifs de la d´finition. on dit aussi que le motif p filtre le mot m. Un langage qui peut ˆtre d´fini comme e e e la valeur d’une expression r´guli`re est dit langage r´gulier. Lorsque m appartient au langage eae e d´fini par p. ´galement not´e p m. . qui ` chaque expression r´guli`re p associe un sous-ensemble [[p]] de Σ∗ .4 Filtrage La relation de filtrage m ∈ [[p ]]. on a presque rien ´crit en fait ! e e e e Tout a d´j` ´t´ dit ou presque dans la section 1. Empty) qui sont les cas de base de la d´finition. e Solution. e e e Exemple 1 Nous savons donc que le langage des mots du fran¸ais selon l’Acad´mie est r´gulier. LANGAGES REGULIERS 137 1. Les r`gles se d´composent e e e e e en axiomes (par ex.3 S´mantique des expressions r´guli`res e e e Une expression arithm´tique a une valeur (plus g´n´ralement on dit aussi une s´mantique) : e e e e c’est tout simplement l’entier r´sultat du calcul. e e e e Fig. 3 – D´finition de la relation p filtre m e Seq OrLeft OrRight Empty Char ǫ ǫ c c p1 m1 p1 p2 p2 m1 m2 m2 p1 p1 | p2 m m m2 p2 p1 | p2 m m StarEmpty StarSeq p* ǫ p m1 p* p* m1 m2 qui est tr`s courant. peut ˆtre d´finie directement par les e e e e r`gles de la figure 3. . alors L est exactement d´crit par l’alternative de tous les mi . Il permet la d´finition inductive d’un pr´dicat.´ 1.1 sur les mots. Les r`gles sont ` comprendre comme e e a . e 1. Il est logique que la d´finition de la s´mantique reprenne e e la structure de la d´finition des expressions r´guli`res. Si L = {m1 . . . La d´finition de p m est faite selon le formalisme des r`gles d’inf´rence. m2 . c e e puisque qu’il est d´fini par une grosse alternative de tous les mots du dictionnaire publi´ par e e cette institution. De mˆme. une expression r´guli`re a une valeur e e e e qui est ici un ensemble de mots. a e e [[ǫ]] [[c]] [[p1 | p2 ]] [[p1 ó p2 ]] [[p*]] = = = = = {ǫ} {c} [[p1 ]] ∪ [[p2 ]] [[p1 ]] · [[p2 ]] [[p]]∗ Cette d´finition a le parfum usuel des d´finitions de s´mantique.

l’emploi de ces notations n’auge e mente pas la classe des langages r´guliers. En enchaˆ ınant les r`gles on obtient une preuve effective du e pr´dicat. les r`gles d’inf´rences sont souvent commodes.. e e on trouve une suite des classes de caract`res suivantes : e Un caract`re c se repr´sente lui-mˆme. Selon e les circonstances. e e e Le joker. m ∈ [[p]] ou p m. p* et p** d´finissent le mˆme langage. e e 2 Notations suppl´mentaires e En pratique on rencontre diverses notations qui peuvent toutes ˆtre exprim´es ` l’aide des e e a constructions de base des expressions r´guli`res. . e La r´p´tition au moins une fois p+ d´finie comme pp*. On a donc [[. il est souvent plus commode de se servir des e e e op´rations sur les langages. Une notation possible pour les entiers relatifs est . Ces motifs e ee e sont des abr´viations de l’alternative not´es entre crochets [. Voici par exemple la preuve de ab*|ba* baa. pour montrer qu’un motif filtre un mot. e e e a a b b ba* ab*|ba* a baa a a* a* ≺ aa baa a* a ǫ Nous disposons donc de deux moyens d’exprimer un langage r´gulier. qui est l’alternative de tous les caract`res de Σ. e e Le motif optionnel p ? d´fini comme p|ǫ. e e Exemple 2 Ainsi une notation plus conforme ` ce que les langages informatiques acceptent en a fait d’entiers d´cimaux est : e (0|1|2|3|4|5|6|7|8|9)+ C’est-`-dire une suite non-vide de chiffres d´cimaux. En revanche. sans chercher ` ´viter d’´ventuels z´ros a e a e e e initiaux. Par exemple. appel´e arbre de d´rivation.]. e e pour montrer des ´galit´s de langages r´guliers.c2 repr´sente les caract`res dont les codes sont compris au sens large e e entre les codes de c1 et c2 . Prouver l’´quivalence p* m ⇐⇒ p** m est plus p´nible. Autrement dit. alors la conclusion e (au dessous du trait) est vraie. e Exercice 2 Prouver que pour tout motif p. mais permet simplement d’´crire des expressions e e r´guli`res plus compactes. e e Solution. Donc.?(0|1|2|3|4|5|6|7|8|9)+ C’est-`-dire un entier naturel pr´c´d´ ´ventuellement d’un signe moins.]] = Σ. au sein de ces crochets.138 ´ ` CHAPITRE VI. . Pour tout langage on a (L⋆ )⋆ = L⋆ par d´finition de l’op´ration de r´p´tition sur les e e e e langages. not´ . . EXPRESSIONS REGULIERES des implications : quand toutes les pr´misses (au dessus du trait) sont vraies. il est plus facile d’employer l’un ou l’autre de des moyens. a e e ee Il existe ´galement toute une vari´t´ de notations pour des ensembles de caract`res. e e e Un intervalle c1 .

e e Exercice 3 Donner une expression r´guli`re compacte qui d´crit la notation adopt´e pour les e e e e ` savoir. en d´finissant Q comme e e l’ensemble des mots dont aucun sous-mot n’est */ et qui ne se terminent pas par une suite non-vide d’´toiles. Il y a peu ` dire ` part : a a (0|[1-9][0-9]*)|0x[0-9a-fA-F]+|0[0-7]+ On note que. Il n’est pas l’usage de citer les caract`res quand c’est inutile. NOTATIONS SUPPLEMENTAIRES 139 La notation [^. ainsi [^*] repr´sente tous e e les caract`res sauf l’´toile. Soit Q le langage du motif q. e Exemple 3 Une notation compacte pour les entiers d´cimaux est [0-9]+.´ 2. e e Enfin certains caract`res sp´ciaux sont exprimables ` l’aide de notations assez usuelles. Nous donnons ensuite ` q la forme q = r*. mais permet aussi d’exprimer e e les caract`res actifs des expressions r´guli`res comme eux-mˆmes. De mˆme.Autrement dit. Cela fonctionne parce e que dans tous les codages raisonnables les codes des chiffres sont cons´cutifs. A e e e 0x. 0 est d´cimal. on a donc p = q\**. l’expression r´guli`re e e e [a-zA-Z] repr´sente toutes les lettres minuscules et majuscules (non-accentu´es). 00 octal. Dans le cas du compl´mentaire. ce qui est e e bien ´videmment le cas en pratique. e a Solution. cela suppose un alphabet Σ fini.] se comprend comme le compl´mentaire des classes de caract`res donn´es. bien ´videmment. un commentaire commence par /* et s’´tend jusqu’` */. Notons tout de suite qu’un mot de P peut se terminer par une suite d’´toiles. les chiffres de 10 ` 15 ´tant repr´sentes par les lettres minuscules ou majuscules de a ` f). du e e a style \n est le caract`re fin de ligne et \t est la tabulation etc. le caract`re barre oblique inverse se e e e e note \\. e e Exemple 4 Nous sommes d´sormais en possession d’un langage de description des ensembles de e mots assez puissant. On vise un motif de la forme /\*p\*/. et 09 interdit. . Il e e e est clair que les notations de classes de caract`res peuvent s’exprimer comme des alternatives e de motifs caract`res. c’est un moyen simple de prendre e a . Le caract`re barre oblique e e inverse (backslash) introduit les notations de caract`res sp´ciaux. e e e ` A savoir. Exercice 4 Donner une expression r´guli`re qui d´crit l’autre sorte de commentaires de Java. comme on doit identifier le premier sous-mot */ pour fermer le commentaire. selon l’expression donn´e. les commentaires commencent par le sous-mot /* et se terminent par le sous-mot */. //[^\n]*\n C’est-` dire qu’un commentaire commence par deux / et se poursuit jusqu’` la fin de la ligne a a courante. Notons que le motif //. Par e e e e a exemple le caract`re ´toile se note \* et. Par exemple voici le motif qui d´crit une des deux sortes de commentaires e Java.*\n ne convient pas. c’est ` dire de les citer. puisqu’en e e Java par exemple (en ASCII en fait) les codes des lettres sont cons´cutifs. notation d´cimale (sans z´ro initiaux). car il filtre par exemple : //Commentaire avant i++ . a e e a et notation octale (introduite par un z´ro). hexad´cimale (introduite par entiers par Java. le motif p doit correspondre au langage P de tous les mots dont */ n’est pas un sous-mot. et. . Tandis que e e [^a-zA-Z] repr´sente tous les caract`res qui ne sont pas des lettres. e Solution.

Il faut maintenant decomposer un mot (non-vide) m comme un pr´fixe appartenant ` R (langage de r) et un suffixe m′ . alors le dernier caract`re du pr´fixe n’est pas une e e e ´toile. m′ ∈ Q. F. alors cette ´toile peut e c e ˆtre suivie d’autres. e e . on pose un pr´fixe d´crit par \*+[^*/]. Le sujet est extrˆmement vaste. Soit r = [^*]|\*+[^*/].140 ´ ` CHAPITRE VI. Au final. voire de nombreuses transformations de ces fichiers. et obligatoirement d’un caract`re qui n’est ni * ni /. ensuite on trouve e e le suffixe m′ . R´ciproquement si. et on ne peut pas introduire le sous-mot */ en y ajoutant m′ . tels que e a m est dans Q. Si m commence par un caract`re c qui n’est pas l’´toile. 4 – Syntaxe abstraite de /\*([^*]|\*+[^*/])*\*+/ ó / \* ó ó ó * | + \* / [^*] + \* ó [^*/] 3 Programmation avec les expressions r´guli`res e e Dans cette section nous nous livrons ` une description rapide de quelques outils qui emploient a les expressions r´guli`res de fa¸on cruciale. alors m se d´compose facilement e e e ′ . EXPRESSIONS REGULIERES en compte que Q contient des mots de longueur arbitraire. Il est alors clair que e e m′ ∈ Q. mˆme si m′ commence e e par /. comme m = cm e e Si m commence par une ´toile. et l’´quivalence est ´vidente. Le livre de Jeffrey E. Friedl [3] traite des expressions r´guli`re du point de vue des outils. ea Fig. car le formalisme e e c e des expressions r´guli`res est suffisamment expressif pour permettre de nombreuses recherches e e dans les fichiers textes. si et seulement si m′ est dans Q. Commen¸ons par supposer m ∈ Q. L’arbre de syntaxe abstraite e e e est donn´ ` la figure 4. la solution est : /\*([^*]|\*+[^*/])*\*+/ La syntaxe concr`te est celle introduite dans les sections pr´c´dentes. Autrement dit.

regexp. l’argument text ´tant le mot m.*i. donn´ sous forme d’un fichier texte. Pour le moment. .2 Recherche de lignes dans un fichier. jusqu’` produire un automate (voir le e e a a chapitre suivant). La syntaxe des motifs est relativement conforme ` ce que e a nous avons d´j` d´crit. e e le Matcher obtenu est donc la combinaison d’un motif p et d’un mot m.*i. on pourra effacer tous les fichiers objets Java par la commande : % /bin/rm *.*i.*). mais c’est toujours prudent. . e ıne e par souci d’efficacit´. on peut compter toutes les lignes des fichiers source du r´pertoire e e e courant : % wc -l *. il offre (entre autres !) 1 On obtient le d´tail du fonctionnement d’une commande Unix cmd par man cmd. a e Ainsi. que le fichier /usr/share/dict/ ea e french de votre machine Unix est un dictionnaire fran¸ais. e 3.3 En Java Le support pour les expressions r´guli`res est assur´ par les classes Pattern et Matcher e e e du package java. En fait.java La commande wc1 (pour word count) compte les lignes.). En simplifiant. Ce e e a n’est en fait pas toujours n´cessaire. Notons simplement que ? repr´sente un caract`re quelconque (not´ e e e e pr´c´demment . Enfin. comme c’est souvent le cas. il suffit de consid´rer qu’un Pattern est la forme Java d’un e motif. Pour confronter un Pattern p ` un mot m il faut fabriquer cette fois un Matcher en invoa quant la m´thode matcher(String text) de p. e . L’option (( -l )) restreint l’affichage au seul compte des lignes. c e a ` raison d’un mot par ligne.util. PROGRAMMATION AVEC LES EXPRESSIONS REGULIERES 141 3. e % egrep ’i. elle proc`de ` bien plus de travail. Les objets de la classe Pattern impl´mentent les motifs. ceci afin d’´viter que le e e shell n’interpr`te les ´toiles comme faisant partie d’un motif ` appliquer aux noms de fichier.*i.´ ` 3.compile qui prend une chaˆ repr´sentant e ıne e un motif en argument et renvoie un Pattern. la commande egrep La commande egrep motif fichier affiche toutes les lignes de fichier dont motif filtre un sous-mot (et non pas la ligne enti`re).p2 } (pour p1 | p2 ).??. On pourrait penser que la m´thode compile e interpr`te la chaˆ donn´e en argument et produit un arbre de syntaxe abstraite.???} 3.class Dans le mˆme ordre d’id´e. et e sont construits par la m´thode statique Pattern. et que e e e e e e l’alternative se note ` peu pr`s {p1 . on e peut faire la liste de tous les fichiers du r´pertoire courant dont le nom comprend de un ` trois e a caract`res : e % ls {?. On peut alors trouver les mots qui contiennent au moins six fois la lettre (( i )) de cette mani`re. mots et caract`res dans les fichiers e donn´s en argument. La syntaxe concr`te des motifs est e e franchement particuli`re.*i’ /usr/share/dict/french indivisibilit´ e On notera que l’argument motif est donn´ entre simples quotes (( ’ )).1 Dans l’interpr`te de commandes Unix e Le shell c’est ` dire l’interpr`te de commandes Unix reconnaˆ quelques expressions r´guli`res a e ıt e e qu’il applique aux noms des fichiers du r´pertoire courant. Supposons. tandis que * repr´sente un mot quelconque (not´ pr´c´demment .

2. Ceci permet d’abord d’obtenir des informae tions suppl´mentaires. la transformation des chaˆ e ınes vers les motifs est coˆteuse et on souhaite la rentabiliser.group()) .println(m. la ligne est affich´e sur la sortie standard. mais au del` du dernier sous-mot identifi´ par find. apr`s construction du Pattern pat. un moyen assez compliqu´ de savoir si un mot mot contient le e sous-mot sous au moins deux fois est d’´crire : e static boolean estSousMotDeuxFois(String sous. } } On notera qu’il n’est pas imm´diat que le code ci-dessus affiche bien tous les entiers de e la chaˆ text. En effet si text est par exemple "12" les deux affichages 12.compile(sous + ". ceci afin de pouvoir le lire ligne ` ligne .util. La documentation du e e e e langage offre de nombreuses pr´cisions. ou encore 1 ıne puis 2 sont corrects : il n’affichent que des sous-mots filtr´s par le motif "[0-9]+". enfin le code appelle a la m´thode grep.compile("[0-9]+"). par new FileReader(arg[1]) . par exemple.compile une seule fois et pratiquons de nombreux filtrages. si find r´ussit. l’applique ` toutes les e e e a lignes du fichier.2) in. u Par exemple dans notre Grep (figure 5). Mais. la documentation de la classe Pattern e comprend la description compl`te de la syntaxe des motifs qui est plus ´tendue que ce que nous e e .matcher(text) . La solution ` mon avis la plus satisfaisante est sp´cifier que le sous-mot e a e filtr´ est d’abord le plus ` gauche et ensuite le le plus long. Dans ce code. while (m.*" + sous). Par ailleurs. Dans le cas a e de l’expression r´guli`re simple "[0-9]+". cela revient en effet ` filtrer les sous-mots les e e a plus longs. EXPRESSIONS REGULIERES les m´thodes matches() et find() qui renvoient des bool´ens. l’appel suivant ` find recherchera un sous-mot e a filtr´. L’existence de la classe Matcher s’explique autrement : les Matcher poss`dent un ´tat e e interne que les m´thodes de filtrage modifient.find()) { System. Il y a ´galement un souci d’efficae e cit´. c’est ` dire qu’il filtre le plus de caract`res possibles. Ici elle dit que l’op´rateur e e e e (( + )) est avide (greedy). alors on obtient le sous-mot filtr´ en e e e appelant la m´thode group(). puis enveloppe le fichier comme le BufferedReader (voir B.(). } Nous en savons maintenant assez pour pouvoir ´crire la commande egrep en Java. . e Plus g´n´ralement. . elle d´crit l’effet e e e e e de chaque op´rateur des expressions r´guli`re individuellement.6. la e classe Grep dont le code est donn´ ` la figure 5. la m´thode main se come a e porte principalement ainsi : elle ouvre le fichier dont le nom est donn´ comme second argument e de la ligne de commande. Ainsi. L’architecture du package java. L’existence de la classe Pattern se justifie d’abord par le souci d’abstraction : les auteurs ne souhaitent pas exposer comment sont impl´ment´s les motifs.matcher. e a e a e Tout cela permet par exemple d’extraire tous les entiers pr´sents dans une chaˆ e ıne. En cas de succ`s de l’appel ` find. Cette derni`re. afin de pouvoir changer e e cette impl´mentation dans les versions futures de Java. Par exemple. static void allInts(String text) { Matcher m = Pattern. La premi`re m´thode matches e e e e teste si le motif p filtre le mot m. e a e Le comportement global est donc bien celui de la commande egrep. La sp´cification de Java n’est e a e pas aussi g´n´rale : au lieu de dire globalement ce qui doit ˆtre filtr´. non plus ` partir du d´but. En particulier. nous appelons Pattern. Nous ne d´crirons pas plus en d´tail les expressions r´guli`res de Java.142 ´ ` CHAPITRE VI. tandis que la seconde find teste si le motif p filtre un sous-mot du mot m.find(mot) . et c’est tout ` ıtre e a vrai.regex peut paraˆ bien compliqu´e. sp´cifier compl`tement ce que fait le couple de m´thode find/group e e e e e est un peu d´licat. String mot) { return Pattern.out.

* . System. grep(arg[0].* .´ ` 3.close() .compile(p) .exit(2) . } } } . i f (m.getMessage()) .readLine() .err. String line = in. } catch (IOException e) { System.readLine() . // Pour Pattern et Matcher class Grep { // Affiche les lignes de in dont un sous-mot est filtr´ par le motif p e static void grep(String p.length != 2) { System.java import java. 5 – La commande egrep en Java. } } public static void main(String [] arg) { i f (arg.println("Usage: java Grep motif fichier") .util.exit(2) . while (line != null) { Matcher m = pat.println("Malaise: " + e. } try { BufferedReader in = new BufferedReader (new FileReader (arg[1])) . in. } line = in.regex.err. source Grep. System. PROGRAMMATION AVEC LES EXPRESSIONS REGULIERES 143 Fig.find()) { System.io.out.matcher(line) . // Pour BufferedReader import java. BufferedReader in) throws IOException { Pattern pat = Pattern. in) .println(line) .

beaucoup moins sophistiqu´e que e e e e celle adopt´e notamment par la biblioth`que Java. OR=3. 4.asChar = c . STAR=5 . Par e e exemple. et deux champs p1 et p2 utiles pour les nœuds internes qui ont au plus e deux fils. alors e e que les motifs sont souvent en pratique dans des chaˆ ınes. un champ asChar utile quand le motif est e e un caract`re (tag CHAR). WILD=2. If faut donc en plus tenir compte des r`gles de citation dans les chaˆ e ınes. Attention tout de mˆmes les descriptions de motifs sont donn´es dans l’absolu. on note la pr´sence de nœuds (( WILD )) qui repr´sentent les jokers.4 sur les arbres de syntaxe abstraite. . mˆme avec des teche e e niques simples. .regexp et ´crire e nous aussi un package que nous appelons regex tout court. } Pour cr´er un motif r´p´tition. on pourra. A savoir nous d´finissons une classe Re des nœuds de l’arbre de syntaxe abstraite. return r . e e e Des constantes nomm´es identifient les cinq sortes de nœuds. Il s’agit d’une premi`re approche. e Ensuite nous d´finissons tous les champs n´cessaires. SEQ=4. e package regex . private Re p1. p2 . Ainsi le motif \p{L} filtre n’importe quelle lettre (y compris les lettres accentu´es) mais on le donne sous la forme de la chaˆ "\\p{L}". De fait nous allons imiter l’architecture du package java.util. La correspondance entre constante e e et sorte de nœud est directe. qui identifie leur classe comme appartenant ` ce a package. CHAR=1. la sorte d’un nœud ´tant identifi´e par son champ tag. le constructeur par d´faut est red´fini et d´clar´ priv´.1 Arbres de syntaxe abstraite ` Nous reprenons les techniques de la section IV.144 ´ ` CHAPITRE VI. d´j` aborder les probl`mes de programmation pos´s et comprendre (( comment ea e e c ¸a marche )). car il faut citer un e ıne backslash avec un backslash ! 4 Impl´mentation des expressions r´guli`res e e e Le but de cette section est de d´crire une technique d’impl´mentation possible des exprese e sions r´guli`res en Java. En outre. Nous en profitons donc pour ´crire un package. Toutefois. on appelle : e e static Re charPat(char c) { // On ne peut pas nommer cette m´thode (( char )) e Re r = new Re() . il est pratique de regrouper ces fichiers dans un sous-r´pertoire nomm´ e e justement regex. private Re() {} . Tous les fichiers source du package regex e commencent par la ligne package regex . r. pour cr´er un motif caract`re. EXPRESSIONS REGULIERES avons vu ici. } Nous d´finissons cinq sortes de nœuds. on appelle : e e e EMPTY=0. private char asChar . . Enfin. class Re { private final static int private int tag . e e e e e On construira les divers nœuds en appelant des m´thodes statiques bien nomm´es.

static Re plus(Re p) { return seq(p. star(p)) . On peut par exemple ´crire facilement une m´thode e e plus qui construit un motif p+ comme pp*. de sorte qu’il est garanti que tous les champs utiles dans un e nœud sont correctement initialis´s. text) . un modeste remplacement de la classe hoe monyme de la biblioth`que Java. } // Fabriquer le M atcher public Matcher matcher(String text) { return new Matcher(pat. (cours INF 431). IMPLEMENTATION DES EXPRESSIONS REGULIERES static Re star(Re p) { Re r = new Re() . Mais ne soyons pas d´cus. e e e¸ . la politique de visibilit´ des noms est tr`s stricte. e e Les m´thodes statiques de construction ne se limitent ´videmment pas ` celles qui correse e a pondent aux sortes de nœuds existantes. Pour le moment nous ´vitons les automates et nous contentons e e de cacher un arbre Re dans un objet de la classe Pattern . /* Une classe Pattern simple : encapsulage d’un arbre de syntaxe abstraite */ public class Pattern { private Re pat . private Pattern(Re pat) { this. Rendre les champs priv´s interdira leur acc`s de l’ext´rieur e e e e e e de la classe Re. La partie la plus technique de la tˆche de la m´thode compile est e a e le passage de la syntaxe concr`te contenue dans la chaˆ patString ` la syntaxe abstraite e ıne a e ee e a e repr´sent´ par un arbre Re. c’est la m´thode statique compile qui appelle le e e constructeur. on peut remarquer que tous les champs et le constructeur sont priv´s. la classe Re n’est pas publique. 4. puisque si nous ne modifions pas les champs dans la classe Re.parse(patString) . } Du point de vue de l’architecture.2 Fabrication des expressions r´guli`res e e Nous pr´sentons maintenant notre classe Pattern . } } Comme dans la classe de la biblioth`que. return p .pat = pat . Au final. Elle renforce la s´curit´ de la programmation. r. return new Pattern(re) .p1 = p . op´ration d´l´gu´e ` la m´thode Re. Rendre le constructeur priv´ oblige les utilisateurs de la classe Re appeler les e e m´thodes statiques de construction. Nous ne savons pas e e ´crire cette m´thode d’analyse syntaxique (parsing).´ ´ ` 4.parse. } 145 Les autres autres m´thodes de construction sont ´videntes. La classe Re est donc compl`tement e e invisible pour les utilisateurs du package. ici priv´. e e nous pourrons ˆtre sˆrs que personne ne le fait. package regex . En outre. } // Cha^ne -> Pattern ı public static Pattern compile(String patString) { Re re = Re. son e u e acc`s est donc limit´ aux autres classes du package regex.

c)) . que nous allons d´crire. EXPRESSIONS REGULIERES nous pouvons d´j` par exemple construire le motif qui reconnaˆ au moins k caract`res c. On suivra la sp´cification de la e e ` savoir.empty() .charPat(c) . La technique adopt´e est a e franchement na¨ ıve. c))) } } Enfin.3).charPat(c). elle cherche ` identifier une sous-chaˆ filtr´e par e e a ıne e pat.3).seq(Re.end[ est filtr´e. a u e a ıne e Ce champ permet donc aux appels successifs de find de communiquer entre eux. } else i f (k == 1) { return Re. ıne e a e Notons que c’est notre parti-pris de rendre priv´s tous les champs de l’arbre de syntaxe des e expressions r´guli`re qui oblige ` ´crire toute m´thode qui a besoin d’examiner cette structure e e ae e comme une m´thode de la classe Re.3 Filtrage Le source de la classe Matcher (figure 6) indique que les objets contiennent deux champs pat et text. buildAtLeast(k-1. 4. Les deux champs e servent ` la communication entre un appel ` find et un appel subs´quent ` group (voir a a e a la fin de la section 3.seq (Re.146 ´ ` CHAPITRE VI.1. la m´thode matcher de de la classe Pattern se contente d’appeler le constructeur de e e notre modeste classe Matcher. pour le motif et le texte a filtrer. . mStart.6. Comme on pouvait s’y attendre. char c) { return new Pattern(buildAtLeast(k. e c’est-`-dire o` la m´thode find doit commencer ` chercher une sous-chaˆ filtr´e par pat. } else { return Re. ` partir de la position regStart et de la gauche vers la droite.wild()). ıne e Solution. } private static Re buildAtLeast(int k. e La valeur du champ regStart indique l’indice dans text du d´but de la recherche suivante. Re. on essaie tout simplement de filtrer successivement toutes les sous-chaˆ ınes commen¸ant ` une positon donn´e (start) des plus longues ` la chaˆ vide. Les champs mStart et mEnd identifient la position de la derni`re sous-chaˆ de text dont e ıne un appel ` find a d´termin´ que le motif pat la filtrait. le constructeur ` Matcher(Re pat.matches. on fait appel ` la m´thode statique Re. ` ajouter dans la classe Pattern . l’appel matches() teste le filtrage de toute l’entr´e classe Matcher de la biblioth`que. e a public static Pattern atLeast(int k. A e e par le motif et on peut utiliser group() pour retrouver la chaˆ filtr´e. La convention adopt´e est celle a e e e de la m´thode substring des objets String (voir la section B. e ´ Exercice 5 Ecrire la m´thode matches de la classe Matcher. On renvoie true c a e a ıne (apr`s mise ` jour de l’´tat du Matcher). char c) { i f (k <= 0) { return Re. en ea ıt e appelant la m´thode atLeast suivante. mEnd et regStart.matches et on affecte les champs mStart et mEnd selon a le r´sultat. String text) initialise ces deux champs. La m´thode find est la plus int´ressante. Mais les objets comportent trois champs suppl´mentaires. C’est simple : un appel ` Re. e . Pour savoir si e a e e ıne e e une sous-chaˆ text[start. .star(Re. d`s qu’une sous-chaˆ filtr´e est trouv´e.

. // Recommencer au d´but. // Commencer ` filtrer ` partir du d´but a a e = 0 . // Pas de sous-cha^ne reconnue regStart = 0 . end--) { i f (Re. mEnd = end . pat. mEnd = -1 . } } . 6 – Notre classe Matcher package regex . String text) { = pat . return text.´ ´ ` 4.matches(text.regex a public boolean find() { for (int start = regStart . // La derni`re sous-cha^ne filtr´e est text[mStart. end >= start . public class Matcher { private Re pat . Matcher(Re this.. start. private String text .util. si il y a lieu e ı e public String group() { ı e i f (mStart == -1) throw new Error("Pas de sous-cha^ne filtr´e") .substring(mStart. IMPLEMENTATION DES EXPRESSIONS REGULIERES 147 Fig. e regStart = mEnd .length() . mEnd . // Le prochain find commencera apr`s celui-ci return true . start++) for (int end = text. end)) { mStart = start . } // M´thode de recherche des sous-cha^nes filtr´es a peu pr`s e ı e e // conforme ` celle des Matcher de java. start <= text. this. // Les recherches commencent a cette position dans text ` private int regStart . mEnd) .text = text . } } ı mStart = mEnd = -1 .pat regStart mStart = } pat. // Aucun motif encore reconnu // Renvoie la derni`re sous-cha^ne filtr´e.length() . bizarre e return false .mEnd[ e ı e private int mStart.

asChar . .j[ est non-vide alors on ıne ıne cherche ` la d´composer en pr´fixe et suffixe et ` appliquer la r`gle StarSeq.j[ est la chaˆ dont nous cherchons ` savoir si elle est filtr´e par ıne a e pat. le cas de la r´p´tition q* est un peu plus subtil. i. j) .p2. } } Pour ´crire la m´thode matches de la classe Re. La s´quence (rule Seq) demande plus de travail. int j) { switch (pat. } return false . text. case SEQ: for (int k = i . et le joker filtre toutes les chaˆ ıne e e ınes de longueur un.charAt(i) == pat. . case OR: return matches(text. e e e case EMPTY: return i == j . int i. pat. j)) return true . La longueur de cette chaˆ est j-i. des caract`res et du joker est rapidement r´gl´. pat. il est d’abord clair (r`gle StarEmpty) e e e qu’un motif q* filtre toujours la chaˆ vide. j) || matches(text. pat. pat. k. En effet il faut essayer toutes les d´compositions e e en pr´fixe et suffixe de la chaˆ test´e.148 ´ ` CHAPITRE VI. } Notons bien que text[i. il suffit d’essayer les deux termes de e l’alternative (regles OrLeft et OrRight). le motif caract`re ne filtre ıne e que la chaˆ compos´e de lui mˆme une fois. i.z Et enfin. . . . 0. c’est ` dire la liste des cas du switch ci-dessus. return true . nous allons distinguer les divers motifs e e possibles et suivre la d´finition de p m de la figure 3. i. Si la chaˆ text[i. pat.tag) { . . faute de quoi nous ne pourrions pas renvoyer false avec e ıne e certitude. mEnd = text. EXPRESSIONS REGULIERES public boolean matches() { i f (Re.length())) { mStart = 0 . k) && matches(text.j[ static boolean matches(String text. Nous ´crivons maintenant le source du traitement des ıne e cinq sortes de motifs possibles. return false . } throw new Error ("Arbre Re incorrect") . . .p1. En effet.p1. case CHAR: return i+1 == j && text.p2. e // Test de pat text[i. } else { mStart = mEnd = -1 . k++) { i f (matches(text. le motif vide filtre la chaˆ vide et elle seule (j − i = 0). Le cas des motifs a vide.length() .matches(text. k <= j . Le cas de l’alternative est ´galement assez simple. a e e a e . case WILD: return i+1 == j . Re pat.

*i. nous recherchons les mots qui contiennent n fois c la lettre e. i. k++) { i f (matches(text.´ ´ ` 4. IMPLEMENTATION DES EXPRESSIONS REGULIERES case STAR: i f (i == j) { return true . nous poue vons compiler par javac ReGrep.4 Emploi de notre package regex Nos classes Pattern et Matcher sont suffisamment proches de celles de la biblioth`que pour e que l’on puisse. En effet. . pat. on ´vite le cas k = j ıne e qui correspond ` une division de la chaˆ test´e en pr´fixe vide et suffixe complet. nous recherchons les mots qui contiennent au moins n fois c la mˆme voyelle non accentu´e.*i|o.*o.5 16.*u)’ /usr/share/dict/french Grep ReGrep 1 2.7 15. sans ˆtre ridicule. } 149 On note un point un peu subtil. q ǫ q* q* m m L’inutilit´ de cette r`gle est particuli`rement flagrante.java. e (2) Toujours dans le dictionnaire fran¸ais. ce qui nous donne le nouveau source ReGrep.util. D`s lors. . } return false .4 4 1.*e. Si tel n’´tait a ıne e e e pas le cas.*(e|´|`|^). } else { for (int k = i+1 . pour n = 3 nous ex´cutons la commande : e e e % java Grep ’(a.*e|i.* en import regex.*a. j)) return true .5 18.8 6 1. le second appel r´cursif e e matches(text. k <= j . dans le cas d’une chaˆ non-vide. j) aurait alors les mˆmes arguments que lors de l’appel.java (figure 5). dans le source Grep.9 . changer la ligne import java.*. apr`s effacement des accents.8 4 1.*u.2 5 1.7 17. k) && matches(text. . dans le e e a sens qu’on ne risque pas de ne pas pouvoir prouver q* m parce que l’on abstient de l’employer. 4.1 2 2.0 3 1.9 3. et time java ReGrep .java et nous obtenons un nouveau programme ReGrep qui utilise notre impl´mentation des expressions r´guli`res ` la place de celle de la biblioth`que.5 9.6 6 1. k.*(e|´|`|^)’ /usr/share/dict/french e e e e e e e e e Grep ReGrep 1 2.2 9.6 15. pat. la m´thode matches pourrait ne pas terminer.0 7 1.6 18. Par exemple.7 3.9 12.0 On voit que notre technique. k. pour n = 3 nous ex´cutons la e e commande : % java Grep ’(e|´|`|^).regex. pat.*a|e. ` condition e a que le source des classes du package regex se trouve dans un sous-r´pertoire regex.5 15. Par exemple. est nettement moins efficace.7 3 1.p1. ).9 16. e e e a e Nous nous livrons ensuite ` des exp´riences en comparant les temps d’ex´cution (par les a e e commandes time java Grep . puisqu’une des pr´misses et la conclusion e e e e sont identiques.*o|u.9 5 1. . Un autre e point de vue est de consid´rer que l’application de la r`gle StarSeq ` ce cas est inutile. (1) Dans le dictionnaire fran¸ais.1 2 2.

EXPRESSIONS REGULIERES Cette exp´rience donne des r´sultats similaires ` la pr´c´dente. le langage c−1 ó L peut ˆtre vide.2 22 4. Plus pr´cis´ment d’une part e e a e e e e la biblioth`que est plus rapide en valeur absolue . on a l’´quivalence : e c−1 ó p m ⇐⇒ p cóm e ee Seuls les trois derniers cas de la d´finition de c−1 ó p pr´sentent un int´rˆt. o` =· · · = ıne e ıne u est le caract`re = r´p´t´ n fois. nous inventons imm´diatement un nouveau motif ∅. il semble bien que les temps se stabilisent dans les deux cas. que pour tout mot m. e e e Grep ReGrep 16 0. par exemple e ′ } avec c′ = c. en toute rigueur. c−1 ó L = {m | c ó m ∈ L} Dans le cas d’un langage L r´gulier engendr´ par le motif p.2 18 0.3 0. Qu’` cela ne tienne. (3) Nous recherchons une sous-chaˆ filtr´e par X(. le langage vide ∅ n’est si L = { c e e pas r´gulier. 5 5. et d’autre part. par induction sur la structure de p. nos temps d’ex´cution e e sont croissants.+)+X dans la chaˆ XX=· · · =.3 0. selon notre d´finition des langages r´guliers.2 24 18.6 0. On montre. de sorte qu’un motif est e e d´sormais ∅ ou un motif qui ne contient pas ∅.5 0. mais nous savons [3. on ´tend facilement les trois op´rateurs des e e e expressions r´guli`res.2 Et effectivement l’emploi de la biblioth`que conduit ` un temps d’ex´cution manifestement e a e exponentiel.1 Une autre approche du filtrage Langage d´riv´ e e Soit L un langage sur les mots et c un caract`re. d´fini e e e e comme les suffixes m des mots de L qui sont de la forme c ó m. Nous notons c−1 ó L le langage d´riv´. e On observera d’abord que. e .8 0. Mais et c’est impore e tant. e Proposition 15 Si L = [[p]]. e e ee e Chapitre 5] qu’elle risque de mettre en difficult´ l’impl´mentation de la biblioth`que. Cette reconnaissance doit ´chouer. e e ∅óp = ∅ pó∅ = ∅ ∅|p = p p|∅ = p ∅* = ǫ Ces r`gles nous permettent d’´liminer les occurrences internes de ∅. tandis que ceux de la biblioth`que sont d´croissants. Or.2 20 1.150 ´ ` CHAPITRE VI. engendr´ par le motif c−1 ó p e e ∅ ∅ ǫ ∅ pour c = c′ −1 ó p | c−1 ó p c 1 2 (c−1 ó p1 ) ó p2 si p1 ≺ ǫ (c−1 ó p1 ) ó p2 | c−1 ó p2 si p1 ǫ (c−1 ó p) ó p* Preuve. nous allons montrer que le langage e e c−1 ó L est r´gulier en calculant le motif c−1 ó p qui l’engendre. En consid´rant les valeurs [[p]]. alors le d´fini ainsi : e c−1 ó ∅ = c−1 ó ǫ = c−1 ó c = c−1 ó c′ = −1 ó(p | p ) = c 1 2 c−1 ó(p1 ó p2 ) = c−1 ó(p1 ó p2 ) = c−1 ó p* = langage c−1 ó L est r´gulier. Il est fascinant de constater que notre impl´mentation ne conduit pas ` cette e a explosion du temps de calcul. qui ne e a e filtre aucun mot.

  m = m1 ó m2 q c ó m1  q* m2 ǫ. e e . par induction. Un fois atteint e e e la fin du mot. il ne reste qu’` v´rifier si le motif d´riv´ filtre le mot vide. En revenant aux d´finitions. 5. UNE AUTRE APPROCHE DU FILTRAGE 151 Posons p = p1 ó p2 et supposons en outre p1 ǫ. il faut tenir compte d’une autre d´composition possible du mot c ó m u e en pr´fixe vide et suffixe complet. on peut tout simplement it´rer sur les caract`res du e e mot. en calculant les d´rivations successives de p par les caract`res consomm´s.2 Filtrage par la d´rivation des motifs e Pour savoir si un motif p filtre un mot m. Dans ce cas on a par induction : e p1 p2 Ce qui conduit ` l’´quivalence : a e                  m = m1 ó m2 c−1 ó p1 m1  p2 m2 p1 ǫ c−1 ó p2 m ǫ cóm ⇐⇒ p1 ǫ c−1 ó p2 m m p1 ó p2 ≤ c ó m ⇐⇒ Et on peut conclure. Alors on a : q* c ó m ⇐⇒ En toute rigueur. l’´quivalence r´sulte d’un nombre arbitraire applications de la r`gle Stare e e Seq pour un pr´fixe vide et d’une application de la mˆme r`gle pour la d´composition ´crite e e e e e ci-dessus. les deux r´sultats de la section pr´c´dente (proposition 15 et lemme 16) e e e e e nous permettent d’´crire deux nouvelles m´thodes statiques dans la classe Re. Le pr´dicat N (p) se calcule inductivement ainsi : e = = = = = = faux vrai faux N (p1 ) ∨ N (P2 ) N (p1 ) ∧ N (P2 ) vrai Preuve. a e e e Plus pr´cis´ment. Par ailleurs. e puis par d´finition.5. Induction facile sur la structure des motifs. On peut ensuite conclure par induction. Posons p = q*. on a les ´quivalences successives : e e    m = m1 ó m2  m = m1 ó m2 p1 c ó m1 c−1 ó p1 m1 ⇐⇒ (c−1 ó p1 ) ó p2 ⇐⇒ p1 ó p2 c ó m ⇐⇒   p2 m2 p2 m2 Dans le cas o` p1 ǫ. on d´cide de la validit´ de p e e Lemme 16 On note N (p) le pr´dicat p e N (∅) N (ǫ) N (c) N (p1 | p2 ) N (p1 ó p2 ) N (p*) ǫ par une simple induction structurelle.

La a recherche s’arrˆte quand la chaˆ est lue en totalit´ ou quand le motif d´riv´ est ∅. // Trouv´ le pr´fixe vide e e for (int k = start . k++) { d = Re. en se souvenant (dans found) du filtrage le plus long. d) .length() .nullable(d)) found = start . int found = -1 . tout e e e en identifiant la sous-chaˆ filtr´e la plus ` gauche et la plus longue possible. On fera attention ` e e a la pr´sence du motif ∅ qui sera repr´sent´ par null et ne se trouvera jamais ` l’int´rieur des e e e a e arbres Re.nullable(d) . i f (Re. } Exercice 7 R´crire la m´thode find de la classe Matcher (figure 6) en utilisant cette fois la e e d´rivation de motifs. i f (d == null) return found . On s’efforcera de limiter le nombre des d´rivations de motif effectu´es.derivate est null ou un motif standard. public boolean find() { for (int start = regStart .charAt(k). i f (end >= 0) { mStart = start .length() . d) . la m´thode renvoie la e ea e e premi`re position non-filtr´e . for (int k = 0 . } La m´thode findLongestPrefix calcule simplement les motifs d´riv´s par tous les caract`res e e e e de text ` partir de la position start.charAt(k). Exercice 6 (Non corrig´. . Une solution possible est d’introduire une m´thode findLongestPrefix qui cherche e le plus long pr´fixe filtr´ ` partir de la position start. i f (d == null) return false . ıne e a Solution. sans tenir compte de e e e mStart et mEnd) de la classe Matcher ainsi : public boolean matches() { Re d = pat . } return Re. en cas d’´chec. mEnd = end . En cas de succ`s.152 ´ ` CHAPITRE VI. a e On peut alors ´crire par exemple la m´thode matches (simplifi´e. k++) { d = Re. // Calculer N (p) static boolean nullable(Re p) . EXPRESSIONS REGULIERES // Renvoie le motif c−1 ó p ou null si c−1 ó p est ∅ static Re derivate(char c. k < text. e e e e private int findLongestPrefix(int start) { Re d = pat .nullable(d)) found=k+1 . regStart = mEnd .length() . la m´thode renvoie -1. e ıne e e e ´ Ecrire find est ensuite imm´diat : chercher un pr´fixe filtr´ dans tous les suffixes possibles e e e de text. start <= text. Re p) . k < text. // Trouv´ le pr´fixe de longueur k+1-start } return found . vu en TP) Programmer ces deux m´thodes. C’est-`-dire que le r´sultat de Re.derivate(text. e e i f (Re. start++) { int end = findLongestPrefix(start) .derivate(text.

Notons p0 .nullable(d)) return k+1 . i f (d == null) return -1 . Soit le motif q = . } } mStart = mEnd = -1 .+)+X. p1 etc. Mais on enl`ve les motifs () des e e e s´quences. regStart = 0 .*(. pour une position start donn´e. e e e p0 = X(.5. k < text.+)+X p1 = (.charAt(k). } 153 On observe que. . Par ailleurs la d´rivation =−1 ó q vaut q | q.*(. mais la e d´rivation de X par = vaut ∅. la nouvelle m´thode find trouve bien la plus e e longue sous-chaˆ filtr´e. On a donc : e p3 = (q | q) ó X Et en posant q1 = q et qn+1 = qn | qn il est clair que l’on a : pn+2 = qn+1 ó X Motif dont la taille est exponentielle en n. e e e Solution. la suite des motifs d´riv´s Il faut d’abord d´river deux fois par X. } return -1 .*)*X Pour calculer p2 on a exprim´ p+ comme pp*.. return false . // Aucun pr´fixe ne convient e } La mesure des temps d’ex´cution de la nouvelle classe Matcher fait apparaˆ de meilleurs e ıtre performances dans les deux premi`res exp´riences de la section 4. private int findShortestPrefix(int start) { Re d = pat . d) . UNE AUTRE APPROCHE DU FILTRAGE return true .+)+X p2 = .+)+X et le mot XX=· · · = e e et justifier le r´sultat de la derni`re exp´rience.length() .. Si on suit la d´finition de la d´rivation on a en toute e e e rigueur des r´sultat diff´rents — par exemple.derivate(text. e ea e on a p2 = qX. k++) { d = Re. e e i f (Re.4 et une consommation tr`s e e e importante de la m´moire dans la derni`re exp´rience. Une implantation qui donnerait la plus petite sous-chaˆ filtr´e est ıne e ıne e plus simple.*)*. les motifs sont d´j` bien assez compliqu´s comme cela. // Trouv´ le pr´fixe vide for (int k = start .nullable(d)) return start . Le motif p filtre le mot vide. e e e Exercice 8 Calculer la succession des motifs d´riv´s pour le motif X(. i f (Re. p1 = ()(.

154 ´ ` CHAPITRE VI. EXPRESSIONS REGULIERES .

alg`bre. notons qu’ils sont aussi utilis´s pour e mod´liser les ordinateurs et pour comprendre ce qu’un ordinateur peut faire (d´cidabilit´) et ce e e e qu’il sait faire efficacement (complexit´). Ce chapitre est. q0 . Elle se base sur de nombreuses techniques (topologie. etc. Parmi les a mots de Σ∗ on distingue le mot vide not´ ǫ. reli´s entre eux par des “transitions” qui sont marqu´es par des symboles. L’alphabet Σ est un ensemble de caract`res e (ou symboles). un automate est un ensemble “d’´tats e c e e ´ du syst`me”. Etant e e e donn´ un “mot” fourni en entr´e. un mot est une suite finie de caract`res. δ.Chapitre VII Les automates 1 Pourquoi ´tudier les automates e Ce chapitre est une tr`s succincte introduction ` la th´orie des automates que vous aurez e a e l’occasion de voir de fa¸on d´taill´e si vous choisissez un cursus d’informatique.) V´rification de protocoles de communication e Compression de donn´es e Compilation Biologie (g´nomique) e En dehors de ces utilisations “pratiques” des automates.). e e e Les automates sont des objets math´matiques. logique. e 2 Rappel : alphabets. e e e Avant de donner une d´finition plus formelle des concepts d´crits ci-dessus. L’ensemble des mots sur Σ est not´ Σ∗ . e e Un langage est un sous-ensemble de Σ∗ . tr`s utilis´s en informatique. Σ. un peu plus “th´orique” et un peu moins algorithmique que les pr´c´dents. e e 3 Automates finis d´terministes e Un automate fini d´terministe est un quintupl´ (Q. De fa¸on tr`s informelle. C’est donc une notion fondamentale de l’informatique. l’automate lit les symboles du mot un par un et va d’´tat en e e e ´tat selon les transitions. qui permettent e e e de mod´liser un grand nombre de syst`mes (informatiques). c’est-`-dire un ensemble particulier de mots. langages et probl`mes e Nous reprenons ici les notations du chapitre VI. Le mot lu est soit accept´ par l’automate soit rejet´. th´orie des e e graphes. F ) constitu´ des ´l´ments suivants e e e ee un alphabet fini (Σ) un ensemble fini d’´tats (Q) e 155 . Le mot vide est l’unique mot de longueur z´ro. mots. L’´tude des automates a commenc´ e e e e vers la fin des ann´es cinquante. citons quelques e e exemples classiques d’utilisation d’automates : V´rification d’un circuit ´lectronique e e Recherche d’occurrence dans un texte (moteur de recherches sur le web. c e e par nature. etc.

nous c e ˆ introduisons la fonction de transition ´tendue aux mots. Le processus commence ` l’´tat de d´part q0 a e e Les symboles du mot sont lus les uns apr`s les les autres. e e De fa¸on plus formelle. e e a langage. q0 . δ(2. δ(2. c) ∈ F } 3. δ(q. e →1 ∗2 a 1 1 b 2 2 Il correspond ` l’automate (Q. a) Nous pouvons maintenant d´finir le langage L(A) accept´ par un automate fini d´terministe e e e A = (Q. . Elle se d´finit r´cursivement comme e e e suit. a) = 1. a e Une ligne correspond ` un ´tat de l’automate (l’´tat initial est pr´c´d´ d’une fl`che “→” . pour d´finir exactement le langage reconnu par un automate.e.e. e ` la lecture de chaque symbole. b) = 2. Le langage associ´ a un automate est constitu´ de l’ensemble des mots ıt e` e qu’il reconnaˆ Voici comment l’automate proc`de pour d´cider si un mot appartient ` son ıt. ˆ A partir d’un ´tat q en lisant le mot vide ǫ on reste dans l’´tat q.156 une fonction de transition (δ : Q ∗ Σ → Q) un ´tat de d´part (q0 ∈ Q) e e un ensemble d’´tats finaux (ou acceptant) F ⊆ Q e CHAPITRE VII. i. δ. on emploie la fonction de transition δ pour se d´placer A e vers le prochain ´tat (en utilisant l’´tat actuel et le caract`re qui vient d’ˆtre lu). et un ´tat ´ Etant donn´ un mot c se terminant par a ∈ Σ (i. b) = 2 q0 = 1 F = {2} Il est facile de voir que le langage de cet automate est constitu´ exactement des mots compos´s e e de a et de b qui se terminent par un b. F ).. l’´tat correspondant ` la lecture e e a du dernier caract`re du mot) est un ´tat de F .. δ(1. On dit aussi qu’il le reconnaˆ e ıt ou ne le reconnaˆ pas. c = c e e ˆ ˆ ˆ q de Q. e e e e le mot est reconnu si et seulement si le dernier ´tat (i. Σ. F ) avec a Q = {1. q0 .1 Fonctionnement d’un automate fini d´terministe e L’automate prend en entr´e un mot et l’accepte ou la rejette. δ. c′ ). Exemple 1 Consid´rons la table de transition ci-dessous. 2} Σ = {a.. δ(q. Notons qu’` partir de cette table il est ais´ de retrouver l’ensemble des ´tats a e e ainsi que l’alphabet et donc d’identifier exactement l’automate. c) = δ(q. a) pour q ∈ Q. Σ. δ. c′ a) = δ(δ(q. a e e e e e e l’´tat final d’une ´toile “∗”) e e La valeur δ(q. ∀q ∈ Q. ˆ L(A) = {c|δ(q0 . a ∈ Σ correspond ` l’´tat indiqu´ ` l’intersection de la ligne q et a e ea de la colonne a.2 Des repr´sentation “compactes” des automates e On peut associer ` un automate une table de transition qui d´crit de mani`re extensive la a e e fonction de transition δ : Une colonne correspond ` un caract`re de l’alphabet. LES AUTOMATES 3. ǫ) = q e e ′ a avec c′ ∈ Σ ∪ {ǫ}). b} δ(1. a) = 1.e.

Σ. sn s´par´s par des virgules. q3 }. δ. F ). q2 . {q0 }) donn´ par la table e e . ´ Exercice 2 Ecrire la table de transition de l’automate suivant. e e b a 1 a Fig. e ee Un ensemble d’arcs entre les sommets valu´s par un symbole de σ (un arc entre les ´tats e e q et q ′ valu´ par le symbole s signifie que δ(q.´ 3. q0 . 1 – Un automate fini d´terministe e Pour simplifier encore cette repr´sentation. q1 . q0 . e L’´tat initial q0 est marqu´ par une fl`che entrante. s) = q ′ ). Tous les mots qui contiennent un “b”. Quel est le langage reconnu ? 0 1 A 0 B Solution. {0. La table de transition de l’automate est →A B ∗C 0 B B C 1 A C C 1 C 0. 1 2 a a 1 b 2 a. b 2 b Cet automate reconnaˆ les mots qui contiennent “01”.. 2 – Deux repr´sentations ´quivalentes du mˆme automate fini e e e Exercice 1 Quel est le langage reconnu par l’automate de la figure 2 ? Solution. un arc entre deux sommets q. Cette derni`re convention signifie sime e e plement que ∀i ≤ n.. 1}. e e L’automate de l’exemple 1 est ainsi repr´sent´ sur la figure 1. e La figure 2 illustre une telle simplification. δ. on peut e c e e utiliser un graphe de transition constitu´ des ´l´ments suivants : e ee Un ensemble de sommets (chaque sommet repr´sente un ´l´ment de Q). q ′ peut ˆtre valu´ e e e par plusieurs symboles s1 . AUTOMATES FINIS DETERMINISTES 157 Pour repr´senter de fa¸on tr`s intuitive un automate fini d´terministe (Q. . si ) = q ′ et elle permet d’´viter une multiplication d’arcs sur le graphe. b a 1 b Fig. δ(q.. ıt Exercice 3 Soit l’automate fini d´terministe ({q0 . e e e Les ´tats finaux F sont entour´s d’une double ligne.

il peut y e e e avoir plusieurs transitions avec le mˆme symbole. F ) e e un alphabet fini (Σ) un ensemble fini d’´tats (Q) e . u Solution. Ils peuvent ˆtre convertis en des automates finis d´terministe. Solution. q0 . LES AUTOMATES Dessiner l’automate et montrer qu’il accepte “110101”. car on ne sait pas quel ´tat l’automate va choisir. 1 q0 1 0 0 1 q2 1 Exercice 4 Construire un automate fini d´terministe qui reconnaˆ le langage e ıt L = {x ∈ {0. Σ. e e e Les automates non-d´terministes permettent de mod´liser facilement des probl`mes come e e plexes.158 δ → ∗ q0 q1 q2 q3 0 q2 q3 q0 q1 1 q1 q0 q3 q2 CHAPITRE VII. Ces derniers peuvent ˆtre e e e exponentiellement plus grand que les automates non d´terministe dont ils sont issus. 1}∗ |n1 (x) ≡ 0 mod 4} o` n1 (x) est le nombre d’occurrence du symbole 1 dans le mot x. 0 0 A 1 B 1 Exercice 5 Construire les automates finis d´terministes qui reconnaissent les langages suivants e L1 = {m ∈ (a + b)∗ |chaque a de m est imm´diatement pr´c´d´ et imm´diatement suivi d’un b} e e e e e ∗ |m contienne ` la fois ab et ba} L2 = {m ∈ (a + b) a L3 = {m ∈ (a + b)∗ |m contienne exactement une occurrence de aaa} 1 C 1 D 0 0 q3 0 0 q1 4 Automates finis non-d´terministes e Un automate fini non-d´terministe est un automate tel que dans un ´tat donn´. le fonctionnement d’un tel automate n’est e donc pas totalement (( d´termin´ )). e Un automate fini non-d´terministe est un quintupl´ : (Q. δ.

a) ˆ p∈δ(q. Le langage associ´ est constitu´ de l’ensemble des mots qu’il reconnaˆ e e ıt. a c q0 q1 q2 La table associ´e ` cet automate est alors : e a → q0 q1 ∗ q2 a {q1 } {q1 } ∅ a.. ´tendue aux mots.1 Fonctionnement d’un automate fini non-d´terministe e Comme pour un automate fini d´terministe. . et un ´tat e e q de Q. c b ∅ {q1 } ∅ c ∅ {q1 . c) = δ(q.e. δ. e e e Remarquons que tout automate fini d´terministe est aussi un automate fini non-d´terministe. b. on reste dans l’´tat q. e e e A partir d’un ´tat q en lisant le mot vide ǫ (le mot vide ne contient aucun symbole et est e ˆ toujours not´ ǫ). c) ∩ F = ∅} Exercice 6 Construire l’automate fini non-d´terministe associ´ ` la table ci-dessous. s). ∀q ∈ Q. Σ. e ea →0 1 2 ∗3 a {0. ˆ L(A) = {c|δ(q0 . c} qui comıt e mencent par a et qui finissent par c. q0 . c = c′ a avec c′ ∈ Σ ∪ {ǫ}). l’automate prend en entr´e un mot et l’accepte e e ou le rejette. ǫ) = {q} e e ´ Etant donn´ un mot c se terminant par a ∈ Σ (i. nous nous introduisons la fonction de transition e ˆ Elle se d´finit r´cursivement comme suit.c′ ) Nous pouvons maintenant d´finir le langage L(A) accept´ par un automate fini d´terministe e e e A = (Q. δ.. e e Les repr´sentations compactes des automates finis d´terministes s’´tendent naturellement e e e aux automates finis non-d´terministes. 1. e un ´tat de d´part (q0 ) e e un ensemble d’´tats finaux (ou acceptant) F e C’est la fonction de transition δ qui diff`re ici de celle utilis´e par les automates finis d´terministes. Une cellule de la table de transition contient un souse ensemble d’´tats (´ventuellement vide). c′ a) = δ(p. δ(q. b.´ 4. q2 } ∅ Comme pour les automates d´terministes. i.e. AUTOMATES FINIS NON-DETERMINISTES 159 une fonction de transition δ qui associe ` tout ´tat q ∈ Q et tout symbole s ∈ Σ un sous a e ensemble de Q not´ δ(q. 3} ∅ {3} ∅ b {2} {3} ∅ {1} Solution. e e 4. F ). Exemple 2 Voici automate qui reconnaˆ les mots d´finis sur l’alphabet {a. ˆ ˆ δ(q.

ıt e e e Exercice 10 Construire un automate fini non-d´terministe qui reconnaˆ les nombres dont le e ıt dernier chiffre n’apparaˆ qu’une fois. le joueur oe peut retourner autant de jetons qu’il le souhaite. Le joueur a les yeux e e band´s. Σ 0 h c 1 c h 8 9 u 10 r 11 c 12 h 13 2 o 3 m 4 s 5 k 6 y 7 Σ Exercice 8 Construire un automate finis non-d´terministe qui reconnaˆ les mots de l’alphabet e ıt {a. Σ Solution. blancs d’un cˆt´ e a e e oe et noirs de l’autre. puis effectue une rotation du . A chaque tour. a b 0 1 a 2 b 3 b Exercice 9 Construire un automate fini non-d´terministe et un automate fini d´terministe qui e e reconnaˆ les mots sur l’alphabet {a. b. Face ` lui. Le but du jeu est d’avoir les quatre jetons du cˆt´ blanc.160 CHAPITRE VII. le maˆ e ıtre de jeu annonce si la configuration obtenue est gagnante ou pas. un plateau sur lequel sont dispos´s en carr´ quatre jetons. LES AUTOMATES 1 a a a 0 b a b 3 b 2 Exercice 7 Construire un automate fini non-d´terministe qui reconnaˆ les mots qui contiennent e ıt “church” ou “chomsky”. mais sans les d´placer. Solution. ıt Exercice 11 Mod´lisation d’un jeu (d’apr`s la page de Jean-Eric Pin). Pour cela. c} d´crits par l’expression r´guli`re (a + b + c)∗ b(a + b + c). b} qui terminent par bab.

3} {0. 2} Remarquons que les ´tats {1}.2 D´terminisation d’un automate fini non-d´terministe e e Un automate fini d´terministe est aussi non-d´terministe. e a b {0} b {0. e e . un. 1. 2} {0. Fd ) qui reconnaˆ exactement le mˆme langage. e e e Consid´rons un automate fini non-d´terministe An = (Qn . a) = q∈S δn (q. q0 . Nous illustrons le th´or`me de Rabin-Scott sur quelques exemples. Les ´tats de d´part sont respectivement q0 et le singleton {q0 }. {0. 3}. 1. Pouvez-vous aider le joueur ` gagner en moins d’une minute ? a 4. 2}. on d´finit la fonction de e e transition δd (S. e Fd est l’ensemble des sous-ensembles de Qn qui contiennent au moins un ´l´ment de Fn . L’automate e suivant r´pond ` la question. {3}. e e Exemple 3 reprenons l’exemple de l’exercice 8. Donc tout langage reconnu par un e e automate fini d´terministe est reconnu par un automate fini non-d´terministe. 2. Chaque ıtre e annonce prend une seconde. 3} e sont inatteignables et peuvent ˆtre “retir´s” de l’automate. 1. 3} {1. {2. deux ou trois quarts de tours. 3} {0. e e Qd est constitu´ de tous les sous-ensembles de Qn . e e la r´ciproque est aussi vraie (Th´or`me de Rabin-Scott). {1. b} qui terminent par bab. 2. 3}. 3} b b a {0. δd . Σ. a). AUTOMATES FINIS NON-DETERMINISTES 161 plateau de z´ro. e a a b 0 1 a 2 b 3 b Essayons maintenant de le d´terminiser en construisant un nouvel ´tat ` partir de chaque sous e e a ensemble d’´tat possible. La configuration de d´part est inconnue du e e joueur. mais le maˆ de jeu annonce avant le d´but du jeu qu’elle n’est pas gagnante. Il s’agissait de construire un automate fini non-d´terministe reconnaissant les mots de l’alphabet {a. 3} {2.´ 4. a) de la mani`re suivante e δd (S. ee ´ Etant donn´ un sous ensemble S de Qn et un symbole a ∈ Σ. 3}. 2}. {q0 }. {1. Fn ) et construisons un e e automate fini d´terministe Ad = (Qd . 3} {1. Σ. δn . {2}. 1} a {1} {2} {3} {0. {0. {0. 2}. et il faut 3 secondes au joueur pour retourner les jetons. 2} a {1. e ıt e Les alphabets de An et de Ad sont identiques. 2. {0. 2. Plus surprenant.

Si seconde chaˆ est vide. ee – ajouter S ` Qd . suivi d’une suite de chiffres. La table e e a est identique ` celle utilis´e pour un automate fini non-d´terministe ` ceci pr`s qu’on la compl`te a e e a e e d’une colonne associ´e au caract`re vide ǫ. . ∅ ∅ {D} ∅ ∅ ∅ 0 ∅ {F } {C} {D} ∅ ∅ 1 ∅ {C} ∅ {D. l’ajouter ` E ea a + ajouter un arc sur l’automate entre S et S ′ et la valuer par a Exercice 12 D´terminiser l’automate de l’exercice 7 (long). Rappelons qu’un nombre d´cimal est un nombre r´el qui est ıt e e e le quotient d’un entier relatif par une puissance de dix. on ne cr´e pas imm´diatement tous les ´tats de l’automate e e e fini d´terministe. E} ∅ ∅ ··· ··· ··· ··· ··· ··· ··· 9 ∅ {C} ∅ {D. la premi`re e chaˆ de chiffres ne peut pas ˆtre vide et ne commence pas par “0” (sauf si le nombre d´cimal ıne e e est 0). 9 A ǫ. a) e + si S ′ n’est pas d´j` dans Qd . + calculer l’´tat S ′ = q∈S δn (q. lors de la conversion. − B 1. on omet la “. La seconde chaˆ ne se termine pas par “0”. le “+” ou le “-” sont optionnels. Une ǫ transition (not´e ǫ sur l’arc d’un automate) e e permet de passer d’un ´tat ` l’autre d’un automate sans lire de symbole.”. ıne ıne 0. E} ∅ ∅ . · · · . La table de transition associ´e ` cet automate est alors : e a →A B C D ∗E ∗F ǫ {B} ∅ ∅ ∅ ∅ ∅ + {B} ∅ ∅ ∅ ∅ ∅ − {B} ∅ ∅ ∅ ∅ ∅ . d’une e e c virgule et d’une suite de chiffres. E} ∅ ∅ 2 ∅ {C} ∅ {D. soit en lisant − soit enfin en ne lisant rien. · · · . on souhaite pouvoir e e ´crire le nombre d´cimal en commen¸ant par un “+” ou un “-’. on peut passer de A ` B soit en e a e e a lisant +. −. 9 La transition de l’´tat A ` l’´tat B est r´gie par ǫ.3 Les ǫ transitions Rappelons qu’ǫ repr´sente le mot vide. 9 0 F 0. Une table de transition peut ˆtre associ´e ` un automate contenant des ǫ transition. LES AUTOMATES En pratique. +. Les ´tats “utiles” sont cr´es quand on en a besoin en suivant la m´thode de e e e e construction ci-dessous : Qd est initialis´ ` ∅ et soit E un ensemble d’´tats initialis´ ` E = {{q0 }} ea e ea Tant que E est non vide. +. e 4. a – pour tout symbole a ∈ Σ. Cette facilit´ permet e a e de programmer facilement des automates complexes. C D 1.162 CHAPITRE VII. Plus pr´cis´ment. 9 E Exercice 13 On cherche ` construire un automate qui reconnaˆ les mots qui se terminent par a ıt bab ou qui commencent par aba. · · · . – choisir un ´l´ment S de E (S est donc un sous ensemble de Qn ). e e Exemple 4 Pour illustrer les ǫ transitions. Ainsi. Bien entendu. construisons un automate fini non d´terministe qui e reconnaˆ les nombres d´cimaux.

e e e a 6. b a 4 5 b 6 a 7 Il suffit alors d’assembler ces automates avec une simple ǫ transition. e e e Nous n’aborderons pas ici cette ´limination. il est e e toujours possible d’´liminer les ǫ transition et d’obtenir un automate fini d´terministe ´quivalent. e a e e e 6 Un peu de Java Il est ais´ de repr´senter les automates finis sous la forme d’un programme java. AUTOMATES FINIS ET EXPRESSIONS REGULIERES 163 On sait construire un automate qui reconnaˆ les mots qui se terminent par bab (exercice 8) : ıt a b 0 1 a 2 b 3 b il est facile de construire un automate qui reconnaˆ les mots qui commencent par aba.1 Mod`le e Le mod`le de donn´es est alors tr`s simple : e e e . ıt a. le th´or`me e e e e e e d’´quivalence des expressions r´guli`res et des automates finis (Kleene) ´tablit que Le langage e e e e accept´ par un automate fini correspond ` la valeur d’une expression r´guli`re et r´ciproquement.´ ` 5. En effet. e 5 Automates finis et expressions r´guli`res e e Les automates finis et les expressions r´guli`res ont la mˆme expressivit´. a. Sans perte de g´n´ralit´. nous allons supposer que l’alphabet est l’ensemble des caract`res ASCII e e e e et que les ´tats sont num´rot´s ` partir de 0. Notre objectif e e est de mod´liser un automate fini non-d´terministe (sans ǫ transition) e e et d’´crire un programme qui d´termine si une chaˆ de caract`re est reconnue par l’aue e ıne e tomate. b a b ǫ i ǫ 4 a 5 b 6 a 7 0 1 a 2 b 3 b L’introduction des ǫ transition ne change pas la nature des langages reconnus par les automates. Comme pour les automates non-d´terministes que l’on peut toujours d´terminiser.

le mot que l’on cherche ` reconnaˆ est une chaˆ de caract`res String mot. e Enfin.suivant. Liste x) { val = v. int v) { // Le test d’appartenance i f (x == null) return false.val. } // Le k `me ´l´ment e e e static int kieme(Liste x. class Liste { int val.val == v || estDans(x.suivant. int i. e a e L’ensemble des ´tats finaux est une liste d’entiers. } } 6. Ainsi e e e delta[q][c] est la liste des ´tats atteignables ` partir de q en lisant le caract`re c. Liste[][] delta. Liste suivant. Liste f. LES AUTOMATES L’´tat initial de l’automate est indiqu´ par un entier q0. Automate e a.suivant). } } class Automate { // ´tat initial e int q0. Automate a) qui permet de v´rifier qu’un mot mot est ace cept´ apr l’automate a appelle la fonction static boolean accepter(String mot. Liste[][]d) { q0 = q. k-1).2 Algorithme de recherche Nous aurons besoin de quelques fonctions classiques sur les listes static int longueur(Liste x) { // La longueur d’une liste i f (x == null) return 0. a ıtre ıne e Soit donc en Java les deux classes List et Automate. } static boolean estDans(Liste x.164 CHAPITRE VII. else return kieme(x. } La fonction accepter(String mot. finaux = f. static boolean accepter(String mot. int q). suivant = x. int k) { i f (k == 1) return x. e e La table de transition delta est un tableau bidimensionnel de listes d’entiers (la liste d’entier ´tant ici le moyen le plus simple de repr´senter un ensemble d’´tats). // ´tats finaux e Automate(int q. else return 1 + longueur(x. else return x. // fonction de transition Liste finaux. Automate a) { . Liste(int v. delta = d. v).

null). Automate a. new Liste(1. null). e Elle renvoie true si et seulement si le sous-mot de mot dont on a retir´ les i premiers caract`res e e ´tait reconnu par un automate semblable ` a dont l’´tat initial serait q. int q) prend. a. int i. on appelle r´cursivement accepter. } } La fonction static boolean accepter(String mot.finaux.6. nv_q = nv_q. on va essayer d’ema prunter toutes les transitions possibles.q0). il nous suffit de construire la table de transition. else { boolean resultat = false. delta[2][(int)’0’] = null. delta[2][(int)’1’] = null. 1} ∅ ∅ 1 {0} {2} ∅ Pour le construire. nv_q. 0 0 1 1 2 0. delta[0][(int)’0’] = new Liste(0. 1 La table associ´e ` cet automate est alors : e a →0 1 ∗2 0 {0. e L’´tat courant q.val atteignable ` e a partir de q en lisant c. return resultat. int q) { i f (i == mot. q).delta[q][c]. e a e Remarquons que si i est le dernier caract`re du mot (ce qui correspond au test if (i == mot. int c = mot.charAt(i). Si ce n’est pas le cas. 0. // le code ASCII du caract`re courant e for (Liste nv_q = a. deux autres param`tres : e e La position du caract`re courant i dans le mot. e e 6. delta[1][(int)’1’] = new Liste(2. Automate a = new Automate(0.val). null)). . null). delta[1][(int)’0’] = null. UN PEU DE JAVA 165 return accepter(mot. i+1.suivant) resultat = resultat || accepter(mot. public static void main(String [] arg) { Liste[][] delta = new Liste[3][128]. Une fois dans l’´tat nv_q.length())) e alors il suffit de tester l’appartenance de q ` a. on explore ainsi tous les ´tats nv_q. delta[0][(int)’1’] = new Liste(0. a.3 Mise en œuvre sur un automate Consid´rons l’automate suivant qui accepte toutes les chaˆ e ınes qui se terminent par “01”.val.length()) return Liste.finaux. int i. a. delta). new Liste(2. } static boolean accepter(String mot.estDans(a. en plus de l’automate et du mot que l’on ´tudie. Automate a. nv_q != null.

out.println("accepter = " + accepter(arg[0]. e Exercice 14 Comment proc´der pour coder un automate avec des ǫ transitions ? e . LES AUTOMATES System. } Remarquons que le code ASCII du caract`re ’0’ est obtenu par (int)’0’. a)).166 CHAPITRE VII.

on calcule le nombre d’op´rations ´l´mentaires a e ee (op´rations arithm´tiques. e e e e de les utiliser et de les modifier. Les ´tapes ´l´mentaires sont ´ventuellement r´p´t´es (notion de boucle) et sont soumises ` e ee e e ee a des test logiques (instruction de contrˆle). la plupart du temps. Les mesures d’efficacit´ sont donc des fonctions des donn´es e e e e e d’entr´e.Annexe A Le coˆ t d’un algorithme u Ce chapitre rappelle les diff´rents moyens d’´valuer le coˆt d’un algorithme.) effectu´es pour traiter la e e o e donn´e d. la taille n. il est clair que la qualit´ de l’algorithme n’est pas absolue e mais d´pend des donn´es d’entr´e. 167 . lui aussi bien sp´cifi´. on cherche souvent ` o e ea e a ´valuer le nombre d’op´rations n´cessaires pour traiter les donn´es qui ont une certaine “taille”. la longueur n d’une liste pour une inversion. les ` e e e e donn´es (d’entr´es. Les algorithmes peuvent. e e u 1 Une d´finition tr`s informelle des algorithmes e e La formalisation de la notion d’algorithme est assez tardive. Les travaux fondamentaux de Post et de Turing datent de la fin des ann´es 30. de sortie et interm´diaires) sont structur´es. Pour ´valuer le temps d’ex´cution d’un algorithme (par exemple un tri) sur une donn´e e e e e d (par exemple un ensemble d’entiers ` trier). Pour e simplifier nous supposerons dans la suite que la “taille” d’une donn´e est ´valu´e par un unique e e e entier n. cette hypoth`se n’est valable que si tous les nombres sont cod´s sur un mˆme nombre e e e de bits. etc.). etc. le nombre n d’´l´ments ` trier pour un algorithme de tri. pour ce cours. ˆtre o e cod´s ` l’aide de programmes informatique e a 2 Des algorithmes “efficaces” ? Qu’est-ce qu’un bon algorithme ? C’est bien entendu un algorithme qui r´pond correctement e au probl`me pos´. Ceci permet de les conserver. Plutˆt que de calculer le nombre d’op´rations associ´ ` chaque donn´e. On souhaite g´n´ralement qu’il soit aussi rapide et n’utilise pas trop de e e e e m´moire. e e e ee Attention. des e outils qui permettent d’exposer tr`s rigoureusement la notion d’algorithme et de complexit´ ale e gorithmique. Nous nous contenterons de la d´finition suivante : Un algorithme est un assemblage e ordonn´ d’instruction ´l´mentaires qui a partir d’un ´tat initial bien sp´cifi´ permettent d’aboue ee ` e e e tir a un ´tat final. affectation. On admet commun´ment que toute op´ration ´l´mentaire prend un temps constant. Il e e e e existe souvent un param`tre naturel qui est un estimateur raisonnable de la taille d’une donn´e e e (par exemple. m d’une ee a matrice pour le calcul du d´terminant. Nous ne disposons pas. Notons que pour qu’un algorithme puisse s’ex´cuter. instruction de contrˆle. e Quel que soit la mesure choisie.

On mesure alors le nombre d’op´rations avec les donn´es e e e qui m`nent ` un nombre maximal d’op´rations. B et C telles que ∀n ≥ n0 . plus rapide si la liste est partiellement tri´e plutˆt que dans un ordre parfaitement e o al´atoire.168 ˆ ANNEXE A. ce ıtre e qui est d´licat ` estimer. Par exemple. On fait parfois l’hypoth`se que les donn´es sont ´quiprobables e a e e e (ce qui est bien souvent totalement arbitraire). f (n) = O(g(n)) si et seulement si il existe deux constantes positives n0 et B telles que ∀n ≥ n0 . Section 4). On dit d’un e ee algorithme qu’il est lin´aire si il utilise O(n) op´rations ´l´mentaires. Ces notions s’´tendent ` la consommation m´moire d’un algorithme. LE COUT D’UN ALGORITHME Les informaticiens s’int´ressent ` l’ordre de grandeur des temps d’ex´cution (ou de taille e a e m´moire) quand n devient tr`s grand. e . Un algorithme en O(1). e ee f (n) = Ω(g(n)) si et seulement si il existe deux constantes positives n0 et B telles que ∀n ≥ n0 . il faut connaˆ la distribution des donn´es. Soit donc e a e complexit´ dans le pire cas = e d donn´e de taille n e max C(d) o` C(d) est le nombre d’op´rations ´l´mentaires pour ex´cuter l’algorithme sur la donn´e u e ee e e d’entr´e d. On utilise donc les notations e e e O. e Attention tout de mˆme ` deux ´cueils pour les calculs en moyenne : e a e Pour que ce calcul garde un certain sens. Bg(n) ≤ f (n) ≤ Cg(n) Dans la pratique. Θ et Ω pour exprimer des relations de domination : Soient f et g deux fonctions d´finies des e entiers naturels vers les entiers naturels. C’est donc un ensemble e e e constant d’op´rations ´l´mentaires (exemple : l’addition de deux entiers). le temps d’ex´cution d’un algorithme d´pend non seulement de n mais e e aussi de la structure des donn´es. f (n) ≥ Bg(n) f (n) = Θ(g(n)) si et seulement si il existe trois constantes positives n0 . 3 Quelques exemples Nous donnons ici des exemples simples et vari´s d’analyse d’algorithmes. Cette notion n’a de sens que si l’on dispose d’une hypoth`se e e sur la distribution des donn´es. c’est un algorithme ıt dont le temps d’ex´cution ne d´pend pas de la taille des donn´es. f (n) ≤ Bg(n) Ce qui signifie que f ne croˆ pas plus vite que g. Comme nous allons le voir. On parle alors de complexit´ e a e e spatiale (maximale ou moyenne). e La complexit´ en moyenne. pour certains e algorithmes. les calculs de complexit´ en moyenne sont fort d´licats ` mettre e e a en œuvre. De nombreux autres e exemples sont dans le polycopi´. on a donc besoin de plusieurs mesures : e La complexit´ dans le “pire cas”. Pour analyser finement un algorithme. En pratique. Le coˆt exact en secondes est en effet tr`s difficile ` e e u e a mesurer et d´pend de tr`s nombreux param`tres (cf. Il est polynomial si e e ee il existe une constante a telle que le nombre total d’op´rations ´l´mentaires est O(na ). le tri d’une liste d’entiers est. Soit donc e complexit´ en moyenne = e d donn´e de taille n e π(d)C(d) o` π(d) est la probabilit´ d’avoir en entr´e une instance d parmi toutes les donn´es de u e e e taille n. le pire cas est rarement atteint et l’analyse en moyenne semble plus s´duisante.

else return trouve(T. e e 2 C(n) = O(log n). QUELQUES EXEMPLES 169 3. Identifions les tours par un entier. else i f (T[mid] > v) return trouve(T. Pour effectuer une recherche sur tout le tableau. } T. e e ee La complexit´ est O(n) e La seconde version est r´cursive. Le nombre total d’op´rations est proportionnel au nombre de comparaisons C(n) effectu´es e e par l’algorithme r´cursif.3 Tours de Hanoi Le tr`s classique probl`me des “tours de Hanoi” consiste ` d´placer des disques de diam`tres e e a e e diff´rents d’une tour de d´part ` une tour d’arriv´e en passant par une tour interm´diaire. for (int i= 2. static boolean trouve(int[] i f (min >= max) // vide return false. Et donc. } 3. mid).1 Factorielle static int factorielle(int n) { int f = 1. e e Nous avons n − 1 it´rations au sein desquelles le nombre d’op´rations ´l´mentaires est constant.2 Recherche Dichotomique Consid´rons un tableau T de n entiers tri´s. int max){ 2. return f. int v. Soit C(n) le nombre d’op´rations n´cessaires pour calculer e e e factorielle(n). Les e e a e e r`gles suivantes doivent ˆtre respect´es : on ne peut d´placer qu’une disque ` la fois. else return n * factorielle(n-1). Soit donc. int min. v. 3. il suffit de remarquer que si e e l’on sait d´placer une tour de taille n de la tour ini vers dest. min. max). nous avons imm´diatement : C(n) = 1 + C( n ). et on ne e e e e a peut placer un disque que sur un disque plus grand ou sur un emplacement vide.3.length). T. La fonction trouve cherche l’entier v dans T entre les indices min et max -1. int mid = (min + max) / i f (T[mid] == v) return true. 0. } La premi`re version de notre programme permettant de calculer n! est it´rative. v. i <= n. Pour r´soudre ce probl`me. on utilise une recherche “dichotomique”. Nous avons alors C(n) ≤ λ + C(n − 1) o` λ est une constante qui majore le u nombre d’op´rations pr´c´dent l’appel r´cursif. i++) f = f * i. Pour ce faire. On cherche ` tester si un entier v donn´ se e e a e trouve dans le tableau. static int factorielle(int n) { i f (n <= 0) return 1. De plus C(1) se calcule en temps constant et e e e e donc C(n) = O(n). v. alors pour d´placer une tour e e . il suffit d’appeler trouve(T. mid + 1.

Quelle est la complexit´ en moyenne ? e e n tableaux. Parmi ceux-ci. } Notons C(n) le nombre d’instructions ´l´mentaires pour calculer hanoi(n. u 3. LE COUT D’UN ALGORITHME de taille n + 1 de ini vers dest. ini. return false.1. public static void hanoi(int n. temp).println("deplace" + ini + " " + dest).4 Recherche d’un nombre dans un tableau. il faut parfois atteindre de tr`s grandes valeurs de n pour qu’un algorithme e en O(n log n) se comporte mieux qu’un algorithme quadratique. i < T. } La complexit´ dans le pire cas est clairement O(n). Dans le cas contraire. Tour de Hanoi est exponentielle. l’algorithme proc`de ` exactement n it´rations. il suffit de d´placer une tour de taille n de ini vers temp. ini. En pratique. complexit´ en moyenne e On suppose que les ´l´ments d’un tableau T de taille n sont des nombres entiers distribu´s ee e de fa¸on ´quiprobable entre 1 et k (une constante). temp. int v) { for (int i = 0.length.println("deplace" + ini + " " + dest). nous avons une complexit´ moyenne de e a e e C= Or ∀x. return. (k − 1)n ×n+ kn n n i=1 (k − 1)i−1 ×i ki ixi−1 = i=1 1 + xn (nx − n − 1) (1 − x)2 n i i=1 x (il suffit pour ´tablir ce r´sultat de d´river l’identit´ e e e e C=n = 1−xn+1 1−x ) et donc 1 k n (k − 1)n (k − 1)n n +k 1− (1 + ) n n k k k =k 1− 1− 4 Coˆ t estim´ vs. i++) i f (T[i] == v) return true. dest. System. Au total. o` λ est une constante. dest). int ini. Consid´rons maintenant l’algorithme de c e e recherche ci-dessous qui cherche une valeur v dans T.out. // sinon recursion } hanoi(n . (k − 1)n ne contiennent pas v et Remarquons que nous avons k dans ce cas. coˆ t r´el u e u e Les mesures pr´sent´es dans ce chapitre ne sont que des estimations asymptotique des algoe e rithmes. ini. hanoi(n-1. dest).out. static boolean trouve(int[] T. l’entier est e a e dans le tableau et sa premi`re occurrence est alors i avec une probabilit´ de e e (k − 1)i−1 ki et il faut alors proc´der ` i it´rations. . int temp. temp.170 ˆ ANNEXE A. ee Nous avons alors C(n + 1) ≤ 2C(n) + λ. un e disque de ini versdest et finalement la tour de hauteur n detemp versdest. int dest){ i f (n == 1){ // on sait le faire System.

les op´rations d’acc`s aux disques. l’algorithme sp´cifiquement adapt´ au probl`me que l’on e e e cherche ` r´soudre.ˆ ´ ˆ ´ 4.. Il est toujours n´cessaire de proc´der ` des analyses exp´rimentales avant de e e a e choisir le “meilleur” algorithme. a e . ou le trafic r´seau g´n´r´ e e e e ee ne sont pas pris en compte alors que ces param`tres peuvent avoir une influence consid´rable sur e e un programme). COUT ESTIME VS. COUT REEL 171 Les analyses de complexit´ peuvent servir ` comparer des algorithmes mais le mod`le de coˆt e a e u est relativement simple (par exemple. i.e.

172 ˆ ANNEXE A. LE COUT D’UN ALGORITHME .

avec l’argument msg. Par cons´quent tout a a e e ce qu’elle d´finit (variable msg et m´thode main) est statique. il n’est pas si facile de d´finir ce qu’est exactement un objet dans le cas g´n´ral.1 Programme de classe Un programme se construit ` partir de une ou plusieurs classes. ca permet de s’y retrouver. // d´claration de variable e public static void main (String [] arg) // d´claration de m´thode e e { System. Si le source est contenu dans un fichier Simple.out. e e 1. La section 2 d´crit la ıne e e construction des objets ` partir des classes. e e e e a Par exemple tous les objets poss`dent une m´thode toString sans argument et qui renvoie e e une chaˆ repr´sentant l’objet et normalement utilisable pour l’affichage.out d´signe la variable out de la classe System. Les classes e e e regroupent des membres qui sont le plus souvent des variables (plus pr´cis´ment des champs) e e et des m´thodes. Une variable e ou une m´thode qui existent d`s que la classe existe sont dites statiques. Les variables des classes sont les vae e riables globales du programme et leurs m´thodes sont les fonctions du programme. Par exemple. e 173 .Annexe B Morceaux de Java 1 Un langage plutˆt classe o Java est un langage objet avec des classes. a Mais d´crivons d’abord la structuration des programmes r´alis´e par les classes. Elle se suffit ` elle mˆme. Par re-cons´quent. la m´thode main invoque la m´thode println de l’objet e e System. la classe simple suivante c est un programme qui affiche Coucou ! sur la console : class Simple { static String msg = "Coucou !" . ¸ En termes de programmation objet. Les classes ont une double fonction : structurer les programmes et dire comment on construit les objets. e e Commen¸ons par un programme en une seule classe. C’est une bonne id´e de mettre les classes dans des e fichiers homonymes.println(msg) . car si on ne met rien les membres ne sont e e e e e pas statiques. } } Cette classe ne sert pas ` fabriquer des objets.java. mais peuvent aussi ˆtre d’autres classes. Disons qu’un e e e objet poss`de un ´tat et des m´thodes qui sont des esp`ces de fonctions propres ` chaque objet. il se compile par javac Simple.java et se lance par java Simple. System. Pour ce qui est de la seconde fonction. toutes les e e e d´clarations sont pr´c´d´es du mot-cl´ static. dont une au moins contient a une m´thode main qui est le point d’entr´e du programme.out.

Reste ` se demander quel est l’objet rang´ dans System. disons que c’est un a e objet d’une autre classe (la classe PrintStream) qui a ´t´ mis l` par le syst`me Java et ne nous ee a e en pr´occupons plus pour le moment.out. } Tandis que le programme est modifi´ ainsi : e class Simple { public static void main (String [] arg) { // d´claration de m´thode e e System.2 Compl´ment : la m´thode main e e La d´claration de cette m´thode doit obligatoirement ˆtre de la forme : e e e public static void main (String [] arg) En plus d’ˆtre statique. e 1. i++) { System. Et bien. vous pouvez (devez) vous aussi ´crire e e plusieurs classes.java). } } Si on met la classe Message dans un fichier Message. 1 echo est une commande Unix qui affiche ses arguments .msg.msg) . Le message e a est fourni par une classe Message class Message { static String msg = "Coucou !" .length .java. De ıne e e sorte que l’on peut facilement ´crire une commande echo en Java. elle sera compil´e automatiquement e lorsque l’on compile le fichier Simple. Le sens du mot-cl´ public est expliqu´ plus loin. MORCEAUX DE JAVA qui fait partie de la biblioth`que standard de Java. alors autant en profiter. } } } Ce qui nous donne apr`s compilation : e % java Echo coucou foo bar coucou foo bar 1.out. Pour structurer vos programmes.1 e class Echo { public static void main (String [] arg) { for (int i = 0 .println(arg[i]).out. Notons que msg est en fait Simple. on peut se passer de rappeler que msg est une variable de la classe Simple. la classe System ´crite par les ea e auteurs du syst`me Java. Le tableau de chaˆ est initialis´ par le syst`me pour contenir les arguments de la ligne de commande.3 Collaboration de classe La classe-programme Simple utilise d´j` une autre classe.174 ANNEXE B. r´crivons le programme simple ` l’aide de deux classes.java (par javac Simple. Par exemple. ıne e e Le reste des obligations porte sur le type de l’argument de main (son nom est libre). e mais dans la classe Simple. la m´thode main doit imp´rativement ˆtre publique (mot-cl´ public) et e e e e e prendre un tableau de chaˆ en argument.println(Message. Encore une bonne raison pour mettre les classes dans des fichiers homonymes. i < arg.

e class Simple { public static void main (String [] arg) { System. Il est alors moins risqu´ de modifier la r´alisation d’un service. static void setEnglish() { hello = "Hello!" . Les autres fournissent des services. chaque m´thode r´alisant une tˆche sp´cifique. } static String getHello() { return hello . La structure des programmes est plus claire car. e e dont on n’a pas besoin de tout savoir. Une m´thode getHello est donc fournie. qui sera maintenu mˆme si la r´alisation de ce service e e change. Deux autres m´thodes laissent la possibilit´ aux utilisateurs de la classe de s´lectionner e e e le message anglais ou le message fran¸ais. pour pouvoir lire le e message.getHello()) . on peut garantir que ces donn´es e e e seront dans un certain ´tat. l’une d’entre elles contient la m´thode main. qui constituent une nouvelle barri`re d’abstraction. est un fondement de la e bonne programmation. } static { setEnglish() . Supposons que la classe Hello doit fournir un message de bienvenue. puisqu’il suffit de contrˆler le code d’une seule classe pour e o s’en convaincre.ˆ 1. la s´paration en classe segmente le programme en unit´s plus petites. } c est ex´cut´ lors de la cr´ation de la classe en machine. la d´marche d’abstraction revient ` ´crire plusieurs e e e a e m´thodes. En fait il suffit normalement de comprendre les d´clarations des e e m´thodes export´es assorties d’un commentaire judicieux. la conception de la classe Hello garantit une propri´t´ : ` tout instant. Finalement. puis en m´thodes. ee a Hello .out. UN LANGAGE PLUTOT CLASSE 175 1. } static void setFrench() { hello = "Coucou !" . e e On parle d’abstraction. puis modifi´s. ce qui assure le choix initial de la langue e e e du message. qui interagissent selon des e e conventions claires qui disent le quoi et cachent les d´tails du comment.e. il suffit de comprendre les m´thodes (et variables) export´es (i. le bout de code static { setEnglish() . La biblioth`que de e e e Java.. } } La variable hello est priv´e (mot-cl´ private) ce qui interdit son acc`s ` partir de code qui e e e a n’est pas dans la classe Hello . ` A l’int´rieur de la classe elle-mˆme. pour comprendre comment les diverses classes interagissent. e c La pratique de restreindre autant que possible la visibilit´ des variables et m´thodes am´liore e e e a ` la fois la sˆret´ et la structuration des programmes. qui est ´norme. e . u e Chaque classe propose un service. est structur´e en packages. Enfin. e class Hello { private static String hello .4 M´fiance de classe e Lorsque l’on fabrique un programme avec plusieurs classes. c On pourra ´crire. En outre. puisque e e seul le code de la classe peut modifier les donn´es priv´es. } } Classe utilis´e par une nouvelle classe Simple. en g´n´ral sous forme de m´thodes accessibles e e e e a ` partir des autres classes. puis en classes. e e non-priv´es) des classes. e e Le d´coupage en packages. Les classes elle-mˆmes peuvent ˆtre e e e a e e e regroup´es en packages.println(Hello. en anglais ou en fran¸ais.hello contient n´cessairement un message de bienvenue en fran¸ais ou en anglais. c’est-`-dire de la production de programmes compr´hensibles et donc de a e programmes qui sont (plus) facilement mis au point.

Mais la classe Foo ne va pas se contenter de d´marrer dans la vie. static void setEnglish() { Hello. ea Toutes les classes dont les sources sont dans le r´pertoire courant sont membres d’un e mˆme package implicite. La m´thode setEnglish ci-dessus doit.176 ANNEXE B. e class HelloGoodBye extends Hello { private static String goodbye . Nous connaissons d´j` quelques emplois de public. puis lance e e e le programme de l’utilisateur.setEnglish() . d’o` l’emploi d’une notation e compl`te Hello . } e e La classe Foo d´marre dans la vie avec toutes les variables et toutes les m´thode de la classe Bar. Comme premi`re approche. e Comme d´montr´ par la nouvelle et derni`re classe Simple. Quand vous lisez la documentation d’une classe de la biblioth`que (par exemple String) e vous avez peut ˆtre d´j` remarqu´ que tout est public (ou tr`s rarement protected). e class Foo extends Bar { . Noter que d´clarer public les autres m´thodes de vos classes e e e e n’a aucun sens. la classe HelloGoodBye a bien e e e re¸u la m´thode getHello en h´ritage.. du plus restrictif au plus permissif. n’est pas membre de ce package. on peut construire une e e e e classe HelloGoodBye qui offre un message d’adieu en plus du message de bienvenue de Hello . goodbye = "Adieu !" .5 Reproduction de classe par h´ritage e La plus grande part de la puissance de la programmation objet r´side dans le m´canisme de e e l’h´ritage.setEnglish. MORCEAUX DE JAVA Il y a en Java quatre niveaux de visibilit´. La classe qui initialise le syst`me d’ex´cution de Java. } static { setEnglish() . a e e La d´claration compl`te de la m´thode toString des objets est e e e public String toString() . } } On note que deux m´thodes sont red´finies (setEnglish et setFrench) et qu’une m´thode est e e e ajout´e (getGoodBye). e public : visible de partout.. pour assurer la coh´rence des e e e u deux messages. elle peut effectivement e ´tendre la classe dont elle h´rite en se donnant de nouvelles m´thodes et de nouvelles variables. } static String getGoodBye() { return goodbye . nous examinons rapidement l’h´ritage et seulement du e e e point de vue de la classe.setFrench() . Soit une classe Foo qui h´rite d’une classe Bar. e private : visible de la classe. Par exemple. e e e Elle peut aussi red´finir les m´thodes et variables h´rit´es. et garantit que les deux messages sont dans la mˆme langue. c e e class Simple { public static void main (String [] arg) { . Et c’est logique. appeler la m´thode setEnglish de la classe Hello . puisqu’il est normal de pouvoir afficher un objet de n’importe o` dans le u programme. Il est donc logique que main soit d´clar´e public. } static void setFrench() { Hello. e ea e e 1. Rien : visible du package. protected visible du package et des classes qui h´ritent de la classe (voir ci-dessous). goodbye = "Goodbye!" . ` moins d’ˆtre train d’´crire un package.

getHello()) . qui calcule la e e e distance euclidienne entre deux points. Autrement dit on sait d´j` que les e ea objets ont des m´thodes.distance(new Point (1. . c’est-`-dire qu’il y e a a en fait deux constructeurs qui se distinguent par le type de leurs arguments. nous en utilisons forc´ment. renvoy´e comme un√ e flottant double pr´cision double.100) . e (1) Les tableaux sont presque des objets. . Les objets sont reli´s aux classes de deux fa¸ons : e c L’objet est cr´´ par un constructeur d´fini dans une classe. Par exemple. )) comme pour les variables de classes. Les points poss`dent aussi des m´thodes. 2 Obscur objet Dans cette section.println(). (2) Les chaˆ ınes String sont des objets. le point est un objet e e e de la classe Point du package java. // Point origine On peut aussi cr´er un point en donnant explicitement ses coordonn´es (enti`res) au construce e e teur : Point p = new Point (100.2.println(HelloGoodBye. l’h´ritage des classes n’a que peu d’int´rˆt en pratique. si t est un tableau t. car l’environnee e e ment d’ex´cution de Java est principalement en style objet. On cr´e un nouveau point par un appel de constructeur e dont la syntaxe est : Point p = new Point () . a Les classes sont aussi plus ou moins les types des objets. 2. voir out.out. e e e On peut ensuite acc´der aux champs d’un point ` l’aide de la notation usuelle.y) System.awt. par exemple la m´thode distance. auxquels on acc`de e e e e par la notation en (( . } } 177 En fait. peuvent ´galement ˆtre surcharg´es. e Un moyen assez compliqu´ d’afficher une approximation de 2 est donc e System. e statiques ou non.println("Le point est sur la diagonale"). La biblioth`que de Java emploie ´norm´ment les objets. . Par exemple.println(HelloGoodBye. Les m´thodes. (3) La biblioth`que construit des objets dont nous appelons les m´thodes.out. Les points e a ont deux champs x et y qui sont leurs coordonn´es horizontales et verticales : e i f (p.out.getGoodBye()) . l’h´ritage des objets est bien e ee e plus utile. On dit que le constructeur de la classe Point est surcharg´ (overloaded ). e e Les objets poss`dent aussi des champs ´galement appel´s variables d’instance.length est la taille du tableau. quand nous ´crivons e Point p = . nous examinons la dialectique question de la classe et de l’objet. 1))) .out. System. OBSCUR OBJET System.x == p. et l’objet garde cette classe-l` toute sa vie.1 Utilisation des objets Sans mˆme construire des objets nous mˆmes. Ce constructeur d´finit la classe ee e e d’origine de l’objet.println(new Point ().

Pair () { this(0. Le concepteur de la classe Pair a e peut par exemple se dire qu’il est bien dommage de devoir cr´er deux objets si on veut sime plement calculer une distance. En revanche. u u .x. Pair p2 = new Pair (2. " + p2. peut. ˆtre e rang´ dans une variable dont le type est une autre classe. ce qui est somme toute peu surprenant. null appartient ` toutes les classes-types.y. De mˆme.distance(p) n’ont pas de raison particuli`res d’ˆtre ´gales. this. int y . les distances a p1. Il pourrait donc inclure la m´thode statique suivante dans sa e classe Pair .x + ". . En effet. affiche (( 0. return Math. } Pair (int x. p.distance(p) et p2. } Il serait alors logique d’´crire la m´thode distance dynamique plutˆt ainsi : e e o double distance(Pair p) { return distance(this.x = x .x . ni champs. ni rien. static double distance(int x1. se comprend comme Pair .out.x et this .sqrt (dx*dx + dy*dy) .y .0) . le programme e Pair p1 = new Pair (0. dans certaines conditions.y.this. e Un premier exemple (assez extrˆme) de cette distinction est donn´ par null . cela signifie qu’un objet dont la classe reste immuable.. La valeur null e e n’a ni classe d’origine (elle n’est pas cr´´ par new).sqrt(dx*dx+dy*dy) .y . . } double distance(Pair p) { int dx = p. ni m´thodes.. Le plus souvent. distance ( this . .this. int x2. int y2) { int dx = x2-x1. On cr´e tr`s facilement une classe des paires (d’entiers) de la fa¸on suivante : e e e e c class Pair { int x . int dy = p. Chaque e objet poss`de ses propres champs.0) dans le constructeur sans e a arguments.x. la classe-type peut changer au cours de la vie l’objet.2).y) . int y) { this. ce qui est logique. . 1) . int y1.y = y . 3 )). } O` bien sˆr.2 Fabrication des objets Notre id´e est de montrer comment cr´er des objets tr`s similaires aux points de la section e e e pr´c´dente.x . System. si p est une autre paire. a 2.x. Dans certaines conditions (voir les sections e 2.sqrt est la racine carr´e e } } Nous avons partout explicit´ les acc`s aux variables d’instance et par this . p.3 et III.y) .println(p1. 3) . MORCEAUX DE JAVA Nous d´clarons une variable de type Point. cela permet de partager le code entre constructeurs. this. // Math. Nous e e nous sommes aussi laiss´s aller ` employer la notation this (0. return Math. On remarque que les champs x et y ne sont pas introduits par le mot-cl´ static.distance ( this .178 ANNEXE B.x. dy = y2-y1 .1. mais alors ee e rien du tout. Rien n’empˆche e e e e de mettre des membres static dans une classe ` cr´er des objets. la m´thode distance e e est propre ` chaque objet.

En Java.toString() produit cette fois ci le r´sultat bien plus satisfaisant (1. sous-typage e L’h´ritage dans toute sa puissance sera abord´ au cours suivant. 0) . l’h´ritage entraˆ le sous-typage. le code suivant est accept´ par e e e e le compilateur. dont la fameuse m´thode toString.print(p) . Les objets de la classe Object (et donc tous les objets) poss`dent quelques e m´thodes. nous aurons beau parcourir la liste des neuf d´finitions de m´thodes print surcharg´es de la classe PrintStream. mais on peut e l’expliciter. le code e de cette m´thode est ´quivalent ` ceci e e a public void print (Object obj) { i f (obj == null) { this. String repr = p. OBSCUR OBJET 179 2. En effet. de print(boolean b) e e e a ` print(Object obj).3 H´ritage des objets. voir la section 1. Et nous obtenons une fois encore l’affichage (1. } else { this. C’est le sous-typage : le type des objets Pair est un sous-type du type des objets Object (penser sous-ensemble : Pair ⊂ Object).print("null") . pour pouvoir r´soudre la surcharge (il s´lectionne la bonne m´thode print e e e parmi les neuf possibles). et s’ex´cute sans erreur.2. System. Par cons´quent. e e e Mais nous pouvons red´finir (override) la m´thode toString dans la classe Pair . } Et l’affichage de p.out. " + y + ")" . Toute classe h´rite implicitement (pas besoin de extends. les objets des classes que nous ´crivons ne d´marrent pas tout e e e nus dans la vie.print(obj. e On note que le compilateur proc`de d’abord ` un sous-typage (il se dit qu’un objet Pair est e a un objet Object). Ce que renvoie la m´thode toString des Object n’est pas bien beau. Mais ´crivons maintenant plus directement : e e e System. nous n’avons bien ´videmment aucune chance d’y trouver une m´thode e e print( Pair p).5) e de la classe Object. 0). e e // public car il faut respecter la d´claration initiale e public String toString() { return "(" + x + ". La conversion de type vers un sur-type est assez discr`te. il n’est au fond pas tr`s surprenant que lorsque nous appelons e e la m´thode toString de l’int´rieur de la classe Pair comme nous le faisons ci-dessus. Tout se passe exactement comme si nous avions ´crit e e une m´thode toString.out. e Pair@10b62c9 e On reconnaˆ le nom de la classe Pair suivi d’un nombre en hexad´cimal qui est plus ou moins ıt l’adresse dans la m´moire de l’objet dont on a appel´ la m´thode toString. Mais nous pratiquons d´j` e e ea l’h´ritage sans le savoir. e e e e Pair p = new Pair (1. En simplifiant un peu. e Mais un objet de la classe Pair peut aussi ˆtre vu comme un objet de la classe Object. ce soit la e e nouvelle m´thode toString qui est appel´e. Mais ` l’int´rieur de e e a e cette m´thode.print(repr) . // Change le type explicitement C’est donc finalement la m´thode print(Object obj) qui est appel´e.out. alors que cette m´thode est en fait h´rit´e. on dit alors plus e ıne synth´tiquement que Pair est une sous-classe de Object. Or. e Mˆme si c’est un peu bizarre.toString() .print((Object)p) . on ne sait pas que obj est en fait un objet Pair . . 0).toString()) . System.

C’est ce que l’on appelle parfois la liaison tardive. Les e e a e valeurs des objets sont des r´f´rences. il y a une petite surprise : c’est la m´thode a e toString() de la classe d’origine (la (( vraie )) classe de obj) qui est appel´e. MORCEAUX DE JAVA C’est-`-dire que. Un sch´ma r´sume la situation. la construction y = x se traduit par une copie de la valeur contenue dans la variable x dans la variable y. une valeur est ce que l’ordinateur manipule facilement. int [] u = t . au cas de null pr`s. elle tient ` la fa¸on dont ces valeurs sont trait´es par la machine. Si on prend un point de vue math´matique. Et l`.1 Scalaires et objets Il y a en Java deux grandes cat´gories de valeurs les scalaires et les objets. 2. qui contiennent chacune une valeur. et non pas la e m´thode toString() des Object. ee le code int y = x . Dans les descriptions qui suivent nous entendons valeur plutˆt dans ce sens technique. Ainsi. Ecrivons par e ee e e exemple int x = 1 . ce qui entraˆ des contraintes : par ıne exemple. Le sch´ma e ee e est une simplification de l’´tat de la m´moire. e 3 Constructions de base Nous ´voquons des concepts qui regardent l’ensemble des langages de programmation. Les valeurs scalaires se suffisent ` elle-mˆmes. Si on prend un point de vue technique. Si x et y sont deux variables. e e 3. un ensemble d’entiers etc. Par variable on entend en o g´n´ral (selon le point de vue technique) une case qui poss`de un nom o` peut ˆtre rang´e une e e e u e e valeur.1. 3} correspond ` une zone de m´moire qui contient des trois entiers. La distinction e est en fait technique. 3} . mais la a e valeur qui est rang´e dans la variable t est une r´f´rence pointant vers cette zone. la m´thode print(Object obj) appelle print(String s) a e e avec comme argument obj. la premi`re valeur e ´tant scalaire et la seconde une r´f´rence. int [] t = {1. 3.180 } } ANNEXE B. une valeur peut ˆtre ` peu pr`s e e a e n’importe quoi. que cette valeur soit une r´f´rence ou non. 2. variables Par valeur on entend en g´n´ral le r´sultat de l’´valuation d’une expression du langage de e e e e programmation. les zones m´moires apparaissent comme des cases e e e (les variables portent un nom) et les r´f´rences apparaissent comme des fl`ches qui pointent vers ee e les cases. Une variable est donc une portion nomm´e de la m´moire de la machine. un entier (de Z).toString(). ou plus a c e exactement sont rang´es dans la m´moire.1 Valeurs. . Une r´f´rence (( pointe )) vers quelque chose (une zone de ee ee ´ la m´moire) — r´f´rence est un nom civilis´ pour pointeur ou adresse en m´moire. un entier n’a pas plus de 32 chiffres binaires (pas plus de 32 bits). e ee e e x 1 t 1 2 3 Le tableau {1. et pas e seulement les langages objet. Les variables x et t sont deux cases.

int [] u = t .out. 3} . int [] u = t . String v = "cou" . System. 3} . e e e t u 1 2 3 v 1 2 3 On dit parfois que == est l’´galit´ physique. affiche t==u : true. 2. L’´galit´ physique donne parfois des r´sultats sure e e e e prenants. Dans les sch´mas nous repr´sentons null ainsi : e e t Puisque null ne pointe nulle part il n’est pas possible de le (( d´r´f´rencer )) c’est ` dire d’aller e ee a voir o` il pointe. CONSTRUCTIONS DE BASE produit l’´tat m´moire simplifi´ suivant. String u = "coucou" . e e e e e Si l’´galit´ de deux scalaires ne pose aucun probl`me. Il existe une r´f´rence qui ne pointe nulle part null. t==v : false.out.3. System. il faut comprendre que == entre deux e e e objets traduit l’´galit´ des r´f´rences et que deux r´f´rences sont ´gales.println(t[1]) . nous pouvons l’employer partout o` une ee u r´f´rence est attendue. 3} .out. int [] v = {1. u[1] = 4 . Les r´f´rences t et u sont ´gales parce qu’elles pointent ee e vers le mˆme objet. e ee Cela peut se comprendre si on revient aux ´tats m´moire simplifi´s. System.println("t==u : " + (t == u) + ". Un essai par exemple de t[0] d´clenche une erreur ` l’ex´cution. Soit le programme Test simple suivant class Test { public static void main (String [] arg) { String t = "coucou" . t==v : " + (t == v)) . t==w : " + (t == w)) . Autrement dit. le programme e e int [] t = {1. 2. e e e x 1 y 1 t u 1 2 3 181 Le sch´ma permet par exemple de comprendre pourquoi (ou plutˆt comment) le programme e o suivant affiche 4. String w = v + v .println("t==u : " + (t == u) + ". u e a e ´ Egalit´ des valeurs e L’op´rateur d’´galit´ == de Java s’applique aux valeurs — ainsi que l’op´rateur diff´rence !=. si et seulement si elles e e ee ee e pointent vers la mˆme zone de m´moire. Les r´f´rences t et v qui pointent vers des objets distincts sont distinctes. } } . 2. ee int [] t = null . int [] t = {1.

java:10: No method matching incr() found in class MalType. Il en r´sulte principae e e e e lement qu’il ne faut pas tester l’´galit´ des chaˆ e e ınes (et ` vrai dire des objets en g´n´ral) par ==. le e programme suivant contient deux erreurs de type : class MalType { static int incr (int i) { return i+1 . c’est-`-dire que si l’on ´crit un programme incorrect e a e du point de vue des types. System. // Oubli d’argument ^ 2 errors Le syst`me de types de Java assez puissant et les classes permettent certaines audaces. // Mauvais type System. un programme a besoin de savoir si deux chaˆ ınes ont exactement les mˆmes caract`res et non pas si elles occupent la mˆme zone de m´moire.1. a e e Dans le cas des chaˆ ınes. ce programme affiche t==u : true.println (afficher une ligne sur e la console). } static void mauvais() { System. alors le compilateur refuse de le compiler.out. // Mauvais type ^ MalType. t u "coucou" w "coucou" La plupart du temps.2 Types G´n´ralement un type est un ensemble de valeurs. parce qu’elles o ont des repr´sentations en machine identiques (byte occupe 8 bits en machine. Ce qui r´v`le e e e e que les chaˆ ınes (objets) r´f´renc´s par t et u sont exactement les mˆmes.out. on peut passer n’importe quoi ou presque en argument. 3. MORCEAUX DE JAVA Une fois compil´ et lanc´.out. mais d’une aide ` la mise au point e e a des programmes : la majorit´ des erreurs stupides ne passe pas la compilation. Ce qui ne nous avance pas beaucoup ! e e Disons plutˆt qu’un type regroupe des valeurs qui vont naturellement ensemble. System. La e plus courante se voit tr`s bien dans l’utilisation de System.1 Typage statique Java est un langage typ´ statiquement. tandis que w est une ee e e autre chaˆ ıne. il existe une m´thode equals sp´cialis´e (voir 6.java MalType. Il s’agit non pas d’une contrainte irraisonn´e impos´e par des informaticiens fous.3) qui compare les e e e chaˆ ınes caract`re par caract`re.println(incr(true)) . un objet Object est un objet).println(incr(true)) .out. 3. La m´thode equals est l’´galit´ structurelle des chaˆ e e e e e ınes. int en occupe 32). e ou surtout parce qu’elles vont naturellement ensemble (un objet Point est un point du plan.2. t==w : false. s´par´ par des (( + )) : e e .out.java:9: Incompatible type for method. // Oubli d’argument } } e La compilation de la classe MalType ´choue : % javac MalType. Can’t convert boolean to int.println(incr()) .182 ANNEXE B. Par exemple.println(incr()) .

3. dans ce cas d’une conversion vers un sur-type.println ("bool´en :" + (10 < 11) + "entier : " + (314*413)) . ee e e Les tableaux sont un cas ` part (voir 3. ` savoir d’abord quatre types (( entier )). Pair p = (Pair)o . c’este e e e p−1 (inclus) et 2p−1 (exclu). . puis dans p : cet objet est finalement une paire. int et a long. . e e il n’y a aucune difficult´ ` parler du type d’une variable qui peut contenir un objet.2. . Ces entiers sont en fait des entiers modulo 2p (avec p respectivement ´gal ` 8.println prend une chaˆ en argument et que le compilateur ins`re des converıne e sions l` o` il sent que c’est utile. . si on change un int (32 bits) en long (64 bits).2. les e caract`res char et deux sortes de nombres flottants. Comme Pair est une sous-classe de Object (ce que sait le compilateur). Par exemple. 32 et 64 chiffres binaires. Nous essayons d’´viter de parler du (( type )) d’un objet. e Elle lui dit de changer son opinion sur expression. Il est malheureusement parfois n´cessaire d’utiliser c e de telles conversions (vers un sous-type) voir III. En revanche. le compilateur saura changer son opinion tout seul si besoin est. Mais a attention.3. System. . a u Il y a huit types scalaires.println(p. byte. e a e Les autres types scalaires sont les bool´ens boolean (deux valeurs true et false ). On dit aussi que les quatre types entiers a `-dire compris entre −2 correspondent aux entiers repr´sentables sur 8. que System.out. dit explicitement au compilateur que l’expression p (normalement de type Pair ) est vue comme un Object. short. les types sont en fait bien plus une propri´t´ du source des programmes. car les repr´sentations internes des scalaires ne sont pas toutes e identiques. et poss`de donc e e une variable d’instance x. 16. CONSTRUCTIONS DE BASE System. ce changement d’opinion est toujours possible et (Object)p ne correspond ` aucun calcul au cours de a l’ex´cution. En fait. la machine r´alise un e . Mais ici. cette fois les conversions entraˆ ınent des transformations des valeurs converties. Il n’en va pas de mˆme dans l’autre sens e Object o = . selon la technique e dite du compl´ment ` la base (l’oppos´ d’un entier n est 2p − n).println((Object)p) .out. Les classes sont des types pour les objets. L’´conomie de m´moire r´alis´e en utilisant les float (sur 32 bits) ` la place des e e e e a double (64 bits) n’a d’int´rˆt que dans des cas sp´cialis´s. la conversion est n´cessaire pour le faire changer d’opinion sur e la valeur d’abord rang´e dans o. que des ee valeurs lors de l’ex´cution. rien ne le garantit. 16.2 Conversion de type La syntaxe de la conversion de type est simple (type)expression La s´mantique est un peu moins simple.out. e On peut aussi convertir le type des scalaires. Le compilateur accepte ce source. et l’expression ( Pair )o correspond ` une a v´rification lors de l’ex´cution. Une conversion de type s’adresse d’abord au compilateur. alors le programme ´choue aussi (en e e e e e lan¸ant l’exception ClassCastException). System. 32 e a et 64) les repr´sentants des classes d’´quivalence modulo 2p sont centr´s autour de z´ro.out. e 183 Cela peut se comprendre si on sait que + est l’op´rateur de concat´nation sur les chaˆ e e ınes. comme nous l’avons d´j` vu ea en page 179 Pair p = .3. Si cette v´rification ´choue. ou du type ea d’un argument objet. on peut mˆme omettre la e e conversion explicite. simple pr´cision float et double pr´cision e e e double.x) .6). mˆme quand on programme proprement en e ne m´langeant pas des objets de classes distinctes. Par exemple. .

5 + (double)2. on veut prendre sa partie enti`re e int e = d . Ensuite. alors on a un r´sultat e e a e ´trange. assez bavard. Le programme e double d = 1e100 . alors que l’on veut ici la division des flottants.2. afin d’effectuer une addition entre double. e e // d est une variable de type double. prenons la partie enti`re d’un double. Le compilateur n’effectue jamais tout seul une conversion qui risque de faire perdre de l’in` formation. et ce dernier est converti afin d’effectuer la division des flottants. Si par exemple on ´crit 1. MORCEAUX DE JAVA certain travail.5 + 2. Il y a un cas o` on ins`re soitu e mˆme ce type de conversions. qui contrairement ` short est e a . On aurait d’ailleurs pu ´crire : e double p = (100.) Si on ne change pas explicitement un des arguments de la division en double. A la place. Par exemple.3 Compl´ment : caract`res e e Les caract`res de Java sont d´finis par deux normes internationales synchronis´es ISO/e e e CEI 10646 et Unicode. pour avoir le mˆme type que l’autre argument e de la multiplication.0 * a) / b . Un char a une double personnalit´. e e La plupart des conversions de types entre scalaires restent implicites et sont effectu´es ` l’occae a sion des op´rations.) et e e e d’autre part un entier sur 16 bits (disons le (( code )) du caract`re). le compilateur change alors a en double. En simplie e e e fiant.184 ANNEXE B. a 3. la division est entre un flottant et un int . il e ´choue. on veut calculer le pourcentage a/b double p = 100 * (((double)a)/b) . En effet. quand une telle conversion est n´cessaire pour typer le programme. Ce calcul n’est pas ici une v´rification. Le compilateur. une valeur du type char occupe 16 chiffres binaires et chaque valeur correspond ` un a caract`re. on doit prendre ses responsabilit´s et ´crire e e int e = (int)d .java:4: found : required: int e possible loss of precision double int = d .out.0E100. " + (int)d) . est d’une part un caract`re (comme ’a’. alors le compilateur arrive ` comprendre e e a 1. mais un changement de repr´sentation. e alors (( / )) est la division euclidienne.println(d + ". nous dit : T. Il faut noter que nous avons effectivement pris le risque de faire n’importe quoi. ^ Dans ce cas. (Notez l’abondance de parenth`ses pour bien sp´cifier les arguments de la conversion et des e e op´rations. // 10100 System. Dans la suite nous ne tenons pas compte de e cette complexit´ suppl´mentaire introduite notamment pour repr´senter tous les id´ogrammes e e e e chinois. conduit ` afficher 1. Si d est trop gros pour que sa partie enti`re tienne dans 32 bits (sup´rieure ` 231 − 1). ’´’ etc. e // a et b sont des int. C’est simplifi´ parce qu’en fait Unicode d´finit plus de 216 caract`res et qu’il faut pare e e e fois plusieurs char pour faire un caract`re Unicode. Le nom g´n´rique le plus appropri´ semblant ˆtre UTF-16. 2147483647.

Les d´clarations e e e de variable sont de la forme suivante : modifiers type name . O` expression est une expression du bon type. (b) Une d´claration sans le modificateur static d´finit une variable d’instance des objets e e de la classe. La premi`re personnalit´ d’un caract`re se r´v`le quand on l’affiche. Par exemple. dont les plus e o fr´quents expriment le (( retour a la ligne )). l’ASCII. Les d´clarations de (( variable )) sont en fait de trois sortes : e (1) Dans une classe. par exemple System.3 D´clarations e De fa¸on g´n´rale. sp´cification de visibilit´ private etc. . voici trois d´clarations de variables. en Windows c’est la a e e s´quence d’un carriage return not´ ’\r’ et d’un line feed. les deux autres (( variables )) existent e .4). "vendredi". "jeudi". e Ces trois sortes de (( variables )) sont toutes des cases nomm´es mais leurs comportements e sont diff´rents. voire tr`s diff´rents : les variables locales sont autonomes. c’est le code de biblioth`que qui se a e charge de fournir les bons caract`res ` la console selon le syst`me sur lequel le programme est en e a e train de s’ex´cuter. et final pour e e e les constantes). (a) Une d´claration avec le modificateur static d´finit une variable (un champ) de la e e classe. En e Unix un retour ` la ligne s’exprime par le caract`re line feed not´ ’\n’. "mardi". int [] ou String) et name est un nom de variable. Dans ce cas seul e e e le modificateur final est autoris´. String [] jours = {"dimanche". L’ASCII regroupe notamment les chiffres de ’0’ ` ’9’ et les lettres (non-accentu´es) mia e nuscules et majuscules.3. Pour ce faire : modifiers type name = expression . "mercredi". et en Mac OS.println() effectue e e toujours un retour ` la ligne sur l’affichage de la console. mais aussi un certain nombre de caract`res de contrˆle. Une petite digression va nous montrer que la stane ` dardisation du jeu de caract`res n’est malheureusement pas suffisante pour tout normaliser. e a 3. ca ´vite bien des e e ¸ e oublis. la seconde e e e e e quand on le compare ` un autre caract`re. mais aussi constante camoufl´e en variable). Les modifiers sont des mots-cl´s ( static . Une bonne pratique est d’initialiser les variables d`s leur d´claration.. CONSTRUCTIONS DE BASE 185 toujours positif. Toutefois des probl`mes peuvent surgir en cas de transfert de fichiers d’un e e syst`me ` l’autre. chaˆ et tableau de chaˆ : ıne ıne int i = 0. le type est un type (genre int . . "lundi". m´thode. Les 128 premiers caract`res (c’est-`-dire ceux dont les a e e a codes sont compris entre 0 et 127) correspondent exactement ` un autre standard international a bien plus ancien.out. (2) Dans une m´thode. "samedi"} . et leurs noms ob´issent e e e e a ` la port´e lexicale (voir “structure de bloc” dans 3. String message = "coucou" . une d´claration de variable d´finit une variable locale. u e de types respectifs (( entier )). une d´claration ´tablit une correspondance entre un nom et une construcc e e e e tion nommable (variable. c’est un carriage return e e tout seul ! Le plus souvent ces d´tails restent cach´s.

De mˆme (( objet. )) final) s´par´es par des virgules (( . e e e La distinction entre expressions (dont l’´valuation produit un r´sultat) et instructions (dont e e l’ex´cution ne produit rien) est assez hypocrite. final e e e e (red´finition interdite). Une instruction (par ex. Appel de m´thode C’est un peu comme pour les champs : e . this d´signe l’objet dont on a appel´ la m´thode. c’est-`-dire le code e e a qui fait le vrai travail. En fait on peut voir les e e ` arguments d’une m´thode comme des variables locales presque normales.186 ANNEXE B. e Les d´clarations de m´thode ne peuvent se situer qu’au niveau des classes et suivent la forme e e g´n´rale suivante : e e modifiers type name (args) Les modifiers peuvent ˆtre. Notez que. car toute expression suivie de (( .x )) e a d´signe le contenu du champ m de C. il n’y a pas lieu de parler d’initialisation ee e e par d´faut des variables locales. elles sont normalement d´sign´es e e comme C.x. e a e 3. Il en r´sulte que this e e n’est jamais null . static (m´thode de classe). e e Acc`s ` un champ Si x est un nom de champ et classe un nom de classe C. o` C et o sont respectivement une classe et un objet. o` x est le nom d’une variable (locale) d´clar´e par ailleurs. u e e Mot-cl´ this Dans une m´thode dynamique .5). objet n’est pas forc´ment un nom. voir le cours suivant) et e e native (m´thode ´crite dans un autre langage que Java). comme le compilateur Java refuse l’acc`s ` une variable locale quand celle-ci n’a pas e a ´t´ initialis´e explicitement. )). En revanche.x et o. e e Les expressions les plus courantes sont : Constantes Soit 1 (entier). contrairement ` x. "coucou !" (chaˆ e ıne). entre accolades (( { )) et (( } )). car si on en est arriv´ ` ex´cuter un corps de m´thode. this d´signe l’objet en cours de construction. Il est notable que les e d´clarations d’arguments sont des d´clarations de variables ordinaires. c’est a e une expression dont la valeur est un objet.x )) d´signe le champ de nom x e e e de l’objet objet. L’ensemble de ces informations constitue la signature e e de la m´thode. e e e e e Dans un constructeur.4 Principales expressions et instructions Nous d´crivons maintenant ce qui se trouve dans le corps des m´thodes. La partie type est le type du r´sultat e e e de la m´thode (void si il n’y en a pas). Heureusement ou malheureusement. )) devient une e instruction (le r´sultat est jet´). alors (( C. qui est un objet sans champs ni m´thodes. Une constante amusante est null . etc. true (bool´en). u Sur une note plus mineure. e Usage de variable Soit (( x )). une affectation) peut inclure une expression. mais font parfois passer l’acc`s ` e e e e e a un champ pour un usage de variable. il existe des notations abr´g´es qui all`gent l’´criture (voir 3. false pour les boolean. ou affect´e avant lecture. MORCEAUX DE JAVA comme sous-parties respectivement d’une classe et d’un objet . c’est bien que ea e e l’objet dont a appel´ la m´thode existait. les variables de classe et d’objet non-initialis´es explicitement e contiennent en fait une valeur par d´faut qui est fonction de leur type — z´ro pour les types e e d’entiers et de flottants (et pour char). name est le nom de la m´thode et args sont les e e d´clarations des arguments de la m´thode qui sont de bˆtes d´clarations de variables (sans le e e e e (( . et null pour les objets. A ceci pr`s qu’il n’y e e a pas lieu d’initialiser les arguments ce qui d’ailleurs n’aurait aucun sens puisque les arguments sont initialis´s ` chaque appel de m´thode. e Suit ensuite le corps de la m´thode. Les e e expressions sont ´valu´es en un r´sultat. une sp´cification de visibilit´. synchronized (acc`s en exclusion mutuelle. Les instruce e tions sont ex´cut´es. Le corps d’une m´thode est une s´quence d’instructions).

. comme dans le cas de la n´gation des bool´ens ! .. objet. soit e d´signation de case m´moire e e e e e qui contient un entier). )) Evidemment. Affectation Par exemple i = 1. e Incr´ment. voir 3. par exemple. Appel de constructeur G´n´ralement de la forme new C (. L’application des op´rateurs est souvent infixe. Cela permet ee essentiellement de contourner les priorit´s relatives des op´rateurs. Notons qu’un e e e (( op´rateur )) en informatique est simplement une fonction dont l’application se fait par une e syntaxe sp´ciale. alors (e) est aussi une expression. ou encore postfixe. -. Par exemple : i *= 2 range deux fois le contenu de i dans i et renvoie donc ce contenu doubl´.). C’est bien sˆr a e u le cas d’une affectation.2.).).). l’expression ` droite de = est calcul´e sa valeur est rang´e dans a e e la variable donn´e ` gauche de =. =. Expression parenth´s´e Si e est une expression. Cela se comprend si on lit cette a e expression comme i = (j = 0). d´cr´ment´e) de i en r´sultat. En fait. ee e L’affectation est une expression dont le r´sultat est la valeur affect´e. e e u Que l’on essaie de deviner ce que fait t[++i] *= --t[i++] et on comprendra. C. En Java. O` m est un nom de m´thode et (.) est une s´quence d’expressions s´par´es par des u e e e e virgules. Les finauds e noteront que ++i est aussi i += 1. il reprend quelques expressions assez peu ordinaires. o` t est un tableau d´fini par ailleurs. d´cr´ment Soit i variable de type entier (en fait. Notez bien que les mˆmes notations abr´g´es que pour les champs s’appliquent e e e au nom de la m´thode. Quelques op´rateurs inattendus sont donn´s en 7. Par exemple. si une m´thode a un type de retour void. son appel e e n’est pas vraiment une expression. c’est-`-dire que l’op´rateur est ıt e e a e avant son argument. pour initialiser i et j ` z´ro. c’est une instruction. comme par exemple dans t[i][j]. un e e ´l´ment de tableau t[i] ou une d´signation de champ objet. on peut trouver tout ce qui d´signe une case de m´moire. Ce qui permet des e e trucs du genre i = j = 0. e e e e e e Affectations particuli`res La construction i op= expression est sensiblement ´quivalente ` i = i e e a op expression. mais il est de bon goˆt de les employer avec parcimonie.m(.m(. ` comprendre e a comme (t[i])[j] (t est un tableau de tableaux).. e e comme pour l’op´rateur de post-incr´ment i++.6 Usage des op´rateurs Par exemple i+1 (addition) ou i == 1 || i == 2 (op´rateur ´galit´ et e e e e op´rateur ou logique). . Mais elle peut ˆtre pr´fixe. L’expression i-. e e e e Acc`s dans les tableaux Par exemple t[i]. 187 dynamique ou bien. cette construction n’est utile que si e fait des effets de bord (c’est-`-dire fait autre chose que rendre son r´sultat)... Ces expressions avanc´es sont g´niales. Enfin l’expression ++i (resp. Les instructions les plus courantes sont les suivantes : ´ Expression comme une instruction : (( e . --i) est e similaire. les op´rateurs e e e eux-mˆmes sont exclusivement compos´s de caract`res non alphab´tiques (+. c’est peut-ˆtre e e plus lisible que i == 1 || i == 2. on peut ´crire (i == 1) || (i == 2). comme souvent. Java a beaucoup emprunt´ au langage C. etc. La construction des tableaux e e est l’occasion de nombreuses variantes. Il n’est pas e u e surprenant que l’on puisse mettre une expression ` la place de i. Alors l’expression i++ range i+1 dans i et renvoie l’ancienne valeur de i. on peut trouver autre chose qu’une variable ` e a a gauche de =..x. mais aussi de rendre e e un source plus clair... CONSTRUCTIONS DE BASE statique Soit.3. Incidemment.fait la mˆme chose avec i-1. c’est-`-dire que l’op´rateur e e a e apparaˆ entre ses arguments. Il est un peu plus surprea nant que cela soit ´galement le cas pour t. mais elle renvoie la valeur incr´ment´e (resp.

Il faut bien comprendre que d’un e affichage ` l’autre l’usage de variable (( i )) ne fait pas r´f´rence ` la mˆme variable.println("i=" + i).out. D´clarations de variables locales Une d´claration de variable (suivie de . affiche une premi`re ligne i=1 puis une seconde i=0. la r`gle est de remonter a e e le source du regard vers le haut. i = i + 1. )). La port´e des d´claration e e e internes au bloc s’´teint ` la sortie du bloc.188 ANNEXE B. alors return n’a pas d’argument. la port´e lexicale.) est une instruction e e qui r´serve de la place pour la variable d´clar´e et l’initialise ´ventuellement. alors e e on peut l’omettre. seule la port´e des variables est limit´e par les blocs. dans le second on cr´e une nouvelle variable. } System. affiche deux lignes i=1. Par exemple. . // Affectation de i System. // D´claration d’un nouvel i e System.out. { int i = 1. } ` A noter que.out. e void rien () { return . e i = i + 1.out. on modifie le e contenu d’une variable qui existe d´j`.println("i=" + i). ea e Bloc On peut mettre une s´quence d’instructions dans un bloc {. dans le premier cas. la m´thode twice qui double son argument entier s’´crit e e int twice (int i) { return i+i . { i = 1. le programme e e int i = 0 . MORCEAUX DE JAVA S´quence On peut grouper plusieurs instructions en une s´quence d’instruction. e e e e int i = 0. } System. } Si la m´thode ne renvoie rien.println("i=" + i). le programme e a int i = 0 . . e e Par exemple. C’est ce que l’on a e appelle parfois. jusqu’` trouver la bonne d´claration. Il ne faut pas confondre affectation et d´claration. e Attention. }. (sans argument). en revanche l’effet des e e instruction passe all`grement les fronti`res de blocs. o` expression est une expression dont le type est celui u des valeurs retourn´es par la m´thode. Par exemple. les instructions e e s’ex´cutent dans l’ordre. si la derni`re instruction d’une m´thode est return .println("i=" + i). Retour de m´thode On peut dans le corps d’une m´thode retourner ` tout moment par l’inse e a truction (( return expression. De sorte que la m´thode rien s’´crit aussi : e e void rien () { } . a ee a e Lorsque l’on veut savoir ` quelle d´claration correspond un usage.

Il faut surtout noter le break. break .out. e break . } } Selon la valeur de l’entier i on s´lectionnera l’une des trois clauses case.out. ou presque.out. } 189 Instruction switch C’est une g´n´ralisation de la conditionnelle aux types d’entiers. break .out.println("z´ro") . } } Boucle while C’est la boucle la plus classique.println("C’est impair") . mais aussi char). qui renvoie le contrˆle ` la fin du switch. } else { System.println("moins un") . e case 0: return "z´ro" . ou la clause par e d´faut default . donc la clause suivante est e e ex´cut´e.println("peu") . } Noter que si la clause se termine par return.3.println("C’est pair") . default: System. Elle permet e e de (( brancher )) selon la valeur d’un entier (de byte ` long. e o a En l’absence de break l’ex´cution se poursuit en s´quence. Voici les entiers de z´ro ` neuf.println("beaucoup") . case 0: System.out. Ainsi si on omet les break et que i vaut -1 on a l’affichage e e moins un z´ro e un beaucoup Cette abominable particularit´ permet de grouper un peu les cas.out. e switch (i) { case -1: case 0: case 1: System. alors break n’est pas utile. e a .out.out. break . case 1: return "un" . Par exemple : a switch (i) { case -1: System. static String estimer(int i) { switch (i) { case -1: return "moins un" . case 1: System. default: System. celle que poss`dent tous les langages de proe grammation. Par exemple. default: return "beaucoup" . CONSTRUCTIONS DE BASE Conditionnelle C’est la tr`s classique instruction if : e i f (i % 2 == 0) { // op´rateur modulo e System.println("un") .println("beaucoup") .

La syntaxe g´n´rale est e e for (einit . Voici une autre fa¸on d’afficher les entiers de 0 c a ` 9. Ce sont des formes polies de goto. qui est une expression bool´enne. i = i+1 . l’it´ration a lieu si econd vaut true. qui fait sortir de la boucle.println(i).. ) { // Boucle infinie. i = i+1 . econd . instruction enext ) L’expression einit est ´valu´e une fois initialement. for (int i=0 . do { System. L’expression econd est de type boolean. e e Boucle do C’est la mˆme chose mais on teste la condition ` la fin du tour de boucle.println(i) . . et de continue qui commence l’it´ration suivante en sautant par dessus ce qui e reste de l’it´ration en cours. i < 10 . on peut ´crire e e boolean trouve = false .190 int i = 0 . int i = 0 . Autrement dit une boucle for est presque la mˆme e e a e e chose que la boucle while suivante. on ex´cute expression. { einit . e e for ( . Il s’agit de break. i++) { . } ANNEXE B. sinon on ex´cute instruction et on recommence. } } Enfin. e e si le r´sultat est false c’est fini. while (i < 10) { System. auquel cas la port´e de la variable est limit´e ` e e e e a la boucle. e Ainsi pour rechercher si l’entier foo est pr´sent dans le tableau t. i < t. notez que econd peut ˆtre omise.out. Cette e ee particularit´ est principalement utilis´e dans l’idiome de la boucle infinie.out. MORCEAUX DE JAVA Soit while (expression) instruction. en ce cas la condition est consid´r´e vraie.println(i) . exceptionnellement (( l’expression )) einit e e peut ˆtre une d´claration de variable. c’est la condition test´e avant chaque e it´ration (y compris la premi`re). enext est e e e ´valu´e ` la fin de chaque it´ration. i = i+1) System. } while (i < 10) Boucle for C’est celle du langage C. Enfin. for (int i = 0 ..length .out. on sort par break ou return . while (econd ) { instruction enext . conclusion : e a on passe au moins une fois dans la boucle. } Gestion du contrˆle Certaines instructions permettent de (( sauter )) quelque-part de l’int´rieur o e des boucles.

Ce qui reste possible en explicitant e les variables d’instance en tant que telles.Point . 50) .C. mais le nom de la variable (m´thode) suffit. i++) { i f (t[i] != foo) continue . Une autre d´claration possible en d´but de fichier est e e import java. On est donc cens´ ´crire par exemple ee java. Dans certaines situations.awt.3.length . Et si le nom de classe Point survient plus tard dans le fichier. le compilateur sait que l’on a voulu dire java. Ainsi.awt. on pourrait ´crire le test d’´galit´. i < t.Point. break .C effectu´es pour toutes les classes C du package java. Dans le premier cas. . } 191 Noter que dans les deux cas on fait des choses un peu compliqu´es. pour l’´viter on peut introduire la d´claration suivante au d´but du fichier e e e e source import java. Dans le e second cas. e e Pour les champs et les m´thodes e Une variable (une m´thode) d’une classe C est normalement d´sign´e en la pr´fixant par e e e e C.awt. C’est tr`s lourd.Point. e 3. Il en va d’ailleurs e de mˆme pour les constructeurs de la classe C.awt.awt. ces instructions e e e sont pratiques (break plus fr´quemment que continue). . Ces masquages ne sont pas toujours malvenus. le nom complet de la classe Point d´finie e e dans le package java. Par exemple.awt. . mais dans le code de la classe elle-mˆme le nom de la variable suffit. e e e e Les notations compl`tes permettent parfois d’insister sur la nature d’une variable. on e pourrait faire une m´thode et utiliser return. List next . for (int i = 0 .awt.awt. ou une boucle while sur trouve. De mˆme. Cela revient aux d´clarations java. class List { int val .Point(100. } } // trouve == true <=> foo est dans t Ou encore. on peut ´crire e int count = 0 . nous avons tendance ` donner aux constructeurs des a arguments homonymes aux variables d’instance initialis´es. dans le e e code des m´thodes des objets (et des constructeurs) une variable d’instance (une m´thode) est e e normalement d´sign´e en la pr´fixant par this .* . et de sure monter le masquage des variables d’instance et de classe par des variables locales. CONSTRUCTIONS DE BASE i f (t[i] == foo) { trouve = true .Point p = new java. count++ . si on veut cette fois compter les occurrences de foo dans t.awt est java.5 Notations compl`tes et abr´g´es e e e Pour les classes (et les constructeurs) Quand une classe C est d´finie dans un package pkg. son nom est pkg.

se ee note (comme dans le langage C) (( type[] )). L’appel de constructeur : new type [taille]. e a t2 t1 . un type des tableaux dont les ´l´ments ee sont de type type tombait du ciel.192 ANNEXE B. pour chaque type type. static String [] ts = new String [3] .next = next . MORCEAUX DE JAVA List (int val.6.6. Par exemple. C’est e e exactement le mˆme principe que pour les variables de classes et d’instance initialis´es par e e d´faut (voir 3. on ´crit : e static int [] ti = new int [3] . compte tenu de la r`gle d’initialisation des objets ` null nous donne.1 Les tableaux Les types Les tableaux sont presque des objets. Ce type des tableaux dont les ´l´ments sont de type type. pour d´clarer et initialiser un tableau t de trois e entiers. // 3 tableaux de cha^nes ı Ce qui. Il est possible d’omettre les derni`res tailles. // tableau de tableaux de cha^nes ı 3. tout se passe plus ou moins comme si. } } Dans le constructeur ci-dessus. ıt e . En particulier on a : int [] ti . // tableau d’entiers // tableau de cha^nes ı Les tableaux (( ` plusieurs dimensions )) ou matrices. et un tableau ts de trois chaˆ ınes. static String [] [] t1 = new String [3][3] static String [] [] t2 = new String [3][] . String [] [] tts . Ainsi les deux d´clarations ci-dessus produisent l’´tat m´moire simplifi´ e e e e e 0 0 0 ts t La notation par constructeur s’applique aussi aux tableaux de tableaux. ce sont en a fait des tableaux de tableaux.6 3. et null pour les objets. il est possible en Java. Les ´l´ments du tableau sont initialis´s (( par d´faut )). n’existent pas en tant que tels. val tout court est l’argument. ı L’aspect tableau de tableaux apparaˆ aussi.3). List next) { this. String [] ts .2 Valeurs tableaux Contrairement ` bien des langages. presque car mais ils n’ont pas vraiment de classe. this .val = val . // 3 x 3 cha^nes. false pour les bool´ens. de fabriquer des tableaux direca tement. On distingue : La constante null avec laquelle on ne peut rien faire est aussi un tableau dont on ne peut rien faire.val est la variable d’instance. this. o` type est le type des ´l´ments du tableau u ee et taille la taille du tableau. ` une valeur qui d´pend de leur type ee e e a e — z´ro pour les entiers et flottants. 3.

e2 . System. a e a La notation explicite ci-dessus est une esp`ce d’abr´viation r´serv´e ` l’initialisation des e e e e a variables. Ce qui compte c’est la port´e des variables. dedans(i) . e La classe de biblioth`que Arrays (package java.out.11.println("i=" + i) .4. Cela signifie que e e e e l’appel de m´thode se fait par copie des valeurs pass´es en argument et que chaque appel de e e m´thode dispose de sa propre version des param`tres.14.out.3.2}.2. Dans le cas des tableaux de tableaux. } L’appel de dehors(0) se traduira par l’affichage de trois lignes i=0..7 Passage par valeur La r`gle g´n´rale de Java est que les arguments sont pass´s par valeur. on ´crira : e int [] [] tj = {{1.13.3. ` e Trouver la longueur d’un tableau : c’est une syntaxe objet.10. Ainsi new int [] {1. {3}}.3 Op´rations sur les tableaux e Il y a deux op´rations primitives : e Acc´der ` un ´l´ment : t[ei ]. Ce qui nous donne ceci : tj 3 1 2 Autrement dit tj[0] est le tableau {1. tout se passe comme si la longueur ´tait le champ length du tableau. . on ´crira : e int [] ti = {1. static void dedans(int i) { i = i+2 . e e e a Examinons un exemple. Notons au passage l’existence du tableau vide (tableau ` z´ro case) {} ` ne pas confondre avec null .5. e 3.15. il faut indiquer le type du tableau en invoquant le constructeur. .9.out. les indices commencent a z´ro. i=2.println("i=" + i) } static void dehors(int i) { System. C’est tr`s seme blable ` l’exemple sur la structure de bloc. On doit noter que Java est ici parfaitement e e coh´rent : si une m´thode f poss`de un argument int x. en } )).6. CONSTRUCTIONS DE BASE 193 Un tableau explicite.2}.println("i=" + i) . pour d´clarer et initialiser le tableau ti des ee e seize premiers entiers. pour e e e a e la dur´e de l’ex´cution de cet appel une nouvelle variable (de nom x) qui est initialis´e ` 2.util) fournit quelques m´thodes toutes stae e tiques qui op`rent sur les tableaux. sous la forme (( {e1 . Le r´sultat a e e est sans doute moins inattendu si on consid`re cet autre programme rigoureusement ´quivalent : e e . o` les ei sont des expressions du u type des ´l´ments du tableau. o` t est le tableau et ei une expression de type entier. 3. {3}} sont des valeurs tableaux.2} et tj[1] est le tableau {3}. e a ee u Attention.length.7.6.8.16} .12. et notamment des tris. alors l’appel f(2) revient ` cr´er. System. i=0.2} et new int [] [] {{1. Par exemple. soit t. Dans un autre contexte.

t[1] += 2.194 static void dedans(int j) { // j = j+2 .2. e 4. e a. } Mais ce que l’on doit bien comprendre c’est que le nom de l’argument de dedans. } L’affichage est le suivant : 1 2 3 3 4 5 Ce qui illustre bien qu’il n’y a.out. tout au cours de l’ex´cution du programme qu’un seul tableau. // sauter une ligne } // trois fa¸ons d’ajouter deux c static void dedans (int[] t) { t[0] = t[0] + 2.println("i=" + i) . l’exp´rience nous apprend que des erreurs se produiront.1 Exceptions lanc´es par le syst`me d’ex´cution e e e Certaines erreurs empˆchent l’ex´cution de se poursuivre d`s qu’elles sont commises. si nous cherchons ` acc´der ` la quatri`me case d’un tableau qui ne comprend que a e a e . ¸ 4 Exceptions Une fois un programme accept´ par le compilateur. c’est une variable muette et heureusement. t[2]++ .out. System. Bien au contraire. affiche (a) .println("i=" + i) . t ´tant juste des noms diff´rents de ce mˆme tableau. ee e // Affichage d’un tableau (d’entiers) pass´ en argument e static void affiche(int[] t) { for (int i = 0 .out.out. Ceci concerne en particulier les tableaux. i ou j n’a pas d’importance. dedans(i) . Par e e e exemple.println() .println("j=" + j) } ANNEXE B. On peut aussi renommer l’argument t e e e en a ca ne change rien.3} . il n’est pas garanti qu’aucune erreur se e produira. i < t. } static void dehors () { e int[] a = {1.length .out. // d´claration et initialisation d’un tableau affiche (a) . dedans (a) . System.print ("t[" + i + "] = " + t[i] + " ") . i++) System. System. La r`gle du passage des arguments par valeur s’applique aussi aux objets. MORCEAUX DE JAVA change ici i -> j static void dehors(int i) { System. mais alors c’est e une r´f´rence vers l’objet qui est copi´e. t[2]++ .

Le syst`me d’ex´cution de Java affiche alors e a e e e un message pour signaler qu’une exception a ´t´ lanc´e.lang.java:7) { { .lang. Pour pr´ciser cet effet de propagation ee e e consid´rons l’exemple suivant d’un programme Square. On e e peut expliquer le m´canisme ainsi : l’exception remplace un r´sultat attendu (par exemple le e e r´sultat d’une division) et ce r´sultat (( exceptionnel )) est propag´ ` travers toutes les m´thodes e e ea e en attente d’un r´sultat. e e e % java Square 11 121 Mais si nous donnons un argument qui n’est pas la repr´sentation d’un entier en base dix. 3. l’indice fautif.forInputString(NumberFormatException.java:3) at Square. L’affichage a e offre quelques bonus.out.1. System.parseInt(Integer.main(Test.4. EXCEPTIONS 195 trois cases.lang.lang.java:4) Le message affich´ nous signale que notre programme a ´chou´ ` cause d’une exception dont e e e a le nom est ArrayIndexOutOfBoundsException (qui semble ` peu pr`s clair). // Lire l’entier en base 10.java:447) at java. le syst`me d’ex´cution ne peut plus rien faire de raisonnable et il fait ´chouer le e e e programme.NumberFormatException: For input string: "bonga" at java.println(i*i). et surtout la ligne du programme qui a lanc´ l’exception. e class Square { static int read(String s) { return Integer. 5}.1 } public static void main(String[] args) int i = read(args[0]).println(a[3]). System. jusqu’` la m´thode main.Integer.read(Square. voir 6.Integer. } } Nous obtenons.ArrayIndexOutOfBoundsException: 3 at Test. class Test { public static void main(String args[]) int [] a = {2. Lancer une exception ne signifie pas du tout que le programme s’arrˆte imm´diatement.java:497) at Square.main(Square.out. nous e obtenons ceci : % java Square bonga Exception in thread "main" java. % java Test Exception in thread "main" java.parseInt(s).java:48) at java. } } Le programme Square est cens´ afficher le carr´ de l’entier pass´ sur la ligne de commande. e D’autres exemples classiques d’erreur sanctionn´e par une exception sont le d´r´f´rencement e eee de null (sanctionn´ par NullPointerException) ou la division par z´ro (sanctionn´e par e e e ArithmeticException).lang.NumberFormatException.parseInt(Integer.

println("Echec sur : " + e) . public static void main(String[] args) { try { int i = read(args[0]). } } Et on obtient alors % java Square bonga Usage : java Square nombre ´chec sur : java. { . Le m´canisme des exceptions est int´gr´ dans le langage. Nous e e e ee devons aussi envisager d’attraper une exception ArrayIndexOutOfBoundsException. } public static void main(String[] args) try { int i = read(args[0]). ce qui veut dire que nous pouvons e e e manipuler les exceptions. En ce cas.out. } } Et on obtient alors. Nous e a e ´crivons read(arg[0]) sans v´rification que le tableau arg poss`de bien un ´l´ment. } catch (NumberFormatException e) { System. Pour ce faire on attrape l’exception ` l’aide d’une instruction sp´cifique.toString() comme ici.println("Usage : java Square nombre") . System. System. Une solution possible est d’´crire : e private static void usage() { System.println(i*i). ou plus fr´quemment e le message contenu dans l’exception par e.NumberFormatException: For input string: "bonga" E Pour ˆtre tout ` fait complet sur l’exemple de Square. System. l’instruction try { instrtry } catch ( E e ) { instrfail } e s’ex´cute comme l’instruction instrtry .println(i*i). Cela permet e a e par exemple d’afficher l’exception coupable par e. ´ System. l’exception attrap´e disparaˆ Dans l’instruce e e ıt.getMessage(). MORCEAUX DE JAVA O` on voit bien la suite des appels en attente que l’exception a du remonter avant d’atteindre u main.lang.println("Usage : java Square nombre") . mais si une exception E est lanc´e lors de cette ex´cution.err. a e public static void main(String[] args) { try { int i = read(args[0]).out. il y a encore un probl`me. e e e alors l’instruction instrfail est ex´cut´e. Ici. la survenue de l’exception trahit une erreur de programmation : en r´action ` une entr´e incorrecte le programme devrait normalement avertir l’utilisateur et e a e donner une explication. % java Square bonga Usage : java Square nombre Sans trop rentrer dans les d´tails. tion instrfail on peut acc´der ` l’exception attrap´e par le truchement de la variable e.196 ANNEXE B.err. } catch (NumberFormatException e) { System.err.exit(2) .err.println("Usage : java Square nombre") .

4. c’est-`-dire du code r´utilisable par autrui. e Supposons par exemple une classe des piles d’entiers. e o The class Exception and its subclasses [. . Ce d´tail mis ` part. . mais qui ne poss`de aucun membre en propre.Empty.. . d’abord d´claration de la classe de notre exception. un message compr´hensible e e e sera affich´. Un constructeur par d´faut Empty () est implicitement fourni. } La classe Empty est d´finie comme un membre statique de la classe Stack (oui. e e class Stack { . e e a il s’agit d’une d´claration de classe normale. } catch (NumberFormatException _) { usage() . ] indicates serious problems that a reasonable application should not try to catch.println(i*i). En fait. Au pire. ici la classe Error. e e Si vous prenez la peine de lire la documentation vous verrez que dans l’esprit des auteurs de la biblioth`que. ıt e En raison de sa simplicit´ nous utilisons syst´matiquement Error dans nos exemples. on se e a e trouve parfois dans la situation de devoir signaler une erreur. . on lance alors une exception par l’instruction throw. mais plutˆt une exception par nous d´finie o e (comme une sous-classe de Exception) . Pour signaler des erreurs r´parables. ceci afin d’´viter les interf´rences avec les exceptions e e lanc´es par les m´thodes de la biblioth`que. e 4. e e Le code indique simplement que Empty est une sous-classe (voir 2. Ainsi l’utilisateur de notre e e composant a l’opportunit´ de pouvoir r´parer son erreur.. . . les exceptions Error ne sont pas cens´es ˆtre attrap´es. Une tentative de de d´piler sur une e pile vide se solde par une erreur. } catch (ArrayIndexOutOfBoundsException _) { usage() . e e e e An Error [. int pop() { i f (isEmpty()) throw new Error ("Pop: empty stack") . Most such errors are abnormal conditions. il vaut mieux ne pas lancer Exception. . . ] indicates conditions that a reasonable application might want to catch. .3 et 1. e e e Nous proc´dons donc en deux temps. de sorte que l’on la d´signe normalement comme Stack. . Il faut alors proc´der exactement e comme le syst`me d’ex´cution de Java et lancer une exception. static class Empty extends Exception { } . c’est pose sible). EXCEPTIONS System. } Il apparaˆ clairement qu’une exception est un objet d’une classe particuli`re. la documentation encourage plutˆt la classe Exception. } } 197 On note qu’il peut en fait y avoir plusieurs clauses catch. Les deux clauses catch appellent ici la m´thode usage.5) de Exception. qui affiche un bref message r´sumant l’usage correct du programme et arrˆte e e e l’ex´cution.2 Lancer une exception Lorsque l’on ´crit un composant quelconque. qui se contente d’appeler le e constructeur Exception ().out.

198

ANNEXE B. MORCEAUX DE JAVA Ensuite, notre exception est lanc´e comme d’habitude. e

int pop() { i f (isEmpty()) throw new Empty () ; ... } Mais alors, la compilation de la classe Stack ´choue. e % javac Stack.java Stack.java:13: unreported exception Stack.Empty; must be caught or declared to be thrown if (isEmpty()) throw new Empty () ; ^ 1 error En effet, Java impose de d´clarer qu’une m´thode lance une exception Exception (mais pas e e e une exception Error). On d´clare que la m´thode pop lance l’exception Empty par le mot cl´ e e throws (noter le (( s ))). int pop() throws Empty { i f (isEmpty()) throw new Empty () ; ... } On peut consid´rer que les exceptions lanc´es par une m´thode font partie de sa signature e e e (voir 3.3), c’est-`-dire font partie des informations ` connaˆ pour pouvoir appeler la m´thode. a a ıtre e Les d´clarations obligatoires des exceptions lanc´es sont assez contraignantes, en effet il faut e e tenir compte non seulement des exceptions lanc´es directement, mais aussi de celles lanc´es e e indirectement par les m´thodes appel´es. Ainsi si on con¸oit une m´thode remove( int n) pour e e c e e e enlever n ´l´ments d’une pile, on doit tenir compte de l’exception Empty ´ventuellement lanc´e ee par pop. void remove (int n) throws Empty { for ( ; n > 0 ; n--) { pop() ; } } Noter qu’il est ´galement possible pour remove de ne pas signaler que la pile comportait moins e de n ´l´ments. Cela revient ` attraper l’exception Empty, et c’est d’autant plus facile que remove ee a ne renvoie pas de r´sultat. e void remove (int n) { try { for ( ; n > 0 ; n--) { pop() ; } } catch (Empty e) { } }

5

Entr´es-sorties e

Par entr´es-sorties on entend au sens large la fa¸on dont un programme lit et ´crit des e c e donn´es, et au sens particulier l’interaction entre le programme et le syst`me de fichiers de la e e machine.

´ 5. ENTREES-SORTIES

199

Un fichier est un ensemble de donn´es qui survit ` la fin de l’ex´cution des programmes, et e a e mˆme, la plupart du temps, ` l’arrˆt de l’ordinateur. Les fichiers se trouvent le plus souvent sur e a e le disque de la machine, mais ils peuvent aussi se trouver sur une disquette, ou sur le disque d’une autre machine et ˆtre acc´d´s ` travers le r´seau. Un syst`me d’exploitation tel qu’Unix, e e e a e e offre une abstraction des fichiers, g´n´ralement sous forme de flux (stream). Dans sa forme la e e plus simple le flux offre un acc`s s´quentiel : on peut lire le prochain ´l´ment d’un flux (ce e e ee qui le consomme) ou ´crire un ´l´ment dans un flux (g´n´ralement ` la fin). Dans la majorit´ e ee e e a e des cas (et surtout en Unix), les fichiers contiennent du texte, il est alors logique, vu de Java, de les consid´rer comme des flux de char. Par fichier texte, nous entendons d’abord fichier e qu’un humain peut ´crire, lire et comprendre, par exemple un source Java. Les fichiers qui ne e s’interpr`tent pas ainsi sont dits binaires, par exemple une image ou un fichier de bytecode Java. e Il est logique, vu de Java, de consid´rer les fichiers binaires comme des flux de byte. Par la suite e nous ne consid´rons que les fichiers texte. e Quand vous lancez un programme dans une fenˆtre de commandes (shell ), il peut lire ce que e vous tapez au clavier (flux d’entr´e standard) et ´crire dans la fenˆtre (flux de sortie standard). e e e Le shell permet de rediriger les flux standard vers des fichiers (par <fichier et >fichier), ce qui permet de lire et d’´crire des fichiers simplement. Enfin il existe un troisi`me flux standard, la e e sortie d’erreur standard normalement connect´e ` la fenˆtre comme la sortie standard. Mˆme e a e e si cela ne semble pas utile, il faut ´crire les messages de diagnostic et d’erreur dedans, et nous e allons voir pourquoi.

5.1

Le minimum ` savoir : une entr´e et une sortie standard a e

Nous connaissons d´j` la sortie standard, c’est System.out — et System.err est la sortie ea d’erreur. Nous savons d´j` ´crire dans ces flux de classe PrintStream, par leurs m´thodes print eae e et println. Pour lire l’entr´e standard (System.in, comme vous pouviez vous en douter), c’est e un rien plus complexe, mais sans plus. Il faut tout d’abord fabriquer un flux de caract`res, objet de la classe, Reader (voir aussi 6.2.1) e a ` partir de System.in : Reader in = new InputStreamReader(System.in) ; On note que l’objet InputStreamReader est rang´ dans une variable de type Reader. Et en e effet, InputStreamReader est une sous-classe de Reader (voir 2.3). Ensuite, on peut lire le prochain caract`re de l’entr´e par l’appel in.read(). Cette m´thode e e e renvoie un int , avec le comportement un peu tordu suivant : Si un caract`re est lu, il est renvoy´ comme un entier. e e Si on est ` la fin de l’entr´e, -1 est renvoy´. La fin de l’entr´e est par vous signal´e par a e e e e (( Control-d )) au clavier et est la fin de fichier si il y a eu redirection. En cas d’erreur l’exception IOException est lanc´e. Ces erreurs sont du genre de celles e d´tect´es au niveau du syst`me d’exploitation, comme la panne d’un disque (rare !), ou e e e un d´lai abusif de transmission par le r´seau (plus fr´quent). Bref, l’exception est lanc´e e e e e quand, pour une raison ou une autre, la lecture est jug´e impossible. e Voici Cat un filtre trivial qui copie l’entr´e standard dans la sortie standard : e import java.io.*; // La classe Reader est dans le package java.io class Cat { public static try { Reader in for ( ; ; int c = void main(String [] arg) { = new InputStreamReader(System.in) ; ) {// Boucle infinie, on sort par break in.read ();

200 i f (c == -1) break; System.out.print ((char)c);

ANNEXE B. MORCEAUX DE JAVA

} } catch (IOException e) { // En cas de malaise System.err.print("Malaise : " + e.getMessage()) ; System.exit(2) ; // Terminer le programme sur erreur } } } Remarquons : Il y a un try. . . catch (voir 4.1), car la m´thode read d´clare lancer l’exception IOException, e e ` qui doit ˆtre attrap´e dans main si elle n’est pas d´clar´e dans la signature de main. A la fin e e e e du traitement de l’exception, on utilise la m´thode System.exit qui arrˆte le programme e e d’un coup. Le message d’erreur est ´crit dans la sortie d’erreur. e Pour afficher c comme un caract`re, il faut le changer en caract`re, par (char)c. Sinon, e e c’est un code de caract`re qui s’affiche. e On peut tester le programme Cat par exemple ainsi : % java Cat < Cat.java import java.io.*; // La classe Reader est dans le package java.io . . .

5.2

Un peu plus que le minimum : lire des fichiers

La commande cat de Unix est plus puissante que notre Cat. Si on donne des arguments sur la ligne de commande, ils sont interpr´t´s comme des noms de fichiers. Le contenu des fichiers ee est alors affich´ sur la sortie standard, un fichier apr`s l’autre. La commande cat permet donc e e de concat´ner des fichiers. Par exemple e % cat a a > b range deux copies mises bout-`-bout du contenu du fichier a dans le fichier b. Comme souvent, si a on ne donne pas d’argument ` la commande cat, alors c’est l’entr´e standard qui est lue. Ainsi a e cat < a > b copie toujours le fichier a dans le fichier b. Pour lire un fichier ` partir de son nom, on construit une nouvelle sorte de Reader : un a FileReader. Cela revient ` ouvrir un fichier en lecture, c’est-`-dire ` demander au syst`me a a a e d’exploitation de mobiliser les ressources n´cessaires pour cette lecture. Si name est un nom de e fichier, on proc`de ainsi. e Reader in = new FileReader (name) ; Si le fichier de nom name n’existe pas, une exception est lanc´e (FileNotFoundException e sous-classe de IOException). Il est de bon ton de fermer les fichiers que l’on a ouvert, afin de rendre les ressources mobilis´es e lors de l’ouverture. En effet un programme donn´ ne peut pas poss´der plus qu’un nombre fix´ e e e de fichiers ouvert en lecture. On ferme un Reader en appelant sa m´thode close(). Il faut e noter qu’une tentative de lecture dans une entr´e ferm´e ´choue et se solde par le lancement e e e d’une exception sp´cifique. Voici la nouvelle classe Cat2 . e import java.io.* ; class Cat2 { static void cat(Reader in) throws IOException { for ( ; ; ) {

´ 5. ENTREES-SORTIES int c = in.read (); i f (c == -1) break; System.out.print ((char)c); } } public static void main(String [] arg) { i f (arg.length == 0) { try { cat(new InputStreamReader(System.in)) ; } catch (IOException e) { System.err.println("Malaise : " + e.getMessage()) ; System.exit(2) ; } } else { for (int k = 0 ; k < arg.length ; k++) { try { Reader in = new FileReader(arg[k]) ; cat(in) ; in.close() ; } catch (IOException e) { System.err.println("Malaise : " + e.getMessage()) ; } } } } }

201

Remarquons : La lecture d’un fichier est r´alis´e par une m´thode cat, car cette lecture s’op`re dans e e e e deux contextes diff´rents. On note aussi l’int´rˆt du sous-typage : l’argument Reader in e ee est ici un objet InputStreamReader ou FileReader, mais comme il n’est pas utile de le savoir dans la m´thode cat, on en profite pour partager du code. e Le second try. . . catch est dans la boucle for ( int k = 0 ;. . . , et sa clause catch ne contient pas d’appel ` System.exit. Il en r´sulte que le programme n’´choue pas en cas a e e d’erreur de lecture d’un fichier donn´ par son nom — ou mˆme de fichier inexistant, car e e FileNotFoundException est une sous-classe de IOException. Au lieu d’´chouer, on e passe au fichier suivant. Les messages d’erreur sont ´crits dans la sortie d’erreur et non pas dans la sortie standard. e Ainsi si le fichier a existe mais que le fichier x n’existe pas, la commande suivante a un comportement raisonnable. % java Cat2 a x a > b Malaise : x (No such file or directory) Un message d’erreur est affich´ dans la fenˆtre, et b contient deux copies de a. e e

5.3

Encore un peu plus que le minimum : ´crire dans un fichier e

La sortie standard est souvent insuffisante, on peut vouloir ´crire plus d’un fichier ou ne e pas obliger l’utilisateur ` faire une redirection. On se dit donc qu’il doit bien y avoir un moyen a d’obtenir un flux de caract`res en sortie connect´ ` un fichier dont on donne le nom. Le flux e e a de sortie le plus simple est le Writer, qui poss`de une m´thode write( int c) pour ´crire un e e e caract`re. Comme pour les Reader on utilise en pratique des sous-classes de Writer. Il existe e entre autres un FileWriter que nous utilisons pour ´crire une version simple de la commande e

) { int c = in. System. est la suivante : l’´criture ou la lecture effective de caract`res a un coˆt fixe e e e e e u important. e . try { Reader in = new FileReader(name1) .exit(2) . for ( .length != 2) { System. L’effet est particuli`rement ne pas se trouver dans le fichier e e gˆnant ` la fin du programme. } } } Il y a peu ` dire. Writer out = new FileWriter(name2) . Ou encore.write(c) .202 ANNEXE B. o` cp name1 name2 copie le contenu du fichier name1 dans le fichier name2 . mais remarquons que nous prenons soin de fermer les flux que nous ouvrons. Cela peut s’expliquer par divers ph´nom`nes.println("Malaise : " + e.getMessage()) . u import java. ´crire ou lire n caract`res coˆte de l’ordre de K0 +K1 ·n. et une op´ration d’entr´e-sortie signifie un seul e e e appel syst`me.exit(2) . } out.* . de par la nature mˆme du dispositif physique (( disque )) qui lit et ´crit des e e donn´es par blocs de taille fix´e. le tampon est de la m´moire appartenant au programme et qui donc disparaˆ avec lui. Le tampon a une taille fixe.6 pour tout savoir ou presque). quelque soit le nombre de caract`res impliqu´s.write(c). . Si le tampon n’est pas vid´ (flushed ). prenons l’exemple de l’´criture. L’id´e est alors de ne pas ´crire effectie e e e vement chaque caract`re en r´action ` un appel out. e e Pour fixer les id´es. a Cela semble une bonne habitude ` prendre (voir aussi 5. un appel e e au syst`me d’exploitation est relativement lent. Les transferts c u e e e e vers les fichiers ` travers un tampon m´moire pr´sentent l’inconv´nient que le caract`re c peut a e e e e 2 d`s que l’on appelle out. son contenu est perdu.read() . Il faut donc vider e e e 2 Ou plus exactement c n’est pas dans les tampons du syst`me d’exploitation. out. o` K0 e e e e u u est bien plus grand que K1 . } catch (IOException e) { System. name2 = arg[1] . le coˆt des transferts e e e u entre disque et m´moire est largement ind´pendant du nombre de caract`res transf´r´s. class Cp { public static void main(String [] arg) { i f (arg. in.4 Toujours plus loin : entr´es-sorties bufferis´es e e La documentation recommande l’emploi de flux bufferis´s (buffered d´sol´ pas de terme e e e fran¸ais ad´quat) pour atteindre l’efficacit´ maximale (( top efficiency )). qui est pay´ quelque soit le nombre de caract`res effectivement transf´r´s entre fichier e e ee et programme.println("Usage : java Cp name1 name2") . Plus pr´cis´ment. a 5. i f (c == -1) break . Par exemple. En e a e effet.err.close() . en route vers le fichier. } String name1 = arg[0]. Il e ıt en r´sulte g´n´ralement que la fin du flux ne se retrouve pas dans le fichier.io.write(c) mais ` la place de le ranger dans e e a a une zone m´moire appel´e tampon (buffer ). System. jusqu’` e e e ee a une certaine taille. et les caract`res du tame e e pon sont effectivement transmis au syst`me d’exploitation quand le tampon est plein. L’id´e des flux bufferis´s.err.close() . c e e e e qui est tr`s g´n´rale. MORCEAUX DE JAVA Unix cp . De cette e fa¸on le coˆt fixe K0 est pay´ moins souvent et l’efficacit´ totale est am´lior´e.

lecture) est un objet de la classe BufferedWriter (resp. les PrintStream de la biblioth`que sont tr`s inefficaces. Les flux bufferis´s BufferedWriter et BufferedReader offrent aussi une vue des fichier e texte comme ´tant compos´s de lignes. qui est rempli ` partir du fichier quand une demande de lecture trouve un tampon a vide. Malheue e reusement.5.1 Entr´es-sorties format´es e e Sorties format´es e Nous savons d´sormais ´crire dans un fichier de nom name de fa¸on efficace en construisant e e c un FileWriter. e e e 5.3). e a e Un flux bufferis´ en ´criture (resp. e avec les mˆmes b´n´fices en terme d’efficacit´. On peut aussi vider le tampon plus directement en appelant m´thode flush() des flux bufferis´s. Dans ce cas. la copie du e e . Writer out = new BufferedWriter (new FileWriter name) . ´crire caract`re par caract`re (ou mˆme par chaˆ avec write(String str)). et qui a reste un Writer (resp.println("Malaise : " + e. e e import java. avant de fermer effectivement le flux. } out. Voici une autre version Cp2 de la commande cp ´crite en e Java.getMessage()) .read() . les lectures (read) se font dans le e e e e tampon. name2 = arg[1] . Toutefois. } catch (IOException e) { System.err. class Cp2 { public static void main(String [] arg) { String name1 = arg[0]. La m´thode print de System. Il existe une m´thode Newline pour ´crire une fin e e e e le ligne. out. ce que fait la m´thode close() de fermeture des e flux.´ 5. } } } Une mesure rapide des temps d’ex´cution montre que le programme Cp2 est ` peu pr`s trois e a e fois plus rapide que Cp. Il y a alors bien entendu une avance ` la lecture mais ce d´calage ne pose pas les mˆmes a e e probl`mes que le retard ` l’´criture. qui emploie les entr´es-sorties bufferis´es. qui se construit simplement ` partir d’un Writer (resp. ) { int c = in.write(c) . puis un BufferedWriter. for ( . et une m´thode readLine pour lire une ligne. Reader). e e BufferedReader). Reader). n’est e e e e ıne pas toujours tr`s pratique. Par exemple.5 5. System. la technique du tampon s’applique ´galement. ENTREES-SORTIES 203 le tampon avant de terminer le programme. i f (c == -1) break . in.close() . . e Pour ce qui est d’un fichier ouvert en lecture. try { Reader in = new BufferedReader (new FileReader(name1)) .* . Il faut aussi noter que les lignes sont d´finies e a e ind´pendamment de leur r´alisation par les divers syst`mes d’exploitation (voir 3.exit(2) .io.out est bien plus commode.2. L’existence d’un retard entre ce qui est ´crit par e e e le programme dans le flux et ce qui est effectivement ´crit dans le fichier donne donc une raison e suppl´mentaire de fermer les fichiers.close() . Writer out = new BufferedWriter (new FileWriter (name2)) . On utilise souvent BufferedReader e pour cette fonctionnalit´ de lecture ligne ` ligne.

sieve(n. on aurait tr`s bien pu (( emballer )) la sortie standard dans un PrintWriter. } while (t[p]) .getMessage()) . Par exemple. k += p) t[k] = true . Les objets de la classe PrintWriter sont des Writer (flux de caract`res) qui offrent les sorties e format´es. . try { PrintWriter out = new PrintWriter ("primes. for (int k = p+p . out) . .txt") . int p = 2 . ) { out. System. Noter aussi que la sortie out est vid´e (et mˆme ferm´e) par la e e e m´thode main qui l’a cr´´e. } } La nouvelle m´thode sieve ´crit dans le PrintWriter out dont l’initialisation est rendue p´nible e e e par les exceptions possibles. k <= n . c’est-`-dire qu’ils poss`dent une m´thode print (et prinln) surcharg´e qui all`ge un e a e e e e peu la programmation. } catch (IOException e) { System. Il y a plusieurs constructeurs des PrintWriter. On s’en rend compte en modifiant sieve pour utiliser un PrintWriter.txt. a static void sieve(int n) { boolean [] t = new boolean [n+1] . k += p) t[k] = true . for ( . i f (p > n) return . k <= n .out. do { p++ . } } public static void main(String [] arg) { . voici par exemple une m´thode simple qui affiche e e les nombres premiers jusqu’` n directement dans System. Par ailleurs. } while (t[p]) . int p = 2 .out. /* Chercher le prochain p premier */ do { p++ . } } L’application du crible d’Eratosth`ne est na¨ mais le programme est inefficace d’abord ` cause e ıve.close() . PrintWriter out) throws IOException { boolean [] t = new boolean [n+1] . . i f (p > n) return .err. qui prennent divers e ee arguments. et non pas dans le e fichier primes. . Pour ce qui est du temps d’ex´cution des essais rapides e . si l’on avait voulu ´crire dans la sortie standard. les PrintWriter sont bufferis´s par d´faut.exit(2) . e par new PrintWriter(System.204 ANNEXE B.println("Malaise: " + e.println(p) .out).println(p) . out. a de ses affichages. e e static void sieve(int n. // Afficher p premier /* Identifier les multiples de p */ for (int k = p+p . for ( . MORCEAUX DE JAVA fichier a dans le fichier b par java Cat < a > b est environ vingt-cinq fois plus lente que java Cp2 a b ! Mais la m´thode print est bien pratique. ) { System.

err. Nous insistons donc : System. e ¸ e Heureusement. while (n % p == 0) { out. int p = scan. ENTREES-SORTIES 205 montrent que le nouveau sieve (avec PrintWriter) peut ˆtre jusqu’` dix fois plus rapide que e a l’ancien (avec PrintStream). Il poss`de aussi des m´thodes sp´cialis´es pour lire des lex`mes particuliers e e e e e e ou savoir si le lex`me suivant existe et est un lex`me particulier (par exemple nextInt() et e e hasNextInt() pour un int ).hasNextInt()) throw new IOException ("Format of file " + filename) . etc.flush() .length . } } public static void main(String arg []) { for (int k = 0 . } catch (IOException e) { System.println("Malaise : " + e. e il faut renoncer ` son emploi d`s qu’il y a beaucoup de sorties. une chaˆ ıne. C’est d´j` pas mal. On peut donc utiliser primes. les BufferedReader permettent e e de lire ligne par ligne.txt de la section pr´c´dente.out est vraiment tr`s inefficace. out. private static void factor (int n.util. mais c’est parfois insuffisant. Par d´faut les lex`mes reconnus par un Scanner sont les mots de Java — un entier. } . Ce que l’on veut alors c’est e e lire une s´quence d’entiers int .txt pour d´composer un int en e facteurs premiers ainsi (mˆme si ce n’est pas une tr`s bonne id´e algorithmiquement parlant).close() .* .* . a e 5. String filename) { try { Scanner scan = new Scanner (new FileReader (filename)) . Pour ce faire nous pourrions reconstituer nous mˆme les int ` e e a partir de la s´quence de leurs chiffres. scan.2 Entr´es format´es e e Les Reader permettent de lire caract`re par caract`re. import java. Un Scanner poss`de une m´thode next() qui renvoie le lex`me suivant du flux d’entr´e e e e e (comme un String) et une m´thode hasNext() pour savoir si il existe un lex`me suivant.println() . mais ca ne serait pas tr`s moderne.io.out) .exit(2) . e Les lex`mes sont simplement ` un langage informatique ce que les mots sont ` un langage dit e a a naturel. System.parseInt(arg[k]).util.nextInt() .print(n + ":") . while (n > 1) { i f (!scan. e e e import java. n /= p . out. class Factor { private static PrintWriter out = new PrintWriter (System. on peut ea imaginer vouloir relire le fichier primes.5. un e e identificateur. // Pour n = n / p .txt") .getMessage()) .print(" " + p) . k < arg. Les objets Scanner sont des flux de lex`mes (tokens). il existe des objets qui savent faire ce style de lecture pour nous : ceux de la classe Scanner du package java. } } out. "primes. Par exemple.´ 5. k++) factor(Integer. ou si e e le flux est termin´.

e e e L’encodage ISO-8859-1 est techniquement le plus simple possible : les codes des caract`res e sont les mˆmes ` la fois en ISO-8859-1 et en Unicode. C’est un encodage simple sur 8 bits. o` les caract`res Unicode sont repr´sent´s par un e u e e e nombre variable d’octets selon un syst`me un peu compliqu´ que nous ne d´crirons pas (voir e e e http ://fr. En effet. au d´pend du caract`re Unicode U+00A4 ( ) e e e qui n’est plus exprimable. Pour voir les cae ract`res d´finis en ISO-8859-1. un fichier de texte est un flux de char (16 bits). On note aussi que l’entr´e scan est ferm´e explicitement par scan. Pour Java. Pour lire un flux d’octets comme un flux de char. L’encodage est ici implicite. mais il faut s’assurer que les e sorties du programme sont bien envoy´es au syst`me d’exploitation. Il n’est donc tout simplement pas e a possible d’exprimer les char dont les codes sont sup´rieurs ` 256 en ISO-8859-1 (dont juse a tement e. "UTF-8") . comme UTF-8 est un encodage multie e octets. On peut aussi expliciter l’encodage en e donnant son nom comme second argument au constructeur. l’encodage que java emploie par d´faut sur les machines de l’´cole.206 } ANNEXE B. ils ne sont plus d’accord sur ce que sont les ´l´ments ee d’un tel fichier.6 Compl´ment : tout ou presque sur les fichiers texte e Si Java et Unix sont bien d’accord sur ce qu’est en gros un fichier texte (un fichier qu’un humain peut lire au moins en principe).flush(). un byte pour Java). e e Un flux d’octets est un InputStream. Il existe d’autres e e encodages 8 bits. il ne e serait pas ad´quat de fermer la sortie out qui sert plusieurs fois. Reader inChars = new InputStreamReader (inBytes. il y a des flux de byte e e (OutputStream) et des flux de char (Writer). InputStream inBytes = new FileInputStream (name) . Un exemple simple d’encodage est par exemple ISO-8859-1. Par exemple.close(). Le passage de l’un ` l’autre demande a d’appliquer un encodage. Ici. avec une classe pour faire le pont entre les deux (OutputStreamWriter). mais alors un caract`re Unicode peut s’exprimer comme plusieurs octets. vous pouvez essayer man latin1 sur une machine de l’´cole. e Par exemple on ouvre un flux d’octets sur un fichier name en cr´ant un objet de la classe e FileInputStream. C’est un encodage qui fonctionne pour presque toutes e e les langues europ´ennes (mais pas pour le symbole e). voici comment fabriquer un Writer connect´ ` ea la sortie standard et qui ´crit de l’UTF-8 sur la console : e . Pour fixer les id´es nous consid´rons d’abord les flux en lecture. c’est l’encodage par d´faut. d`s e e e qu’elle n’est plus utile . tandis que la sortie n’est que vid´e par out. Il y a bien entendu d’autres a constructeurs. Et voil` nous disposons maintenant d’un Reader sur un fichier dont la suite d’octets d´finit a e une suite de caract`res Unicode encod´s en UTF-8. dont la valeur Unicode hexad´cimale est 0x20AC. e e 5. on fabrique un InputStreamReader. la lecture d’un char dans inChars implique de lire un ou plusieurs byte dans le flux inBytes sous-jacent. Il existe bien entendu des encodages qui permettent d’exprimer tout Unicode. dont ISO-8859-15 qui entre autres ´tablit justement la correspondance entre e le caract`re Unicode U+20AC et le byte 0xA4. Un e encodage multi-octet r´pandu est UTF-8. MORCEAUX DE JAVA On note que le Scanner est construit ` partir d’un Reader. Revenons aux flux de Java. Reader inChars = new InputStreamReader (inBytes) . La classe InputStream fonctionne sur le mˆme principe e que la classe Reader : c’est une sur-classe des divers flux de byte qui peuvent ˆtre construits.wikipedia.org/wiki/UTF-8 qui est raisonnablement clair). not´e U+20AC). e qui permet d’exprimer 256 caract`res seulement parmi les 216 d’Unicode. tandis que pour Unix c’est un flux d’octets (8 bits. Les flux en ´criture suivent un sch´ma similaire.

mais ce n’est qu’une apparence. il aurait e e e mieux valu employer les flux de byte.5. En particulier. la version web du polycopi´ comporte des liens vers la documentation en ligne. il y aura toujours d´codage et encodage. syst`me d’ex´cution etc. u e 6 Quelques classes de biblioth`que e En g´n´ral une biblioth`que (library) est un regroupement de code suffisamment structur´ et e e e e document´ pour que d’autres programmeurs puissent s’en servir.out (un PrintStream) est aussi un OutputStream Writer outChar = new OutputStreamWriter (System. qui prennent un Reader ou un Writer en argument. e On objectera que la biblioth`que est ´norme. e La premi`re source de documentation des classes de la biblioth`que de Java est bien entendu e e la documentation en ligne4 de cette derni`re. employer a e ce constructeur synth´tique revient ` : e a new PrintWriter (new OutputStreamWriter (new FileOutputStream (name))) .3 Comme on e a tendance a tout simplifier on a parfois des surprises : par exemple. mais ` quelques optimisations internes toujours possibles pr`s. On se rend vite compte de l’existence de ce tampon si on e ea oublie de fermer un FileWriter. Cette section entend surtout vous aider ` prendre la bonne habitude a de consulter la documentation du langage que vous utilisez.. en th´orie nous savons lire et ´crire tout Unicode. 4 http://java. mais c’est une autre histoire e e qui ne regarde plus Java. Toutes ces histoires d’encodage et de d´codage de char en byte font qu’´crire un char dans e e un Writer ou lire un char dans un Reader ne sont jamais des op´rations simples. Et bien. C’est inutilement inefficace et mˆme dangereux certains d´codages pouvant parfois ´chouer. par exemple un Scanner ou un PrintWriter etc. Le tampon (de char) u e e introduit en amont du FileWriter par new BufferedWriter (new FileWriter (name2)) a alors pour fonction d’amortir ce coˆt irr´ductible de l’encodage.sun.0/docs/api 3 . ou comme on dit la Application Programmer Intere face Specification. En pratique. puisque le coˆt irr´ductible de la u e v´ritable sortie est d´j` amorti par un tampon. La majeure e e e partie de des programmes disponibles dans la biblioth`que d’un langage est normalement ´crite e e dans ce langage lui-mˆme.4). ` l’aide des constructeurs (( naturels )) a de ces classes.` 6.3) poss`dent en fait d´j` un tampon. 207 Notons qu’une fois obtenu un Reader ou un Writer nous pouvons fabriquer ce dont nous avons besoin. il se trouve que le coˆt de l’application e ea u de l’encodage des char vers les byte suit lui-aussi le mod`le d’un coˆt constant important e u relativement au coˆt proportionnel au nombre de caract`res encod´s. Il s’agit donc d’un tampon de byte dans lequel le FileWriter stocke les octets r´sultants de e l’encodage qu’il effectue. new PrintWriter (String name) ouvre direce tement le fichier name. Nous donnons donc des extraits de cette documentation assortis de quelques commentaires personnels. The resulting bytes are accumulated in a buffer before being written to the underlying output stream. On peut alors se demander d’o` provient le gain d’efficacit´ constat´ en u e e emballant un FileWriter dans un BufferedWriter (voir 5. Si on en croit la documentation : Each invocation of a write() method causes the encoding converter to be invoked on the given character(s).com/j2se/1. il faut encore pouvoir e e entrer ces caract`res au clavier et les visualiser dans une fenˆtre.out. Mais en pratique on s’y retrouve assez vite : e e Encodage et d´codage font aussi qu’il ne faut pas ´crire Cp et Cat comme nous l’avons fait (avec des flux e e de char). "UTF-8") . La biblioth`que associ´e ` un e e e a langage de programmation est diffus´e avec le compilateur. les FileWriter (voir 5. Finalement. Par exemple. QUELQUES CLASSES DE BIBLIOTHEQUE // System. Les diverses classes de flux poss`dent parfois des constructeurs qui semblent permettre de court-circuiter le passage par un e InputStreamReader ou un OutputStreamWriter .

208

ANNEXE B. MORCEAUX DE JAVA

La documentation en ligne est organis´e de fa¸on syst´matique, La page d’accueil est une e c e liste de liens vers les pages des packages, la page d’un package est une liste de liens vers les pages des classes, qui contiennent (au d´but) une liste de liens vers les champs, m´thodes e e etc. Les classes que nous utilisons appartiennent ` trois packages seulement. a Les descriptions sont parfois un peu cryptiques (car elles sont compl`tes), mais on arrive e vite ` en extraire l’essentiel. a Il est vain d’objecter que la documentation est en anglais. C’est comme ca. ¸

6.1

Package java.lang, biblioth`que ( standard ) e ( )

Ce package regroupe les fonctionnalit´s les plus basiques de la la biblioth`que. Il est import´ e e e par d´faut (pas besoin de import java.lang.*). e 6.1.1 Classes des scalaires

` A chaque type scalaire ( int , char etc.), correspond une classe (Integer, Character etc.) qui permet surtout de transformer les scalaires en objets. Une transformation que le compilateur sait automatiser (voir II.2.3). Prenons l’exemple de la classe Integer le pendant objet du type scalaire int . Deux m´thodes e permettent le passage d’un scalaire ` un objet et r´ciproquement. a e La m´thode valueOf transforme le scalaire en objet. e public static Integer valueOf(int i) La m´thode r´ciproque intValue extrait le int cach´ dans un objet Integer. e e e public int intValue() La classe Integer a bien d’autres m´thodes, comme par exemple e La m´thode parseInt permet de (( lire )) un entier. e public static int parseInt(String s) throws NumberFormatException Si la chaˆ s contient la repr´sentation d´cimale d’un entier, alors parseInt renvoie ıne e e cet entier (comme un int ). Si la chaˆ s ne repr´sente pas un int , alors l’exception ıne e NumberFormatException est lanc´e. e 6.1.2 Un peu de maths

Les fonctions math´matiques usuelles sont d´finies dans la classe Math. Il n’y a pas d’objets e e Math. Cette classe ne sert qu’` la structuration. Elle offre des m´thodes (statiques). a e La valeur absolue abs, disponible en quatre versions. public public public public static static static static int abs(int a) long abs(long a) float abs(float a) double abs(double a)

Les fonctions maximum et minimum, max et min, ´galement disponibles en quatre versions. e public static int max(int a, int b) public static int min(int a, int b) . . . Les divers arrondis des double, par d´faut floor, par exc`s ceil et au plus proche round. e e

` 6. QUELQUES CLASSES DE BIBLIOTHEQUE public static double floor(double a) public static double ceil(double a) public static long round(double a)

209

On note que floor et ceil renvoient un double. Tandis que round renvoie un long. Les m´thodes existent aussi pour les float et round( float a) renvoie un int . Cela peut e sembler logique si on consid`re que les valeurs double et long d’une part, et float et e int d’autre part occupent le mˆme nombres de bits (64 et 32). Mais en fait ca ne r`gle e ¸ e pas vraiment le probl`me des flottants trop grands pour ˆtre arrondis vers un entier (voir e e la documentation). Et bien d’autres m´thodes, comme la racine carr´e sqrt, l’exponentielle exp, le logarithme e e naturel log, etc. 6.1.3 Les chaˆ ınes

Les chaˆ ınes sont des objets de la classe String. Une chaˆ est tout simplement une s´quence ıne e finie de caract`res. En Java les chaˆ e ınes sont non-mutables : on ne peut pas changer les caract`res e d’une chaˆ ıne. Les chaˆ ınes sont des objets normaux, ` un peu de syntaxe sp´cialis´e pr`s. Les a e e e constantes chaˆ ınes s’´crivent entre doubles quotes, par exemple "coucou". Si on veut mettre e un double quote (( " )) dans une chaˆ ıne, il fait le citer en le pr´c´dant d’une barre oblique e e inverse (backslash). System.err.print("Je suis un double quote : \"") affiche Je suis un double quote : " sur la console. En outre, la concat´nation de deux chaˆ e ınes s1 et s2 , s’´crit e avec l’op´rateur + comme s1 +s2 . e Les autres op´rations classiques sur les chaˆ e ınes sont des m´thodes normales. e La taille ou longueur d’une chaˆ s, c’est-`-dire le nombre de caract`res qu’elle contient ıne a e s’obtient par s.length() public int length() Le caract`re d’indice index s’extrait par la m´thode charAt. e e public char charAt(int index) Comme pour les tableaux, les indices commencent ` z´ro. a e On extrait une sous-chaˆ par la m´thode substring. ıne e public String substring(int beginIndex, int endIndex) beginIndex est l’indice du premier caract`re de la sous-chaˆ et endIndex est l’indice du e ıne, caract`re qui suit imm´diatement la sous-chaˆ La longueur de la sous-chaˆ extraite est e e ıne. ıne donc endIndex-beginIndex. Si le couple d’indices ne d´signe pas une sous-chaˆ l’exe ıne ception IndexOutOfBoundsException est lanc´e. Les r`gles de d´finition du couple e e e d’indices d´signant une sous-chaˆ valide (voir la documentation) conduisent ` la partie ıne a cularit´ bien sympathique que s.substring(s.length(), s.length()) renvoie la chaˆ e ıne vide "". La m´thode equals est l’´galit´ structurelle des chaˆ e e e ınes, celle qui regarde le contenu des chaˆ ınes. public boolean equals(Object anObject) Il faut remarquer que l’argument de la m´thode est un Object. Il faut juste le savoir, c’est e tout. La classe String ´tant une sous-classe de Object (voir 2.3), la m´thode s’applique e e en particulier au seul cas utile d’un argument de classe String. La m´thode compareTo permet de comparer les chaˆ e ınes. public int compareTo(String anotherString)

210

ANNEXE B. MORCEAUX DE JAVA Compare la chaˆ avec une autre selon l’ordre lexicographique (l’ordre du dictionnaire). ıne L’appel (( s1 .compareTo(s2 ) )) renvoie un entier r avec : – r < 0, si s1 est strictement inf´rieure ` s2 . e a – r = 0, si les deux chaˆ ınes sont les mˆmes. e – r > 0, si s1 est strictement plus grande t s2 . L’ordre sur les chaˆ ınes est induit par l’ordre des codes des caract`res, ce qui fait que l’on e obtient l’ordre du dictionnaire stricto-sensu seulement pour les mots sans accents. Il y a beaucoup d’autres m´thodes, par exemple toLowerCase et toUpperCase pour mie nusculer et majusculer. public String toLowerCase() public String toUpperCase() Cette fois-ci les caract`res accentu´s sont respect´s. e e e

6.1.4

Les fabricants de chaˆ ınes

On ne peut pas changer une chaˆ par exemple changer un caract`re individuellement. Or, ıne, e on veut souvent construire une chaˆ petit ` petit, on peut le faire en utilisant la concat´nation ıne a e mais il y a alors un prix ` payer, car une nouvelle chaˆ est cr´´e ` chaque concat´nation. En a ıne ee a e fait, lorsque l’on construit une chaˆ de gauche vers la droite (pour affichage d’un tableau par ıne ex.) on a besoin d’un autre type de structure de donn´es : le StringBuilder (ou StringBuffer e essentiellement ´quivalent). Un objet StringBuilder contient un tampon de caract`re interne, e e qui peut changer de taille et dont les ´l´ments peuvent ˆtre modifi´s. Les membres les plus utiles ee e e de cette classe sont : Le constructeur sans argument. public StringBuilder () Cr´e un nouveau fabricant dont le tampon a la capacit´ initiale par d´faut (16 caract`res). e e e e Les m´thodes append surcharg´es. Il y en a treize, six pour les scalaires, et les autres pour e e divers objets. public StringBuilder append(int i) public StringBuilder append(String str) public StringBuilder append(Object obj) Toutes ces m´thodes ajoutent la repr´sentation sous forme de chaˆ de leur argument ` e e ıne a la fin du tampon interne, dont la capacit´ est augment´ silencieusement si n´cessaire. e e e Dans le cas de l’argument objet quelconque (le dernier), c’est la chaˆ obtenue par ıne e a obj.toString() qui est ajout´e, ou "null" si obj est null . Les autres m´thodes sont l`, e soit pour des raisons d’efficacit´ (cas de String), soit pour les tableaux dont le toString() e est inutilisable, soit parce que l’argument est de type scalaire (cas de int ). Toutes les m´thodes append renvoient l’objet StringBuilder dont on appelle la m´thode, ce qui est e e absurde, cet objet restant le mˆme. Je crois qu’il en est ainsi afin d’autoriser par exemple e l’´criture sb.append(i).append(’:’) ; au lieu de sb.append(i) ; sb.append(’:’) ; e (o` i est une variable par exemple de type int ). u Et bien ´videmment, une m´thode toString red´finie. e e e public String toString() Copie le contenu du tampon interne dans une nouvelle chaˆ Contrairement aux tampons ıne. de fichier, le tampon n’est pas vid´. Pour vider le tampon, on peut avoir recours ` la e a m´thode setLength avec l’argument z´ro. e e On trouvera un exemple d’utilisation classique d’un objet StringBuilder en I.2.1 : une cr´ation, e des tas de append(...), et un toString() final.

` 6. QUELQUES CLASSES DE BIBLIOTHEQUE 6.1.5 La classe System

211

Cette classe System est un peu fourre-tout. Elle posss`de trois champs et quelques m´thodes e e assez utiles. Les trois flux standards. public static final InputStream in public static final PrintStream out public static final PrintStream err Ces trois flux sont des flux d’octets (voir la section sur les entr´es-sorties 5). Notez que e les champs sont d´clar´s final , on ne peut donc pas les changer (bizarrement il existe e e quand mˆme des m´thode pour changer les trois flux standard). Les deux flux de sortie e e offrent des sorties format´es par une m´thode print bien pratique, car elle est surcharg´e e e e et fonctionne pour toutes les valeurs. La sortie out est la sortie normale du programme, tandis que la sortie err est la sortie d’erreur. En fonctionnement normal ces deux flux semblent confondus car aboutissant tous deux dans la mˆme fenˆtre. Mais on peut rediriger la sortie out, par exemple vers e e un fichier (>fichier). Les messages d’erreurs s’affichent alors au lieu de se m´langer avec la e sortie normale du programme dans fichier. Une m´thode pour arrˆter im´diatement le programme. e e e public static void exit(int status) L’entier status est le code de retour qui vaut conventionellement z´ro dans le cas d’une e ex´cution normale, et autre chose pour une ex´cution qui s’est mal pass´e, En Unix, le e e e code est accessible juste apr`s l’ex´cution comme $status ou $?, selon les shells. e e Deux m´thodes donnent acc`s ` un temps absolu. La seconce permet de mesurer le temps e e a d’ex´cution par une soustractions. e public static long currentTimeMillis() public static long nanoTime() La m´thode currentTimeMillis renvoie les milisecondes ´coul´es depuis le 1er janvier e e e 1970. Il s’agit donc du temps ordinaire. La m´thode nanoTime() renvoie les nanosecondes e (10−9 ) depuis une date arbitraire. Il s’agit du temps pass´ dans le code du programme, ce e qui est diff´rent du temps ordinaire — surtout dans un syst`me d’exploitation multi-tˆches e e a comme Unix.

6.2

Package java.io, entr´es-sorties e

Ce package regroupe s’attaque principalement ` la communication avec le syst`me de fichiers a e de la machine. Nous ne passons en revue ici que les flux de caract`res, qui correspondent aux e fichiers texte (voir 5). 6.2.1 Classe des lecteurs

Les objets de la classe Reader sont des flux de caract`res ouverts en lecture. Un tel flux e est simplement une s´quence de caract`res que l’on lit les un apr`s les autres, et qui finit par se e e e terminer. La m´thode la plus significative est donc celle qui permet de lire le caract`re suivant du e e flux. public int read() throws IOException Cette m´thode renvoie un int qui est le caract`re en attente dans le flux d’entr´e, ou −1, si e e e le flux est termin´. On notera que read renvoie un int et non pas un char pr´cis´ment pour e e e

6. que les Reader ne poss`dent pas. Si on ne ferme pas les e e e fluxs dont on ne sert plus. on risque de ne pas pouvoir en ouvrir d’autres. afin de g´rer le tampon interne. e e public int read() throws IOException Cette m´thode n’est pas h´rit´e mais red´finie. Une fois la source construite (un objet de la classe Random). tampons internes). e e e e e La lecture d’une ligne se fait par une nouvelle m´thode.4). La m´thode fonctionne encore pour la derni`re ligne d’un e e e flux qui ne serait pas termin´e par une fin de ligne explicite. ’\r’ ou ’\r’ suivi de ’\n’) et le ou les caract`res (( fin de ligne )) ne sont pas renvoy´s. et un Reader sur l’entr´e standard par e Reader in = new InputStreamReader (System.3 6. InputStreamReader (lecteur sur un flux d’octets) et FileReader (lecteur sur un fichier). il faut fermer le flux.in) . diverses m´thodes e e e .2 Lecteur avec tampon Un objet BufferedReader est d’abord un Reader qui groupe les appels au flux sous-jacent et stocke les caract`res lus dans un tampon. MORCEAUX DE JAVA pouvoir identifier la fin du flux facilement. Quand la fin d’un flux est atteinte. e Le constructeur le plus simple prend un Reader en argument. public BufferedReader(Reader in) Construit un flux d’entr´e bufferis´ ` partir d’un lecteur quelconque.1 Package java. En outre et comme annonc´ par sa d´claration e e elle peut lever l’exception IOException en cas d’erreur (possible d`s que l’on s’int´resse e e au fichiers). e La classe Reader sert seulement ` d´crire une fonctionnalit´ et il n’existe pas ` proprement a e e a parler d’objets de cette classe. Mais e e un BufferedReader offre quelques fonctionnalit´s en plus de celles des Reader. e ea La classe BufferedReader est une sous-classe de la classe Reader de sorte qu’un objet BufferedReader poss`de une m´thode read. qui peuvent se faire passer pour des Reader. e e public String readLine() throws IOException Renvoie une ligne de texte ou null en fin de flux. afin d’am´liorer les performances (voir 5.2. En revanche.util : trucs utiles Les sources pseudo-al´atoires e Une source pseudo-al´atoire est une suite de nombres qui ont l’air d’ˆtre tir´s au hasard. La m´thode fonctionne pour les trois e e conventions de fin de ligne (’\n’. il existe diverses sous-classes (voir 2. Il s’agit par entre autres e des classes StringReader (lecteur sur une chaˆ ıne). Ce e e e qui veut dire que les nombres de la suite ob´issent ` des crit`res statistiques cens´s exprimer e a e e le hasard (et en particulier l’uniformit´.3. en raison de l’´puisement de ces ressources. mais aussi un aspect chaotique pas ´vident ` sp´cifier e e a e pr´cis´ment).212 ANNEXE B. On peut donc par exemple obtenir un Reader ouvert sur un fichier /usr/share/dict/french par Reader in = new FileReader ("/usr/share/dict/french") . public void close() throws IOException Cette op´ration lib`re les ressources mobilis´es lors de la cr´ation d’un flux d’entr´e e e e e e (num´ros divers li´es au syst`me de fichiers. e 6.3) de Reader qui permettent de construire concr`tement des objets.

’0’ . Du style e e e rand. e deux sources qui ont la mˆme semence se comportant ` l’identique. on veut afficher ’+’ si b est vrai et ’-’ autrement. 7.isDigit(char c) qui tient compte de tous les chiffres e possibles (en arabe c’est-`-dire indiens. e – Un entier entre z´ro (inclus) et n (exclu). . et ef si ec vaut false . Pour r´cup´rer la valeur d’un chiffre : a e e int i = c . La semence choisie e change ` chaque appel du constructeur et on suppose qu’elle est choisie de fa¸on la plus a c arbitraire et non-reproductible possible (par exemple ` partir de l’heure qu’il est).println(b ? ’+’ : ’-’) .0 (exclu). Distinguons e e particuli`rement. tandis que le types de et et ef sont identiques.nextDouble() < 0.` 7. public Random() public Random(long seed) La version avec argument permet de donner une semence de la source pseudo-al´atoire. La version sans argue a ment laisse la biblioth`que choisir une semence comme elle l’entend. e public int nextInt(int n) Cette m´thode est pratique pour tirer un entier au pseudo-hasard sur un intervalle e [a. de sorte que les deux trucs suivants e ` s’appliquent.2 Op´rateurs e Java poss`de un op´rateur ternaire (` trois arguments) l’expression conditionnelle ec ? et : e e a ef .out. b[. System. e 7. De sorte que l’on peut afficher un bool´en ainsi : e // b est un boolean. public float nextFloat() public double nextDouble() Ces m´thodes sont pratiques pour d´cider selon une probabilit´ quelconque.). par a + rand.1 Sur les caract`res e Les codes des caract`res de ’0’ a ’9’ se suivent.0 (inclus) et 1. . Ici encore il y a des m´thodes statiques qui donnent les valeurs de chiffres dans la classe e Character. L’expression conditionnelle vaut et si ec vaut true.1) ou produire des exemples destiner ` tester des programmes. a Les constructeurs. etc. . On a besoin de tels nombres (( tir´s au hasard )) par exemple pour r´aliser des simulations e e (voir II. L’expression ec est de type boolean. Pour savoir si un caract`re c est un chiffre (arabe) : e ’0’ <= c && c <= ’9’ Il existe aussi une m´thode Character.75 vaut true avec une probabilit´ de 75 %. – Un flottant entre 0.) que l’on peut consid´rer comme tir´s au e e hasard. e 7 Pi`ges et astuces e Cette section regroupe des astuces de programmation ou corrige des erreurs fr´quemment e rencontr´es. a Les nombres pseudo-al´atoires sont produits par diverses m´thodes (( next )). etc. double.nextInt(b-a).1. PIEGES ET ASTUCES 213 permettent d’obtenir divers scalaires ( int .

et la disjonction (resp. ils sont de la vari´t´ dite s´quentielle (gauche-droite) ou parfois paresseuse : si ´valuer ee e e la premi`re condition suffit pour d´terminer le r´sultat. . . . e e e On remarque que les connecteurs && et || peuvent s’exprimer ` l’aide d’un seul op´rateur : a e l’op´rateur ternaire e1 ? e2 : e3 . MORCEAUX DE JAVA 7. conjonction) vaut true (resp. xs != null && xs. on peut ´crire ee e . false ) . e1 && e2 ) e1 vaut true (resp. alors la seconde condition n’est pas e e e ´valu´e. cas si xs vaut null alors l’acc`s xs. La conjonction e1 && e2 s’exprime comme e1 ? e2 : false e et la disjonction e1 || e2 comme e1 ? true : e2 . si dans e1 || e2 (resp. Plus pr´cis´ment.3 Connecteurs ( paresseux ) ( ) Les op´rateurs || et && sont respectivement la disjonction (ou logique) et la conjonction (et e logique). false).214 ANNEXE B. C’est un idiome e e de profiter de ce comportement pour ´crire des conditions concises.next n’est pas tent´. alors e e e e e2 n’est pas ´valu´e. Par exemple. Cette expression n’´choue jamais. pour savoir si e la liste xs contient au moins deux ´l´ments. .next != null .

F. e e [4] D. ` 1994. 2001. Algorithms. [7] R. Engineering a Sort Function. The Art of Computer Programming. vol. Fundamental Algorithms. 215 . University of Auckland. (2nd edition). Bentley et M. 1249–1265. vol. E. New Zealand. Knuth. 3. Cormen. Introduction a l’algorithmique. vol. Friedl. Seminumerical Algorithms. 1969. Tech. O’Reilly. McIlroy.. Knuth. Addison Wesley. C. E. Addison-Wesley. 1996. Knuth. 1973. E. [6] D. H. 1968.11 (1993). Addison Wesley. D. [3] J. L. The Art of Computer Programming. Sorting and Searching. Leiserson et R.Practice and Experience 23. 1988. [2] T. Addison Wesley. Maˆ ıtrise des expressions r´guli`res. Rap. Uzgalis. [8] R. [5] D. Software . E. Dunod. Sedgewick. Hashing Concepts and the Java Programming Language. The Art of Computer Programming. E. L. 1. 2.Bibliographie [1] J. Rivest.

96 e couvrant. 76 e e code. 91 e de d´rivation. 135 contenu. 212 catch (mot-cl´). 162 fini d´terministe. 126 ´galit´. 173. 82 de syntaxe abstraite. 101 e de syntaxe abstraite. 82 dictionnaire. 181 e e 216 . 82 e arit´. 82 circuit. 155 ǫ transition. 103 de recherche. voir append et nappend des mots. 11. 138 e de Fibonacci. 59 en tableau. 53 u d´bordement de la pile. 20 e r´cursif. 177 chemin simple. 89 pr´fixe. 163 e table de transition. 30 e descendant. 87 de d´cision. 82 e append (m´thode) e it´ratif. 126 droite. 156 balise. 65 automate. voir ´galit´ e e e || (op´rateur). 88. 190 e buffer. 155 e fini non-d´terministe. 209 des listes. 180 algorithme. voir ou logique e acc`s e direct. 168 concat´nation e des chaˆ ınes.Index != (op´rateur). 89 ancˆtre. 82 ordonn´. 196 e champ. 69 Complexit´ e dans le pire cas. 103 e complet. 81 arˆte. 116 e arcs. 144 AVL. 124 e e libre. 124 binaire. 103 collision. 81 class g´n´rique. voir et logique e == (op´rateur). voir ´galit´ e e e && (op´rateur). 190 e double rotation. 9. 87 do (mot-cl´). 168 en moyenne. 190 e copy (m´thode) e it´ratif. 167 allocation (m´moire). 117 tass´. 55. 9. 15 e coˆt amorti. 89. 179. 82 e association. 118 distance. 92 enracin´. 82 e ´quilibr´. 131 biblioth`que. 58 adresse. 87 complet. 189. voir tampon BufferedReader (classe). 198 e add (m´thode des files) e en liste. 20 e arbre. 7 e alphabet. 20. 83. 9 s´quentiel. 158 e repr´sentation Java. 113 continue (mot-cl´). 207 e break (mot-cl´). 125 de s´lection.

24 e instruction. 82 non orient´. 190 e forˆt. 82 droit. 82 fichier. 87 moyenne. 168 langage r´gulier. voir merge e garbage collection. 186 interface. 41. 71 feuille. 198. 45 liste boucl´e. 186. 68 interface (mot-cl´). 189 e empreinte m´moire. 209 d’une liste. 81 e orient´. 82 entr´e e standard. 9 boucle infinie. 25 e insertionSort (m´thode). 81 e hachage. 197 e lancer. 182 et logique. 198 fermer. 182 else (mot-cl´). 176. 189 e implements (mot-cl´). 61 ın´ simplement chaˆ ee. 212 insertion dans un arbre. 81 connexe. 200 ouvrir. 176. 115 de priorit´. 89 d’un tableau. 197 e extr´mit´. 66 e Landau. 168 Grand Θ. 69 HashMap (classe). 168 Grand O. 179 e idiome. 187 initialisation diff´r´e. 14. 181 structurelle. 76 hauteur d’un arbre. 7 ın´ longueur d’un chemin. 27.INDEX physique. 214 exception. 82 d’un arbre binaire. 196 d´clarer. 194 attraper. 101 e des listes tri´es. 214 e e parcours de liste. 89 lex`me. 186 expression conditionnelle. 24 e e op´rateur logique s´quentiel. 53. 205 e LIFO. 45 file. 25. 213. 33. 137 e lettre. 190 boucle vide. 206 encoding. 206 FIFO. 193 d’une chaˆ ıne. 34. 81 e e facteur de charge. 83 e fusion de listes ordonn´es. 7. 82 fils. 66. 191 e infixe. 113 gauche. 113 final (mot-cl´). 41. 145 encodage. 21 op´rateur logique s´quentiel. voir throws. 198 binaire. 9 if (mot-cl´). 96 e FileReader (classe). 36 e circulaire. 36 doublement chaˆ ee. 67 e import (mot-cl´). 13 gestion automatique de la m´moire. 192 e InputStreamReader (classe). voir encodage enfant. 45. 198 bufferis´. 198 erreur de type. 200 texte. 197 expression. 214 extends (mot-cl´). 212 fille. 98 dans une liste tri´e. 31 ee initialisation par d´faut. voir throw. 202 e for (mot-cl´). 13 e Grand Ω. 168 graphe. 185 e flux. 124 h´ritage. 68. 198 e d´finir. 10 217 . voir try. 81 d’un mot. 64 e encapsulage.

87 INDEX . 89 e private (mot-cl´). 22 e rotation. 198 standard. 82 nombres de Catalan. voir surcharge overriding. 90. 88 notation infixe. 90 e suffixe. 89. 178 e objet. 115 pointeur. 51 port´e lexicale. 212 Reader (classe). 188 e postfixe. 89 nappend (m´thode) e it´ratif. 82 partition. 75 sortie d’erreur standard. 87. 175 e push (m´thode) e en liste. 12 e remove (m´thode des files) e en liste. voir red´finition de m´thode e e package. 83 p`re. 92. 174 e mem (m´thode) e it´ratif. 58 return (mot-cl´). 20 e nœud. 11 e membre (d’une classe). 135 e pr´fixiel. 55 en tableau. 82 droit. 90 infixe. 180 signature. 59 en tableau.218 main (m´thode). 175 e public (mot-cl´). 90 postfixe. 82 e pile. 187 e pr´fixe (d’un mot). 21 e r´cursif. 31 e r´cursif. 115 en profondeur. 11. 23 red´finition de m´thode. 90. 11 e r´cursif. 90 pr´fixe. 175 parcours. 177. 90 parent. 175 e profondeur d’un nœud. 180 de pile. 180 ordre fractionnaire. 16 e r´cursif. 198 sous-arbre. 188 e reverse (m´thode) e it´ratif. 55 en tableau. 21 e r´cursif. 81 interne. 126 scalaire. 214 overloading. 211 r´cursion e terminale. 45. 173. 175. 81 ou logique. 61 racine. 82. 24. 18 e r´cursif. 90 origine. 90 pr´fixe. 51 pop (m´thode) e en liste. 89 en largeur. 186. 28 e mergeSort (m´thode). 47 nremove (m´thode) e it´ratif. 51 queue. 82 e merge (m´thode) e it´ratif. 28 e mot. 179 e e r´f´rence. 115 e suffixe. 91 infixe. 198 sondage lin´aire. 180 ee remove (m´thode) e it´ratif. 49. 113 Random (classe). 187 pr´fixe. 87 gauche. voir file a ` deux bouts. 18 e null (mot-cl´). 115 d’arbre. 87 protected (mot-cl´). 73 e par double hachage. 173 m`re. 94 postfixe.

24 try (mot-cl´). 193 d’une chaˆ ıne. 183. 196 e type. 189 e tableau. 189 e 219 . 200 sous-typage. 98 surcharge. 199. 185 e String (classe). 9 e structure de bloc. 38. 83 uniq (m´thode). voir d´bordement de la pile e static (mot-cl´). 53. 7 mutable. 198 e token. 191 e throw (mot-cl´). 180 variable d’instance. 210 StringBuilder (classe). 197 e throws (mot-cl´). 209 StringBuffer (classe). 53. 177. 12. voir lex`me e toString.INDEX sous-classe. 179. 209 tampon. 179. 7 fonctionnelle. voir mutable e inductive. voir pile stack overflow. 210 tas. 201 sp´cification de visibilit´. 188 suffixe (d’un mot). 175 e e Stack (classe). 97 this (mot-cl´). 210 switch (mot-cl´). 100 tri (des listes) fusion. 135 suppression d’une cl´. 18. 178. 182 abstrait. 20 non-mutable. 180 variable. 11. 54. 91 par tas. 28 par insertion. 115 d’un tableau. voir non-mutable imp´rative. 177 while (mot-cl´). 96. voir non-mutable s´quentielle. 173. 179 e tri par comparaison. 197. 10 red´finition. 179. 54. 24 e valeur. 120 e dans un arbre. 192 taille d’un arbre. 186. 202. 20 persistante. 63 Union-Find. 55 stack. 210 structure dynamique.

Sign up to vote on this title
UsefulNot useful