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

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

coˆ t d’un algorithme u Une d´finition tr`s informelle des algorithmes e e Des algorithmes “efficaces” ? . . . B Morceaux de Java 1 Un langage plutˆt classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mots. . . . . . . . . . . . . . . . . . . . coˆt r´el . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Automates finis et expressions r´guli`res . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Entr´es-sorties . . . . . . . . . . . . . . . . . . u e u e . . . . . . . . . . e e Un peu de Java . . . . . . . . . . . . . . langages et probl`mes e Automates finis d´terministes . . . . . . . . . . . . . . . . 4 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Rappel : alphabets. . . . . . . .6 VII Les 1 2 3 4 5 6 A Le 1 2 3 4 automates Pourquoi ´tudier les automates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o 2 Obscur objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Coˆt estim´ vs. . . . . . . . . . . . . . . . . . . . ` 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 6 Quelques classes de biblioth`que e 7 Pi`ges et astuces . . . . . e Automates finis non-d´terministes . . . . . . . . . . . . . . . . . . Quelques exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e R´f´rences ee Index . . . . . . . . . . . 3 Constructions de base . . . . . . . . . . . . .

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

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

e e e Les donn´es sont r´parties en cylindres qui correspondent ` des anneaux sur le disque. ee e Et c’est tout.next . ne nous empˆche d’employer un autre type de boucle. l’acc`s ` e e a e a un cylindre donn´ se fait par un d´placement radial de la tˆte de lecture. 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. on peut voir la m´moire e e e comme un grand tableau (d’octets) dont les indices sont appel´s adresses. En effet. e Solution. ` part le souci de la coutume.next ) { // Traiter l’´l´ment qs. 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. ici une boucle for . (2) on peut ajouter un nouvel ´l´ment e en tˆte d’une liste ℓ (new PointList (e. } La liste simplement chaˆ ee est une structure s´quentielle. par une a e e a redirection <fichier ). il n’y a que deux op´rations. 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 . la lecture de leur code en est facilit´e. e e e ee Cette op´ration se fait id´alement selon un idiome. while (qs != null) { // Traiter l’´l´ment qs. ℓ)). par nature. e Exercice 1 Donner deux autres exemples de dispositifs mat´riels offrant acc`s s´quentiel et e e e acc`s direct. e ee e le dispositif d’adressage des donn´es est juste un peu plus compliqu´ que celui de la m´moire. parce que la m´moire est allou´e petit ` e e e e a petit directement en fonction des besoins. par a e exemple PointList qs = ps . En fait le tableau est une abstraction de la m´moire de l’ordinateur. en revanche la m´moire de l’ordinateur offre l’acc`s direct. la liste est une structure de donn´es s´quentielle. e // ps est une PointList for (PointList qs = ps . ee Une op´ration fr´quemment programm´e est le parcours de tous les ´l´ments d’une liste. STRUCTURE DYNAMIQUE. 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.next). ce qui rend la e e programmation r´cursive assez naturelle (inductif et r´cursif sont pour nous informaticiens des e e synonymes). e e a e a ee . Enfin.val e e } Les programmeurs Java emploient normalement une telle boucle pour signaler que leur code r´alise un simple parcours de liste. La liste peut ˆtre d´finie inductivement. toutes les autres op´rations doivent ˆtre programm´es ` partir des op´rations e e e a e ´l´mentaires. Pour construite les listes. qs != null . jusqu’` trouver l’´l´ment cherch´. STRUCTURE SEQUENTIELLE 9 (b) ou bien extraire la liste qui suit ℓ (ℓ. c’est-`-dire selon une tournure fr´quemment e e a e employ´e qui signale l’intention du programmeur. 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. Cette technique d’acc`s s´quentiel s’oppose ` a ee e e e a l’acc`s direct.´ 1. Un disque dur peut ˆtre consid´r´ comme offrant l’acc`s direct. Un exemple simple de structure de donn´es qui offre l’acc`s direct est le tableau. e (1) la liste vide existe ( null ).val e e qs = qs. En r´sum´ la liste est une structure dynamique. Notons que e e e rien. qs = qs.

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

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

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

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

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

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

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

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

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

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

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

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

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

et d’autre part. nous disposons d’une estimation r´aliste du coˆt global. d’une part. Trier des listes de taille croissantes et d´j` tri´es en ordre croissant donne un coˆt ea e u 2 . 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).26 CHAPITRE I. Il est rappel´ (cf. LISTES Notons que pour borner I(n + 1) nous avons suppos´ qu’un appel r´cursif ´tait effectu´. et que insertionSort est donc en O(n2 ). ` 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 . Mais si les listes sont tri´es en en n a e ordre d´croissant. et en tenant compte des n appels ` insert. 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). 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 ). (( 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 +∞. Notons que la notation f (n) e est en O(g(n)) (il existe une constante k telle que. 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. 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). 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. Pour un tri on a tendance e e e a ` compter les comparaisons entre ´l´ments. 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. On peut aussi compter pr´cis´ment une op´ration en particulier. mais comptons d’abord les comparaisons effectu´es par un appel e e a ` insert. 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). Il faut noter que l’on peut e e u g´n´ralement limiter les calculs aux termes dominants en n. pour n assez grand. En premi`re approximation. Il est e e e e donc imm´diat que I(n) est major´e par k1 · n + k0 . le cours pr´c´dent) que f est dans Θ(g) quand il existe deux constantes e e e k1 et k2 telles que. 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. 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. car les insertions se font toujours ` la fin de la liste r. Estimer le coˆt du tri de listes d’entiers tir´s au hasard. pour n assez grand. alors les insertion se font toujours au d´but de la liste r et le coˆt est en n. En effet. Plus exactement encore. Ici on dira. 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. a u u e Solution. e e u . Nous y reviendrons. insertionSort appelle e e n fois insert qui est en O(k) pour k ≤ n. on a f (n) ≤ k · g(n)) est particuli`rement justifi´e pour les coˆts dans le cas le pire. on a k1 · g(n) ≤ f (n) ≤ k2 · g(n) Par ailleurs.

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

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

le compte des comparaisons estime le coˆt global de fa¸on r´aliste. 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). a Solution. 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. il existe un unique p. on a U (2p ) ≤ U (n) < U (2p+1 ) Posons D(n) = U (n + 1) − U (n). 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. 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.3. ee Par ailleurs. avec 2p ≤ n < 2p+1 et on a donc 1/2 · p · 2p ≤ U (n) Par ailleurs. 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. 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. Toute l’astuce est de montrer que pour p assez grand et n tel que 2p ≤ n < 2p+1 . En effet U (2p ) ≤ U (n) r´sulte de D(n) ≥ 0. Par ailleurs on sait d´j` ea que U (2p ) vaut 1/2 · p · 2p . T (p + 1) = k + T (p) de solution k · p. Soit maintenant n > 0. pour n > 1 on a n/2 < 2p . 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⌉ . Ce qui suffit pour prouver l’encadrement pour tout p. tandis U (n) < U (2p+1 ) r´sulte de ce que 2p+1 − 1 est e e impair. la fonction x · log2 (x) est croissante. il vient donc 1/2 · log2 (n/2) · n/2 ≤ U (n) . Exercice 8 (Franchement optionnel) G´n´raliser l’ordre de grandeur du compte des come e paraisons ` n quelconque. ⌊n/2⌋) + S(⌈n/2⌉) + S(⌊n/2⌋). Or.

En effet. 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.java:78) . .StackOverflowError at List.411111 N=30000 T=0. Nous obtenons : N=10000 T=0. ce qui est l’essentiel. O` de l’ordre de 1000 lignes at List. Or ici. Si on souhaite estimer les constantes cach´es dans le Θ et pas seulement l’ordre de grandeur asymptotique. . Et nous touchons l` une limite de la a programmation r´cursive.30 De mˆme. 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.lang. Elle montre surtout e que le tri fusion est bien plus efficace que le tri par insertion. . e ee e e e Pour le moment. alors que nous avons programm´ une m´thode qui semble e e e .java:80) at List. on borne sup´rieurement S(n) par la suite e e V (0) = 0 V (1) = 0 CHAPITRE I.merge. 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.3 Trier de grandes listes Pour confirmer le r´alisme des analyses de complexit´ des tris. sont effac´es.merge(List.609787 Exception in thread "main" java.merge(List. LISTES V (n) = n + V (⌈n/2⌉) + V (⌊n/2⌋)) Suite dont on montre qu’elle est croissante. 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). merge appelle merge qui appelle merge qui etc. 3. . Nous expliquerons plus tard pr´cis´ment ce qui se passe. Encourag´s. Les ´l´ments e ee des listes sont les N premiers entiers dans le d´sordre. nous essayons de mesurer le temps d’ex´cution du tri fusion pour des valeurs e e de N plus grandes. puis major´e par log2 (2n) · 2n. contentons nous d’admettre que le nombre d’appels de m´thode en attente est e limit´. et merge doit attendre le retour e de merge qui doit attendre le retour de merge qui etc.093999 N=20000 T=0.

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

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

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

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

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

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

´ ´ 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

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

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

En fait. pour que Q ´claire l’arˆte issue de P .y) return 1 . Or un sommet est e a e supprim´ au plus une fois. p1. ce qui conduit ` une somme des coˆts des appels ` extend2 en O(n). e a // p1 < p2 <=> compare(p1. Point pt2 = in.y) return -1 .next = p2 . q) >= 0) throw new Error ("Points non tri´s") . 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.val.x > p2. null. q) . en raison de la convention que l’axe des e e a y pointe vers le bas.y > p2. i f (q == null) return e .read() . else i f (p1. e e .x) return 1 . Poly p2 = new Poly(pt2. il e e suffit que l’ordonn´e de Q soit inf´rieure ` celle de P . . En effet. Autrement dit. Nous pr´tendons ensuite que le calcul propree ment dit de l’enveloppe est en O(n). l’algorithme n’est donc plus e incr´mental. On peut trier les n points en O(n log n). il e sera le nouveau point distingu´ lors de la prochaine extension. comme exprim´ par la m´thode e e e compl`te de calcul de l’enveloppe convexe. Point p2) { i f (p1.read() . p2. Ici.p2) < 0 static int compare(Point p1.prev = p2 . En pareil cas. } } On peut donc assurer la correction de l’algorithme complet en triant les points de l’entr´e e selon les abscisses croissantes. for ( .x) return -1 . Evidemment e e e e ´ pour pouvoir trier les points de l’entr´e il faut la lire en entier. P est strictement inf´rieur ` Q selon l’ordre suivant.pt2) >= 0) throw new Error ("Deux premiers point incorrects") .´ ´ 5. p1. e e = extend2(e. i f (pt1 == null || pt2 == null || compare(pt1. Poly p1 = new Poly(pt1.x < p2. ) { Point q = in. else i f (p1. i f (compare(e. e a u a Le tri domine et l’algorithme complet est donc en O(n log n). else return 0 . null) .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. 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). e static Poly convexHullSorted(PointReader in) { Point pt1 = in. Poly e = p2 . avec une liste chaˆ ee seulement selon e e ın´ les champs next. else i f (p1. } On note que le point ajout´ Q fait toujours partie de la nouvelle enveloppe convexe. null) . puis les ordonn´es d´croissantes en cas d’´galit´. p1. e Estimons maintenant le coˆt du nouvel algorithme de calcul de l’enveloppe convexe de u n points.read() . 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.prev = p1 .

44 CHAPITRE I. LISTES .

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

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

` 1. Les calculs e e En hommage au philosophe et math´maticien Jan Lukasiewicz qui a introduit cette notation dans les e ann´es 20. Durant la simulation.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. L’impl´mentation d’une file sera d´crite plus tard. } } 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. passer au (( client )) suivant). Client (int t) { arrival = t . e Le code qui r´alise cette simulation na¨ en donn´ par la figure 2. .nextDouble() < prob . Quand un client est disponible et que le guichet est libre (si t est sup´rieur ` tFree). e 1 . e – Et ajuster tFree en cons´quence.1] e e e static private boolean occurs(double prob) { return rand. 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). e waitMax = 1800 . On note aussi la conversion de type explie cite ((double)nbFedUp)/nbClients. une variable tFree indique la date o` le e u ` guichet sera libre. departure . e a – Tirer al´atoirement un temps de traitement. n´cessaire pour forcer la division flottante. . e – Si oui. Un objet Client est cr´´ e e ee lors de l’arriv´e du client au temps t. La figure 3 donne les r´sultats de la simulation pour e diverses valeurs de la probabilit´ prob. Le code suit les grandes lignes de la description pr´c´dente. A QUOI CA SERT ? ¸ } // Survenue d’un ´v´nement de probabilit´ prob ∈ [0. il faut : Faire un tirage al´atoire pour savoir si un nouveau client arrive. 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. A chaque seconde t. La simulation est discr`te. Il s’agit de moyennes sur dix essais. 1. 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. 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. ajouter le nouveau client (avec sa limite de temps) dans la file d’attente. 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. e a – Extraire le client de la file. } 47 Un client en attente est mod´lis´ par un objet de la classe Client . int arrival. – V´rifier qu’il est toujours l` (sinon. departure = t+Simul.uniform(waitMin. Sans cette e conversion on aurait la division euclidienne. add et remove d´crites pour les Bag de l’introduction. waitMax) .

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

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

} else { stack.conduiraient au mˆme r´sultat 1 . Sans e e les parenth`ses 1 2 3 . for (int k = 0 . 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.equals("-") || x.50 6 -> [3. On supposera donn´e une classe Stack des piles de chaˆ Solution. stack.length . puis ` l’addition (premi`re e a e a e op´ration) . (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.et 1 2 . .pop(). on obtient : ea e % java Infix 6 3 2 . 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). 3] * -> [9] 9 CHAPITRE II. . 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. on effectue d’abord la premi`re op´ration (mule e e tiplication) puis la seconde (addition).3 qui serait e e e incorrect pour 1 2 3 . i2 = stack. 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. 6] . elle dit exactement quoi faire.-.’*’ 6 -> [6] 3 -> [6.push(x) .2 . 9. Dans le mˆme ordre d’id´e.println(x + " -> " + stack) . . i f (x.. k++) { String x = arg[k] .equals("/")) { String i1 = stack. la notation e e postfixe rend les parenth`ses inutiles (ou empˆche de les utiliser selon le point de vue).out.3 . Dans le cas de la notation infixe. Par contraste. 6 -> [(6/((3-2)+1)). Sur l’expression d´j` donn´e.pop() .-> [3. k < arg.equals("*") || x.err.equals("+") || x. gramme Calc. tandis que pour calculer 1 * 2 + 3. il faut r´gler le e e probl`me des priorit´s des op´rateurs. 9.1 + / 9 6 .-> [(6/((3-2)+1)).push("(" + i2 + x + i1 + ")") . 6] . } System. 3] . Il suffit tout simplement de construire la repr´sentation infixe au lieu de calculer.println(stack. } System. } } L’emploi syst´matiques des parenth`ses permet de produire des expressions infixes justes.

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

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

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

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

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

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

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

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

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

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

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

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

next = null ). . TYPE ABSTRAIT. le tableau (ainsi e que le pointeur de pile sp) et la liste sont des champs priv´s. du point de vue du langage de programmation. et parce que la classe des piles est quand mˆme simple ` ´crire. ` l’aide des interfaces. 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. Nous verrons au chapitre suivant comment proc´der. 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. Un programme qui utilise nos piles e doit donc le faire exclusivement par l’interm´diaire des m´thodes push et pop. 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. e a 4 Type abstrait. . Il est naturel de se demander e e c e quelle impl´mentation choisir. CHOIX DE L’IMPLEMENTATION 63 Pour supprimer par exemple le dernier ´l´ment de la queue. Dans nos deux classes Stack. } 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. a e 3 2 0 7 11 fst lst Le code.prev). 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 . les trois impl´mentations des piles offrent exactement le mˆme service. 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. e ae . . . e parce que c’est la solution qui demande d’´crire le moins de code. . } void push(int x) { . En effet. il suffit de changer le contenu de ee lst (lst = lst. Une fois compris le m´canisme d’ajout et de e e retrait des cellules. La pile qui est d´finie exclusivement par les services qu’elle offre e est un exemple de type de donn´es abstrait.´ 4. Tout ce qui compte est d’avoir une pile e (ou une file). choix de l’impl´mentation e Nous avons pr´sent´ trois fa¸ons d’impl´menter piles et files. . e Java offre des traits qui permettent de fabriquer des types abstraits qui prot`gent leur e impl´mentation des interventions intempestives. Il faut toutefois remarquer que. du strict point e ` de vue de la fonctionnalit´. ` moins de changer le nom des classes ce qui contredirait l’id´e e a e d’un type abstrait. } int pop() throws StackEmpty { . il faut choisir la classe de biblioth`que. Mais nous tenons d’abord a faire remarquer que. 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() { . Dans disons e e a e 99 % des cas pour les files et 90 % des cas pour les piles. la question ne se pose pas. e a R´pondons maintenant s´rieusement ` la question du choix de l’impl´mentation.

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). Dans le cas g´n´ral. ou en tout cas utiliseront moins de m´moire au final. e c e e e trop coˆteux. mais la flexibilit´ de l’allocation petit-`-petit e a des cellules de la pile-liste est un avantage. par exemple en r´duisant le tableau de la moiti´ de sa taille quand e e il n’est plus qu’au quart plein. Mais il faut alors y regarder ` deux fois. la quantit´ de m´moire mobilis´e ` un u e e e e e a instant donn´. la pile a atteint sa taille maximale).64 CHAPITRE II. Il n’y a pas de r´ponse toute faite ` cette a e a derni`re question. et e ıne une pile de scalaires se montre au final plus rapide. Si cette profondeur reste raisonnable le choix de la piletableau s’impose. d’une part. le code de biblioth`que entraˆ la fabrication d’objets Integer en pagaille. 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). 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). allouer un objet est une op´ration ch`re. dans le cas e e e e o` le redimensionnement est inutile. u e Il reste alors ` choisir entre tableaux et listes. En effet. 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. dans le pass´. le choix d’une pile-tableau s’impose probablement. 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). tandis que e e la pile-tableau aura allou´ entre une (si la taille initiale du tableau est 1. 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 . notre classe des piles a commence ` devenir compliqu´e est tout ce code ajout´ entraˆ un prix en temps d’ex´cution. 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. la pile-liste aura allou´ au total 4 · P cases de m´moire. puisque u simplicit´ et efficacit´ concordent. 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. La pile-tableau n’alloue e u e donc jamais significativement plus de m´moire que la pile-liste. 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). autrement le choix est moins net. On e e peut aussi penser que les tableaux seront un plus efficaces que les listes. Une pile-file de N ´l´ments mobilise 4 · N cases de m´moire. On peut aussi envisager de r´duire la taille du tableau e interne de la pile tableau. 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. par exemple) et aussi de l’efficacit´ de la gestion de la m´moire par le syst`me d’ex´cution de Java. Pour un programme effectuant au total P push. En effet. l’efficacit´ respective de l’une ou de l’autre u e e technique d´pend de nombreux facteurs. Toutefois. e e e o Un autre coˆt int´ressant est l’empreinte m´moire. (la pile croˆ e ıt-elle beaucoup. Or. ou tout simplement impossible avec notre connaissance limit´e de Java. 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´. Par exemple. et g´n´ralement plutˆt moins. et d’autre part. a e e ıne e .

c’est-`-dire aussi pouvoir ajouter ou a supprimer un ´l´ment d’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. e e e Trouver l’information associ´ ` une cl´ donn´e.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. En g´n´ral. e ee Un enregistrement est compos´ de plusieurs champs (nom.) dont un certain nombre peuvent servir de cl´. afin d’identifier l’information concern´e parmi les ´ventuellement multiples qui e e e sont associ´es ` une mˆme cl´. On en vient naturellement ` d´finir un type abstrait de ee a e donn´es. a Enfin. par exemple. Lors de l’ajout d’une paire cl´-information. Il faut alors r´viser un peu les deux e ea e autres op´rations. 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. une nouvelle association est ajout´e ` la table. La notion se e a e rencontre dans la vie de tous les jours. on choisit l’information la plus r´cemment entr´e. 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). Il n’y a l` aucune e a difficult´ du moins en th´orie. date de naissance etc. l’id´e du num´ro de s´curit´ sociale. on arrive toujours ` se d´brouiller. 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. e Retirer une cl´ de la table (avec l’information associ´e). e e e e e sexe. e Il n’est pas trop difficile d’envisager la possibilit´ inverse. Il se peut aussi que l’information se r´duise ` la cl´. u e e e e Nous voulons un ensemble dynamique d’informations. Dans ce cas appelons enregistrement un ´l´ment d’information. en ajoutant une nouvelle association e a ` la table dans tous les cas. 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´. d’o`. il peut se faire que la cl´ fasse partie de l’information. alors la nouvelle information ea e a e remplace l’ancienne. appel´ table d’association qui offre les op´rations suivantes. num´ro de s´curit´ sociale. e e La seconde op´ration m´rite d’ˆtre d´taill´e. 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. que la cl´ s’y trouve d´j` ou pas. dans le but de produire d’int´ressantes e 65 . ce e a e e e e e e qui revient ` un comportement de pile. e e ee En cas d’homonymie absolue (tout de mˆme rare). comme c’est g´n´ralement le e e e cas dans une base de donn´es. nous e e e e e e pr´cisons : e S’il existe d´j` une information associ´e ` la cl´ dans la table. Sinon. pr´nom. ea e e Ajouter une nouvelle association entre une cl´ et une information.

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

p) . Notons que les chaˆ e ınes sont compar´es e par la m´thode equals et non pas par l’op´rateur ==. nous d´finissons donc une classe simple des cellules de listes.1.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. class L implements Assoc { private AList p . p = p. AList next .3. int val. . WordReader in = .next = next . e class AList { String key . . .1. this. STATISTIQUE DES MOTS Assoc t = . } public int get(String key) { AList r = AList. e a e a e static AList getCell(String key. null ´tant une liste valide) destin´e ` retrouver une paire cl´-information ` partir de sa cl´. count(in. . i f (r == null) { // Absent de la table return 0 . t) . 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. return null . Si cette cellule n’existe pas. L() { p = null . nous estimerons donc le programme Freq ´crit d`s que nous aurons impl´ment´ e e la table d’association n´cessaire. int val .1).next) i f (key.key)) return p . } } . } 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´.out. AList (String key. p != null .toString()) . 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.val = val .getCell(key.val .equals(p. this. System. List next) { this. c’est plus prudent (voir B.key = key . .println(t. } } Nous aurions pu d´finir d’abord une classe des paires. AList p) { for ( . les cl´s sont des chaˆ e e ınes et les informations des entiers. Dans l’exemple de la classe Freq. e e Nous dotons la classe AList d’une unique m´thode (statique. e 1. puis une classe des listes de paires. // Pr´sent dans la table e } else { return r. 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. .

p) . seulement l’interface Assoc. avec les signatures conformes. 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. puis ramener le e cas g´n´ral ` ce cas simple. 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. en particulier e dans le cas fr´quent o` la cl´ n’est pas dans la liste. 2 Table de hachage La table de hachage est une impl´mentation efficace de la table d’association. 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).getCell pour retrouver l’information associ´e ` la cl´ key pass´e en e e a e e argument. En effet si la liste d’association e est de taille N . Il suffit alors d’utiliser un tableau de taille n pour repr´senter la ea e e table d’association. 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.68 CHAPITRE III. e } else { // Pr´sent dans la table. p) . 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´). Notez que count ne connaˆ pas la classe L. ne nous leurrons pas un cas aussi simple est exceptionnel en pratique. new L()) . nous l’avons d´j` exploit´e e e ea e pour les ensembles. o` n est un entier pas e u trop grand. } } } Les objets L encapsulent une liste d’association. e . val.1 Adressage direct Cette technique tr`s efficace ne peut malheureusement s’appliquer que dans des cas tr`s e e particuliers. Mais. nous u e e allons introduire la nouvelle notion de table de hachage. 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. Appelons e univers des cl´s l’ensemble U de toutes les cl´s possibles. .val = val . int val) { AList r = AList. e e a 2. Cette d´claration entraˆ deux cons´quences importantes. Les m´thodes get et put emploient toutes les e deux la m´thode AList . une recherche par getCell peut prendre de l’ordre de N op´rations. n − 1}. ıt Notre programme Freq fonctionne. . Pour atteindre une efficacit´ bien meilleure. Le rang ` l’arriv´e d’un concurrent peut servir de cl´ dans la base de a e e donn´es. ASSOCIATIONS — TABLES DE HACHAGE public void put(String key. les piles et les files dans les chapitres pr´c´dents. // Compter les mots de in. 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. e i f (r == null) { // Absent de la table. ce qui arrive par exemple dans l’appel suivant : // in est un WordReader count(in. . Il faut que l’univers des cl´s soit de la forme {0. mais il n’est pas tr`s efficace. cr´er une nouvelle association p = new AList (key. La technique de l’encapsulage est d´sormais famili`re. modifier l’ancienne association r. e e e Un objet L peut prendre le type Assoc.getCell(key. . Il y a un d´tail ´trange : les e e e e m´thodes sp´cifi´es par une interface sont obligatoirement public.

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

et que hachage est uniforme. 2 – R´solution des collisions par chaˆ e ınage. AList r = AList. } else { return r. l’op´rateur e e e (( reste de la division euclidienne )) % renvoie un r´sultat n´gatif (mis`re). m[ avec une probabilit´ 1/m. } } } Le code de la fonction de hachage hash est en fait assez simple. val. e Estimons le coˆt de put et de get pour une table qui contient N ´l´ments d’information.getCell(key. 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) . . AList r = AList. La classe H d´clare impl´menter l’interface Assoc et le fait effectivement ce que le come e pilateur v´rifie. ASSOCIATIONS — TABLES DE HACHAGE Fig.val . } else { r. Un objet de la nouvelle classe H est donc un argument valide pour la e m´thode count de la classe Freq. 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.abs est malheureusement n´cessaire. On u ee suppose que le coˆt du calcul de la fonction hachage est en O(1).70 CHAPITRE III. int val) { int h = hash(key) . afin de produire un indice valide. i f (r == null) { return 0 . en rempla¸ant p e c par t[h]. 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). t[h]) . t[h]) .val = val . t[h]) . . } } public void put(String key. e e La valeur absolue Math. car pour n n´gatif.getCell(key. u c’est-`-dire que la valeur de hachage d’une cl´ vaut h ∈ [0 . i f (r == null) { t[h] = new AList(key. a e e .

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

int new_sz = 2*old_sz .length * alpha < nbKeys) { resize() .val. val. il est mˆme assez coˆteux.72 CHAPITRE III. effectu´ apr`s ajout d’une nouvelle associae e e e tion.key) . afin de ne pas mobiliser e e une quantit´ cons´quente de m´moire a priori.length). les ´l´ments d’informations sont stock´s directement a ee e dans le tableau. t[h] = new AList (p.length d´passe alpha. 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. 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. i++) { for (AList p = oldT[i] . le cas ´ch´ant. int val) { int h = hash(key) .val = val . } } } 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 . e e e ´ Le facteur de charge α est donc n´cessairement inf´rieur ` un. on continue la recherche en suivant une e e e . p != null . } } else { r. si cette case est e a occup´e par une information de cl´ k ′ diff´rente de k. Le redimensionnement n’est pas gratuit.next) { int h = hash(p.getCell(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´. ASSOCIATIONS — TABLES DE HACHAGE Nous avons aussi chang´ la valeur de la taille par d´faut de la table. // garder une r´f´rence sur l’ancien tableau e e t = new AList [new_sz] . e La m´thode de redimensionnement resize. Plus pr´cis´ment. i f (r == null) { t[h] = new AList(key. i f (t. e public void put(String key. nbKeys++ . 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). t[h]) . Pour cette raison. 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. En effet. // Allouer le nouveau tableau /* Ins´rer toutes les paires cl´-information de oldT e e dans le nouveau tableau t */ for (int i = 0 . private void resize() { // Ancienne taille int old_sz = t. la table de hachage est un tableau de paires cl´-information. puis. p = p. la valeur de hachage h n’est valide que relativement ` la longueur de tableau t.key.4 Adressage ouvert Dans le hachage ` adressage ouvert. AList r = AList. // Nouvelle taille AList [] oldT = t . t[h]) . i < old_sz .length . 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. } } Notez que le redimensionnement est. t[h]) . p. a 2.t. ` ajouter ` la classe H double la taille du tableau e a a interne t.

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

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

mais les formules donnent toujours un majorant. Pair [] oldT = t . 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. e e e Consid´rons par exemple la table ci-dessous. } } } Il faut. Le sondage lin´aire provoque des ph´nom`nes de regroupement (en plus des collisions). On se donne deux fonctions de hachage h : U → {0. 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´. k++) { Pair p = oldT[k] . k < old_sz . Bref. e e h(k) + 2h′ (k). Pour ce faire on peut prendre m ´gal ` une puissance e a ′ (k) toujours impair. ce qui accentuera le le e e e regroupement des cases 4–7. 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. En e e e particulier en cas de collision selon h.2. o` α est le facteur de charge et sous r´serve de hachage uniforme. un cas qui se pr´sente en pratique avec le hachage modulo e e (voir 3). C’est le meilleur moyen de garantir des ajouts compatibles avec les m´thodes e put et get. . comme dans le cas du chaˆ ınage. 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. for (int k = 0 .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 − α)). des adresses d’objets qui se suivent dans la m´moire. etc. dans des e applications plus techniques. h(k) + 3h′ (k). section 6. 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. . section 6. t = new Pair[new_sz] . ou. Knuth montre que. il n’y a aucune raison que les sondages se fassent selon le mˆme incr´ment. quand les cl´s sont par exemple les valeurs successives d’un compteur. On note aussi que oldT[k] peut valoir null et qu’il faut en tenir compte. Ces r´sultats ne sont stricto u e e sensu plus valables pour α proche de un. . . 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. private void resize() { int old_sz = t. sous des hypoth`ses de distribution uniforme et e d’ind´pendance des deux fonctions de hachage. . Ensuite le sondage est effectu´ selon la s´quence h(k) + h′ (k). r − 1} (r < m).length . . i f (p != null) t[getSlot(p. m − 1} et h′ : U → {1.4] D. La meilleure solution consiste ee e e e a ` utiliser un double hachage. . le nombre moyen de sondages pour un hachage e . On d´montre [6. 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. 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.key)] = p . int new_sz = 2*old_sz . . 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. la case 8 a la plus grande probabilit´ d’ˆtre occup´e.

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

e La table L bas´e sur les listes d’associations.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. c’est donc surtout cette derni`re qui se montre performante ici. nombre d’associations n = 16092). au moins dans le cadre de cet exp´rience. taille e e initiale 16) La table Lib bas´e sur les HashMap de la librairie (probablement chaˆ e ınage. e . 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. facteur de charge 0.75. taille initiale 16) Toutes les tables de hachage sont redimensionn´es automatiquement. TABLE DE HACHAGE 77 fait que nous exploitons directement dans notre m´thode get. page 66).val) dans put).0. 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. Les r´sultats (figure 4) e e Fig. Cette e figure montre par exemple qu’il y a un peu plus de 800 listes de collisions de longueur trois. e La table H bas´e sur le hachage avec chaˆ e ınage (facteur de charge 4. 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. En conclusion l’int´rˆt en pratique des tables de hachage est tr`s important. l’efficacit´ des tables de hachage repose sur l’uniformit´ de la fonction e e e de hachage des chaˆ ınes de Java.put(key. e En derni`re analyse. 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). facteur de charge 0.2. taille initiale 16) La table O bas´e sur le hachage ouvert (sondage lin´aire. pour les quatre impl´mentations des tables d’associations. 2.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.

Ce qui veut dire. Par exemple. . pour des entiers k et a petits. an−1 est l’entier a0 · e a ıne B n−1 + a1 · B n−2 + · · · + an−1 . l’interversion de deux caract`res passera inaper¸ue. Dans le mˆme e e contexte. En effet. on a a · B + b ≡ b · B + a (mod m) En pratique. par exemple. 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. si les caract`res des cl´s sont limit´s ` l’alphabet minuscule. si on prend m = 2r . m − 1}. une bonne valeur pour m est un nombre premier tel que B k ± a n’est pas divisible par m. e P (k) = {k|h(k)=j} 1 m En pratique. certaines valeurs de m sont ` ´viter. on peut prendre B = 26 . .78 CHAPITRE III. . 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 . Formellement. e c comme (a · B + b) − (b · B + a) = (a − b)(B − 1). et conduit ` de nombreuses a collisions si les cl´s sont par exemple des adverbes (se terminant toutes par ment). 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. ASSOCIATIONS — TABLES DE HACHAGE Fig. . . 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. En partiıt e a culier. Si les caract`res sont quelconques on a B = 216 en Java et B = 28 e en C . que le d´but des chaˆ e e ınes longues (vues comme des entiers en base B = 2p ) ne compte plus. C’est ` dire que la chaˆ a0 a1 . on ait pour tout j avec 0 ≤ j ≤ m − 1. etc. si on prend m = B − 1. e e e a Nous pouvons donc revenir aux fonctions de hachage sur les entiers.1 Choix des fonctions de hachage En th´orie e Rappelons qu’une fonction de hachage est une fonction h : U → {0. Une bonne fonction de hachage doit se rapprocher le plus possible d’une r´partition uniforme. on veut souvent que des cl´s voisines donnent des valeurs de hachages tr`s distinctes. car on peut toujours se ramener ` ce cas. Par exemple. 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. on connaˆ rarement la probabilit´ P et on se limite ` des heuristiques. . si les cl´s sont des chaˆ a e ınes de caract`res.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

case MUL: case DIV: i f (lvl > 2) out. On examine a a e ensuite l’´ventuel parenth´sage d’un op´rateur.7. out. i f (lvl > 1) out.tag == MUL ? ’*’ : ’/’) . e. e e La sortie est un PrintWriter qui poss`de une m´thode print exactement comme System. Exp e. e. 2) . 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).e1. out. e.out e e mais est bien plus efficace (voir B. expToInfix(out. expToInfix(out.2). et ` gauche ou ` droite d’un op´rateur additif ou multiplicatif. a static void expToInfix(PrintWriter out. 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). } throw new Error ("expToInfix : arbre Exp incorrect") .e2. return . e Tout d’abord.tag == ADD ? ’+’ : ’-’) .print(e.print(e.print(e. Pour afficher l’op´rateur. 2) . a e ` droite des multiplicatifs : parenth´ser tous les op´rateurs. i f (lvl > 2) out. (3) A e e On identifie les trois classes par 1. les parenth`ses autour d’un entier ne sont jamais utiles.print(’(’) . 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.tag est ´gal ` SUB puisque nous sommes dans un cas regroup´ du switch ne concernant que e a e ADD et SUB.e2. expToInfix(out.5. Ensuite. 2 et 3.print(’)’) . 1) . 3) .1).4. Le code utilise une particularit´ de l’instruction switch : e on peut grouper les cas (ici des additifs et des multiplicatifs). . Il y a cinq positions possibles : au e e a e sommet de l’arbre. expToInfix(out.print(’(’) .print(’)’) .tag) { case INT: out. les op´rateurs d’une classe e e donn´e ont le mˆme comportement vis ` vis du parenth´sage. on a ree cours ` l’expression conditionnelle (voir B. 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.asInt) . ARBRES DE SYNTAXE ABSTRAITE 95 Examinons la question d’afficher un arbre Exp sous forme infixe sans abuser des parenth`ses. Par exemple e. 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. il faut donc pae e renth´ser 1-(2+3)). return . On voit alors que les additifs sont ` parenth´ser a e pour les classes strictement sup´rieures ` 1. ou comme argument d’un op´rateur multiplicatif (consid´rer (1+2)*3 e e e et 1*(2+3)). les additifs (+ et -) et les multiplicatifs (* et /).tag est ´gal ` ADD et ’-’ autrement — et ici (( autrement )) signifie n´cessairement que e a e e. case ADD: case SUB: i f (lvl > 1) out. e. } La m´thode expToInfix m´lange r´cursion et affichage.tag == ADD ? ’+’ : ’-’ vaut a ’+’ si e. a e ` (2) A droite des additifs et ` gauche des multiplicatifs : ne parenth´ser que les additifs. int lvl) { switch (e. return .5.e1. et les multiplicatifs pour les classes strictement e a sup´rieures ` 2. on distingue deux e classes d’op´rateurs.

e e 5. e . ee ea e Tab. Le passage devant le guichet.5. Les files de priorit´ sont des files d’attente o` les ea e e u ´l´ments ont un niveau de priorit´. Reprenons l’exemple de la figure II. ordonn´s ou non. ee e ee se fait en fonction de son niveau de priorit´. 1) . une file de priorit´ est un type abstrait de donn´es op´rant sur un e e e e ensemble ordonn´. Exp e = postfixToExp(arg) . 5 Files de priorit´ e Nous avons d´j` rencontr´ les files d’attente. 1 – Complexit´ des implantations de files de priorit´.4. 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. % java Exp 6 3 2 . 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´. ou le traitement de l’´l´ment.’*’ 6/(3-2+1)*(9-6) Ce qui est meilleur que l’affichage ((6/((3-2)+1))*(9-6)) de l’exercice II. 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. out.1 + / 9 6 . L’implantation d’une file de priorit´ est un exemple e e d’utilisation d’arbre. 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. et ce dernier niveau est rempli (( ` gauche )). e e En d’autres termes. e.2. } Les PrintWriter sont bufferis´s.1 Tas Un arbre binaire est tass´ si son code est un segment initial pour l’ordre des mots crois´s. e voir B.96 CHAPITRE IV.flush() avant de finir. dans un tel arbre. Plusieurs implantations d’une file de priorit´ sont envisageables : par tableau ou e e par liste.println() . et c’est pourquoi elle trouve naturellement sa place ici.flush() . Nous allons utiliser des tas. out.4. tous les niveaux sont enti`rement remplis ` l’exception e a peut-ˆtre du dernier niveau. La figure 16 montre un e a arbre tass´. 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.out) . expToInfix(out. il faut vider le tampon par out. De mani`re plus formelle. on peut remplacer (( le plus grand ´l´ment )) par (( le plus petit ´l´ment )) en prenant u ee ee l’ordre oppos´.

FILES DE PRIORITE Proposition 7 La hauteur d’un arbre tass´ ` n nœuds est ⌊log2 n⌋. 17 – Un arbre tass´. Ces num´ros sont des indices dans un tableau (cf e e a e figure 17). e e L’´l´ment d’indice i du tableau est le contenu du nœud de num´ro i. ea 23 15 12 4 8 2 Fig. 0 23 1 15 3 12 7 4 8 8 9 2 4 5 5 6 2 7 6 1 Fig. 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. Ceci entraˆ par transitivit´. que le contenu de chaque e e a ıne. 5. e e a L’arbre de la figure 16 est un tas. avec la num´rotation de ses nœuds. e nœud est sup´rieur ou ´gal ` celui de ses descendants. 16 – Un arbre tass´.2 Implantation d’un tas Un tas s’implante facilement ` l’aide d’un simple tableau. Les nœuds d’un arbre tass´ sont a e num´rot´s en largeur.´ 5. de gauche ` droite. 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 . Dans notre exemple.

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

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

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

en O(n log n). lorsque l’on calcule le maximum suivant. 21 – Un arbre de s´lection pour 8 listes. 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. En effet.maximum(). seule une donn´e sur les k donn´es compar´es a chang´. et on veut les fusionner en e c e une seule suite croissante. Chaque feuille est en fait une liste. e contient. on appelle n fois chacune e des m´thodes ajouter et supprimer.supprimer(). On est en pr´sence de k e a e e suites de nombres ordonn´es de fa¸on d´croissante (ou croissante). a[i] = v.´ 5. e a a e L’arbre de s´lection est un arbre tass´ ` k feuilles. puis on cherche le maxiee mum de ces nombres. e e ıt La solution que nous proposons est bas´e sur un tas qui m´morise partiellement l’ordre. Si la somme des ee u longueurs des k listes de donn´es est n. } return a. comme cl´. FILES DE PRIORITE { int v = t. t. e e ` Un algorithme (( na¨ )) op`re de mani`re la suivante. l’une e ea des k listes ` fusionner (voir figure 21). L’ordre des k −1 autres ´l´ments reste le e e e e ee mˆme. En effet. 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). e e ee . le maximum des cl´s de ses deux fils (ou le plus grand ´l´ment d’une liste). Le premier terme correspond au (( pr´traitement )). Cette situation se pr´sente par exemple lorsqu’on veut fusionner des e donn´es provenant de capteurs diff´rents. A chaque ´tape.3 Arbres de s´lection e Une variante des arbres tass´s sert ` la fusion de listes ordonn´es. Ce nombre est ins´r´ dans la liste r´sultat. } 101 La complexit´ de ce tri est. 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. La recherche du maximum de k ´l´ments coˆte k − 1 comparaisons. e 5. On e e verra que l’on peut effectuer la fusion en temps O(k + n log k). dans le pire des cas. et on peut ´conomiser des comparaisons si l’on connaˆ cet ordre au moins partiellement. et supprim´ de la liste dont il ee e e provient. c’est-`-dire ` la mise en place de la structure particuli`re. l’algorithme na¨ est donc de complexit´ O(nk). La hauteur de l’arbre est log k.

une perte peut ˆtre accept´e si elle n’est pas e e perceptible. il existe des algorithmes (( num´riques )). avec la e e ` nouvelle valeur du fils mis-`-jour. e e Parmi les algorithmes de compression sans perte. et les caract`res rares e e e par des codes longs . les algorithmes bas´s sur les dictionnaires enregistrent toutes les chaˆ e ınes de caract`res trouv´es dans une table. L’algorithme de Huffman est un algorithme statistique. 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. dans la liste. et si une chaˆ apparaˆ une deuxi`me fois. Le codage arithm´tique e e e a e repose aussi sur la fr´quence des lettres. 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. par son suivant. A la racine. ARBRES En particulier.1 Codage de Huffman Compression des donn´es e La compression des donn´es est un probl`me algorithmique aussi important que le tri. on aboutit ` la liste dont la valeur e a de tˆte est ce maximum. Ces algorithmes seront ´tudi´s dans le cours 431 ou en e e e majeure. . sur les nœuds d’un chemin menant ` la racine. e ee temps k. elle est remplac´e e e ıne ıt e e par son indice dans la table. 22 – Apr`s extraction du plus grand ´l´ment et recalcul.102 CHAPITRE IV. L’extraction d’un plus a grand ´l´ment se fait donc en temps constant. on trouve le nouveau maximum (voir figure 22). 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. on e ee e remonte vers la racine en recalculant. et la compression avec perte. a La mise-`-jour se fait donc en O(log k) op´rations. le valeur de la cl´. L’´l´ment est remplac´. On e e distingue la compression sans perte. En e e revanche. de donn´es scientifiques ou du code compil´ de programmes. o` les donn´es d´compress´es sont identiques aux donn´es u e e e e de d´part. L’algorithme de Ziv-Lempel est bas´ sur un dictionnaire. La compression sans perte est importante par exemple e dans la compression de textes. Ensuite. Cette extraction est suivie d’un mise-`-jour : ee a En descendant le chemin dont les cl´s portent le maximum. on distingue plusieurs cat´gories : les algoe rithmes statistiques codent les caract`res fr´quents par des codes courts. pour la compression d’images et de sons. ou si elle est automatiquement corrig´e par le r´cepteur. Enfin. pour chaque nœud rencontr´. 6 6.

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

6. 10. Le nouvel arbre a pour e e fr´quence la somme des fr´quences de ses deux sous-arbres. 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. une fois cod´. 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. abracadabra devient a 000101100011001000010110. Si l’on choisit un e e autre codage. e a e e Le probl`me qui se pose est de minimiser la taille du texte cod´.2 Construction de l’arbre L’algorithme de Huffman construit un arbre binaire complet qui donne un code optimal. e e . le texte abracadabra. Avec le code donn´ dans le e e e tableau ci-dessus. et on e e recommence ` la racine. Dans le cas de l’arbre de la figure 24. 010.104 CHAPITRE IV. 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. est compos´ de 25 bits. 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. 011. ´ventuellement ` un reste pr`s qui est un pr´fixe d’un mot du code. et a pour valeur la fr´quence d’apparition de la a e lettre dans le texte. Quand le code est complet. It´ration On fusionne deux des arbres dont la fr´quence est minimale. 2 lettres b et r. il y a 5 lettres a. Le code de la lettre est le code de la feuille. et 1 fois la lettre c et d. et de se laisser guider par les ıne e e ´tiquettes. Ainsi. Lorsque l’on est dans une feuille. 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. on est sˆr de pouvoir toujours d´coder un a u e message. 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. 24 – Le code des feuilles est {00. Chaque e e feuille correspond ` une lettre du texte. c’est-`-dire e a l’´tiquette du chemin de la racine ` la feuille. Bien e sˆr. ARBRES 0 0 a 0 b 1 1 c 0 d 1 1 r Fig. Ici. 11}. on y trouve le caract`re correspondant. e Initialisation On construit une forˆt d’arbres binaires form´s chacun d’une seul feuille.2. on associe e a les lettres aux feuilles de la gauche vers la droite.

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

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

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

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

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

dans un arbre de Huffman. e e Notons enfin que le principe de l’algorithme adaptatif s’applique ` tous les algorithmes a de compression statistiques. mais e a les fr´quences moyennes rencontr´es dans les textes de la langue consid´r´e. Il existe. comme l’algorithme statique. 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 e La deuxi`me op´ration garantit que l’ordre reste compatible avec les fr´quences. e e e Partant de la feuille du caract`re. 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. une version adaptative de l’algorithme de compression arithm´tique. un arbre de Huffman qu’il met ` jour comme l’exp´diteur. e e e Avant l’incr´mentation. C’est dans ce cas que l’algorithme adaptatif s’av`re particuli`rement utile. e L’algorithme adaptatif op`re. 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. 31 – Un arbre de Huffman avec un parcours compatible. Supposons que e e e . 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. Les deux inconv´nients du codage de Huffman (statique) sont corrig´s par cette version. les nœuds sont num´rot´s dans un tel ordre. sa fr´quence est incr´ment´e. on peut toujours trouver un ordre de parcours e poss´dant ces propri´t´s. Soit x le caract`re lu dans e e e e le texte clair. avec leurs fr´quences. Bien entendu. de la mani`re suivante. Le code correspondant est envoy´. sur un arbre. puis les a e deux op´rations suivantes sont r´alis´es. Aux nœuds de l’arbre sont stock´es les e e fr´quences cumul´es. 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). 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). et il sait donc ` tout moment quel a e a est le code d’une lettre. 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. Il correspond ` une feuille de l’arbre.110 CHAPITRE IV. 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. deux nœuds fr`res sont toujours cons´cutifs. L’arbre e ee e e de Huffman ´volue avec chaque lettre cod´e. par exemple. Aux feuilles de e l’arbre se trouvent les lettres. et on remonte vers la e e e e racine en incr´mentant les fr´quences dans les nœuds rencontr´s. e e On d´montre que. Dans la figure 31.

et arbre final (` droite). Ensuite. avant d’incr´menter le nœud de num´ro e e e e e 8. de mˆme fr´quence (figure 33). la racine e e e e e est incr´ment´e. 33 – Avant permutation des nœuds 8 et 9 (` gauche) . La fr´quence de la feuille d est incr´ment´e.6. Le p`re de la feuille d a pour e e e e e fr´quence 5. dans l’arbre de la figure 31. et les contes de Grimm en faisaient partie. 32 – Apr`s incr´mentation de d (` gauche). a a la lettre suivante du texte clair ` coder. 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. 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. et apr`s permutation des nœuds (` droite). Son num´ro d’ordre est 4. voici une table de quelques statistiques concernant l’algorithme de Huffman. De mˆme. et le plus grand nœud de fr´quence 5 a num´ro d’ordre e e e e 5. le taux e de compression obtenu par les deux m´thodes est sensiblement ´gal.. car les donn´es sont assez e e e . Comme on le voit. 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.. Les deux nœuds sont ´chang´s (figure 32). il est ´chang´ avec le nœud de num´ro 9. Le code a ´mis est alors 1001. e e Pour terminer. soit une lettre d.

ARBRES Texte technique 700 000 518 361 954 519315 519561 Si on utilise un codage par digrammes (groupe de deux lettres). e e .112 homog`nes. 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. e Taille (bits) Huffman Taille code Total Adaptatif Contes de Grimm 700 000 439 613 522 440135 440164 CHAPITRE IV.

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

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

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

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

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

appel´ dictionnaire. 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. Nous pr´sentons les deux. e La m´thode s’´crit r´cursivement comme suit : e e e . les op´rations sont r´alisables de e e mani`re particuli`rement efficace. e e 2. Plusieurs implantations d’un dictionnaire sont envisageables : par tableau. et e e e e e une m´thode it´rative. Si un nœud poss`de un fils gauche. Les arbres binaires de recherche fournissent. cheminant dans l’arbre. ainsi que des op´rations destrucu ee e ea e e tives. mais certainement pas dans le sous-arbre droit Ad . et par arbre binaire de recherche. on a trouv´ la r´ponse . par liste ordonn´s ou non. On ´limine ainsi de la recherche tous e les nœuds du sous-arbre droit. qui n’a pas de fils droit. sinon il y a e e e e deux cas selon que x < c et x > c. Ce nœud n’est pas n´cessairement une feuille. par tas. dans l’arbre de la figure 3.118 CHAPITRE V. on commence par e e ee comparer x au contenu c de la racine de A. Comme toujours. 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. 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. Tab. calqu´e sur la d´finition r´cursive des arbres. e R`gle. une implantation souvent efficace d’un type abstrait de donn´es. 1 – Complexit´ des implantations de dictionnaires e L’entier h d´signe la hauteur de l’arbre. S’il y a ´galit´.. c’este e e a `-dire lorsque la hauteur est proche du logarithme de la taille. comme on le verra.. Si x < c. Ainsi. et son pr´d´cesseur est le e e e nœud de contenu 14. 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. Pour chercher si un ´l´ment x figure dans un arbre A. 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. La table 1 rassemble la complexit´ e e des op´rations de dictionnaire selon la structure de donn´es choisie. la racine poss`de un fils gauche.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. la recherche. en commen¸ant par e e e c la m´thode r´cursive. u ee e e Ces coˆts supposent l’´l´ment supprim´ d´j` trouv´. son pr´d´cesseur dans le parcours infixe est le nœud e e e e le plus ` droite dans son sous-arbre gauche. et null sinon. mais a e il n’a pas de fils droit. On voit que lorsque l’arbre est bien ´quilibr´. Cette m´thode n’est pas sans rappeler la recherche dichotomique. alors x figure peut-ˆtre dans le sous-arbre gauche Ag e de A. ee e il y a le choix entre une m´thode r´cursive.

filsG). x. return chercher(x. 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). return new Arbre(b. e static Arbre inserer(int x. Arbre a) { if (a == null || x == a. a. } . else if (x > a. if (x < a. ce qui est logique.filsG = inserer(x.contenu. null).filsD). } Voici la version non destructive. soit on modifie l’arbre existant (version destructive). ARBRES BINAIRES DE RECHERCHE static Arbre chercher(int x.contenu) return a.contenu) { Arbre b = inserer(x. Arbre a) { while(a != null && x != a.2 Insertion d’une cl´ e L’adjonction d’un nouvel ´l´ment ` un arbre modifie l’arbre. a.contenu) a.filsD). return a. Nous pr´sentons une m´thode e e r´cursive dans les deux versions. } 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. e u Voici la m´thode it´rative.contenu) if (x < a. a. if (x < a.contenu) a. e e static chercherI(int x. a. a.filsD). on ne e ea l’ajoute pas une deuxi`me fois. Ceci inclut le cas o` l’arbre est vide. static Arbre inserer(int x. si l’entier figure d´j` dans l’arbre.filsG).filsD.filsD = inserer(x. e e e 2.filsG. return a. null). Voici la version destructive. else a = a.filsG).contenu) return chercher(x.contenu) a = a. x. Arbre a) { if (a == null) return new Arbre(null. Dans les deux cas. Arbre a) { if (a == null) return new Arbre(null. a. if (x < a.2. } 119 Cette m´thode retourne null si l’arbre a ne contient pas x. a.

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

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

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

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

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

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

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

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

Apr`s une insertion (respectivement une suppression).max(H(g). 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). Pour l’arbre vide. contenu = c. filsD. on fait une rotation droite. la hauteur vaut −1.4 Implantation : la classe Avl Pour l’implantation.max(H(a. Pour une feuille par exemple.. On suppose que |h(G) − h(D)| = 2.hauteur. } . Si h(G) − h(D) = −2 on op`re de fa¸on sym´trique. Cette propri´t´ n’est plus vraie pour une suppression. filsD = d. Avl(Avl g. } static void calculerHauteur(Avl a) { a. Cette propri´t´ est g´n´rale. L’algorithme est le suivant : u Algorithme. e a e a class { int int Avl Avl contenu. Soit A un arbre. hauteur. ARBRES BINAIRES 61 double rotation 37 44 50 71 83 Fig. 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.hauteur = 1 + Math. nous munissons chaque nœud d’un champ suppl´mentaire qui contient e la hauteur de l’arbre dont il est racine.filsD)). 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. H(a. e e La m´thode H sert ` simplifier l’acc`s ` la hauteur d’un arbre.filsG). hauteur = 1 + Math. Avl d) { filsG = g. filsG.128 71 44 37 61 83 insertion de 50 37 44 61 50 71 83 CHAPITRE V. Si h(G) − h(D) = 2. e ee 3. } static int H(Avl a) { return (a == null) ? -1 : a.H(d)).1. 15 – Insertion suivie d’une double rotation.. qui est repr´sent´ par null et qui n’est donc pas un objet. G et D ses sous-arbres gauche et droit. } . ce champ a la valeur 0. int c.

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. en prenant soin de r´´quilibrer a e e ee l’arbre ` chaque ´tape.filsD) == -2) { if (H(a. } La m´thode principale implante l’algorithme de r´´quilibrage expos´ plus haut. ARBRES EQUILIBRES 129 La m´thode calculerHauteur recalcule la hauteur d’un arbre ` partir des hauteurs de ses souse a arbres. a. a. Avl a) null) a.H(a.filsG.filsG) < H(a.filsD. b.filsD)).filsD = inserer(x.contenu) a. Avl a) { if (a == null) return new Avl(null.filsG).filsG) . Avl c = new Avl(a.filsD)) a. return rotationG(a). if(H(a.filsG = rotationG(a. null).filsG). } return a.filsG)) a. Ces m´thodes et les suivantes font toutes partie de la classe ee e Avl.max(H(a.filsD).filsD).filsD. b. H(a. On utilise la version non e e destructive qui r´´value la hauteur. on obtient e e static Avl inserer(int x.filsG) . if (x < a.´ ´ 3. Les rotations sont reprises de la section pr´c´dente. x.filsD).filsG).contenu) .contenu.filsG.filsD = rotationD(a. else if (x > a. } Il reste ` ´crire les m´thodes d’insertion et de suppression. a. static Avl rotationG(Avl a) { Avl b = a. return rotationD(a). b.filsG.contenu. //seul changement } La suppression s’´crit comme suit e static Avl { if (a == return if (x == supprimer(int x.hauteur = 1 + Math. } //else version sym´trique e if (H(a. On reprend simplement les m´thodes d´j` ´crites pour un arbre binaire a e e eae de recherche g´n´ral.filsD) < H(a.H(a.filsG).filsD) == 2) { if (H(a. Pour l’insertion. return equilibrer(a).filsD.filsG = inserer(x. return new Avl(c. a. e ee e static Avl equilibrer(Avl a) { a.contenu) 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. si chaque nœud a de l’ordre de 1000 e e e fils.filsD = supprimer(x.filsD == null) return null. mais d’arit´ plus e e grande. 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.filsD). e Les donn´es sont en g´n´ral rep´r´es par des cl´s.filsG = supprimer(a. e Les B-arbres ou les arbres a-b servent.filsG). return equilibrer(a). En effet. Les arbres rouge et noir (ou bicolores) sont semblables. ARBRES BINAIRES static Avl dernierDescendant(Avl a) // inchang´e e { if (a. return equilibrer(a). Chaque nœud interne d’un tel arbre contient.filsG = supprimer(x. En effet.2 B-arbres et arbres a-b Dans cette section. on trouve aussi les B-arbres et en particulier les arbres 2-3-4. e e ee e les nœuds internes pouvant avoir un nombre variable de fils (ici entre a et b). a. a. environ autant de temps que 200 000 instructions. return dernierDescendant(a.filsG).contenu. il suffit d’un arbre de hauteur 3 pour stocker un milliard de cl´s. en moyenne. a e Un disque est divis´ en pages (par exemple de taille 512.filsG). a. un e e e e seul acc`s disque peut prendre. la hauteur d’un tel arbre — qui mesure le nombre d’acc`s disques n´cessaire — est alors tr`s faible. if (a. // seul changement } CHAPITRE V.contenu = b.filsD == null) return a.filsG). if (x < a. e Nous consid´rons ici des arbres de recherche qui ne sont plus binaires.filsD). ee .filsG == null) return equilibrer(a. if (a. et de les manipuler de concert. a. qui sont rang´es dans un arbre. Avl b = dernierDescendant(a. 2048. en plus des r´f´rences vers ses sous-arbres. Dans cette cat´gorie e d’arbres.filsD). Or. // seul changement } static Avl supprimerRacine(Avl a) { if (a. nous d´crivons de mani`re succinte les arbres a-b. un disque en g´n´ral. ` savoir e e e a l’acc`s proprement dit aux donn´es. Si chaque e e e ee e e acc`s ` un nœud requiert un acc`s disque. a. De plus. ` minimiser les acc`s au disque. } 3. il survient un autre probl`me. La e page est l’unit´ de transfert entre m´moire centrale et disque.contenu) a.filsD == null) return equilibrer(a. les donn´es ne tiennent pas en m´moire vive.filsG == null && a. Il est donc rentable de grouper e e les donn´es par blocs. et les e e e e donn´es sont donc accessibles seulement sur la m´moire de masse. ee e e Lorsque l’on manipule de tr`s grands volumes de donn´es. else a.contenu. L’int´rˆt des arbres ´quilibr´s est qu’ils permettent des modifications en temps logarithmique.130 return supprimerRacine(a). dans ce contexte. 4092 ou 8192 octets).

e et les feuilles contiennent les cl´s. dans les arbres e e que nous consid´rons.2. e a les autres nœuds internes ont au moins a et au plus b fils. . . cd ). . les cl´s aux nœuds internes ne servent qu’` la navigation.]kd−1 . 17 – Un arbre 2-4. 16 – Un nœud interne muni de balises et ses sous-arbres. alors log n/ log b h < 1 + log(n/2)/ log a . e e 3. 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. ARBRES EQUILIBRES 131 des balises. ]kd . 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. Les nœuds internes contiennent les balises. . 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. kd telles que c0 k1 < c1 ··· kd < cd pour toute s´quence de cl´s (c0 . Il en r´sulte que pour chercher une cl´ c. e e u e k1 k2 ··· kd cl´s e k1 A0 cl´s e ]k1 . . . Un arbre a-b est un arbre de recherche tel que les feuilles ont toutes la mˆme profondeur. e ee e Proposition 13 Si un arbre a-b de hauteur h contient n feuilles. kd ] Ad−1 cl´s e > kd Ad Fig. Un nœud a entre 2 et 4 fils. alors il est muni de d balises k1 .1 Arbres a-b Soient a 2 et b 2a − 1 deux entiers. . . Ad .. e la racine a au moins 2 fils (sauf si l’arbre est r´duit ` sa racine) et au plus b fils. figure 16). ∞[ contient la cl´. . . L’arbre de la figure 17 repr´sente un arbre 2-4. o` chaque ci est une cl´ du sous-arbre Ai (cf. kd ]. Plus pr´cis´ment. ..´ ´ 3. k1 ]. L’int´rˆt des arbres a-b est justifi´ par la proposition suivante. 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. on d´termine le sous-arbre Ai appropri´ en e e e e d´terminant lequel des intervalles ] − ∞. . . ]k1 . alors que. k2 ] A1 cl´s e ]kd−1 . k2 ]. si un nœud interne poss`de d + 1 sous-arbres e e e e A0 . . .

2. Mais alors. pour e e e qu’il puisse ` son tour proc´der ` l’insertion des deux fils ` la place du nœud ´clat´. 3.2. On ins`re alors une u e nouvelle feuille. le nœud aux balises 11. c’est-`-dire si son nombre de fils descend au dessous e a de a. Cette insertion se fait par un double e ´clatement. 12. il y a au moins 2ah−1 feuilles. la suppression est plus complexe. Tout nœud a au plus b fils. 18 – Un arbre 2-4. de la cl´ 15 dans l’arbre 17 produit l’arbre de la figure 18.3 Suppression dans un arbre a-b Comme d’habitude. la fusion des deux nœuds risque de e conduire ` un nœud qui a trop de fils et qu’il faut ´clater. Si un nœud a b + 1 fils. e e e la racine de l’arbre 17 a un nœud de trop. et la racine en a au moins 2. par e une recherche. Tout nœud autre que la racine a au moins a fils. Le coˆt est donc born´ a e u e logarithmiquement en fonction du nombre de feuilles dans l’arbre. D’abord. 14 de l’arbre 17 est ´clat´ en deux. et donc chaque nouveau nœud aura au moins a fils. on commence par d´terminer. 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. On groupe ces deux op´rations sous a e e la forme d’un partage (voir figure 19).132 CHAPITRE V. . 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). ARBRES BINAIRES Preuve. 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´.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. 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. ce qui fait augmenter e e e la hauteur de l’arbre. donc 2ah−1 n bh . l’emplacement de la feuille o` l’insertion doit avoir lieu. 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. 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. Noter que b + 1 2a. 3. Les balises sont ´galement partag´es. La racine elle-mˆme est ´clat´e. et la balise centrale restante est transmise au nœud p`re. Il s’agit de fusionner un nœud avec un nœud fr`re lorsqu’il n’a plus assez de fils. le deuxi`me e e c e les fils de droite. 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). Pour l’insertion. Au total. Mais si son fr`re (gauche ou droit) a beaucoup de fils. Un nœud a entre 2 et 4 fils.

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

134 CHAPITRE V. ARBRES BINAIRES .

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

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

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. mn } est un langage fini contenant n mots. . et en r`gles d’inf´rence e e e proprement dites qui sont les cas inductifs de la d´finition. Empty) qui sont les cas de base de la d´finition. e e e Exemple 1 Nous savons donc que le langage des mots du fran¸ais selon l’Acad´mie est r´gulier. Lorsque m appartient au langage eae e d´fini par p. La d´finition de p m est faite selon le formalisme des r`gles d’inf´rence. De mˆme. on a presque rien ´crit en fait ! e e e e Tout a d´j` ´t´ dit ou presque dans la section 1. . Si L = {m1 . Il permet la d´finition inductive d’un pr´dicat. e e e e Fig. on dit aussi que le motif p filtre le mot m. . LANGAGES REGULIERS 137 1. m2 . 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. C’est tout simplement une d´finition e e e e inductive. 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. 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.´ 1. 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. une expression r´guli`re a une valeur e e e e qui est ici un ensemble de mots.1 sur les mots. ´galement not´e p m. alors L est exactement d´crit par l’alternative de tous les mi . Les r`gles se d´composent e e e e e en axiomes (par ex. e Solution. qui ` chaque expression r´guli`re p associe un sous-ensemble [[p]] de Σ∗ .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. Les r`gles sont ` comprendre comme e e a .4 Filtrage La relation de filtrage m ∈ [[p ]]. peut ˆtre d´finie directement par les e e e e r`gles de la figure 3. Un langage qui peut ˆtre d´fini comme e e e la valeur d’une expression r´guli`re est dit langage r´gulier. .

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

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

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

e % egrep ’i.???} 3.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. Ce e e a n’est en fait pas toujours n´cessaire. on peut compter toutes les lignes des fichiers source du r´pertoire e e e courant : % wc -l *. l’argument text ´tant le mot m. jusqu’` produire un automate (voir le e e a a chapitre suivant).). e . 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 {?. e ıne e par souci d’efficacit´.*i.class Dans le mˆme ordre d’id´e.util. a e Ainsi. En fait. 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). donn´ sous forme d’un fichier texte. . que le fichier /usr/share/dict/ ea e french de votre machine Unix est un dictionnaire fran¸ais. 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. et que e e e e e e l’alternative se note ` peu pr`s {p1 . En simplifiant. tandis que * repr´sente un mot quelconque (not´ pr´c´demment .compile qui prend une chaˆ repr´sentant e ıne e un motif en argument et renvoie un Pattern.regexp.p2 } (pour p1 | p2 ).´ ` 3. elle proc`de ` bien plus de travail. c e a ` raison d’un mot par ligne.*i. . PROGRAMMATION AVEC LES EXPRESSIONS REGULIERES 141 3. Supposons. La syntaxe concr`te des motifs est e e franchement particuli`re.??. On peut alors trouver les mots qui contiennent au moins six fois la lettre (( i )) de cette mani`re. On pourrait penser que la m´thode compile e interpr`te la chaˆ donn´e en argument et produit un arbre de syntaxe abstraite. 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.*i’ /usr/share/dict/french indivisibilit´ e On notera que l’argument motif est donn´ entre simples quotes (( ’ )).*i. et e sont construits par la m´thode statique Pattern.2 Recherche de lignes dans un fichier. Les objets de la classe Pattern impl´mentent les motifs.java La commande wc1 (pour word count) compte les lignes. il offre (entre autres !) 1 On obtient le d´tail du fonctionnement d’une commande Unix cmd par man cmd. e 3. Pour le moment. mais c’est toujours prudent. mots et caract`res dans les fichiers e donn´s en argument.*). Enfin. 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 *. La syntaxe des motifs est relativement conforme ` ce que e a nous avons d´j` d´crit.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. il suffit de consid´rer qu’un Pattern est la forme Java d’un e motif. comme c’est souvent le cas. Notons simplement que ? repr´sente un caract`re quelconque (not´ e e e e pr´c´demment . L’option (( -l )) restreint l’affichage au seul compte des lignes.

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

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

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

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

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

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

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

est nettement moins efficace.*o|u.5 16. dans le source Grep. Si tel n’´tait a ıne e e e pas le cas.2 9.*. . ce qui nous donne le nouveau source ReGrep.9 . . pat. j)) return true .6 15. et time java ReGrep .7 3.´ ´ ` 4.1 2 2.7 17. (1) Dans le dictionnaire fran¸ais.*a|e. k <= j . j) aurait alors les mˆmes arguments que lors de l’appel. D`s lors.util. pat.0 On voit que notre technique. 4. nous recherchons les mots qui contiennent n fois c la lettre e. nous recherchons les mots qui contiennent au moins n fois c la mˆme voyelle non accentu´e.regex.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. dans le cas d’une chaˆ non-vide.*i|o.7 15. ` condition e a que le source des classes du package regex se trouve dans un sous-r´pertoire regex.4 4 1. Un autre e point de vue est de consid´rer que l’application de la r`gle StarSeq ` ce cas est inutile.*(e|´|`|^).*(e|´|`|^)’ /usr/share/dict/french e e e e e e e e e Grep ReGrep 1 2. } 149 On note un point un peu subtil.0 3 1. k) && matches(text. IMPLEMENTATION DES EXPRESSIONS REGULIERES case STAR: i f (i == j) { return true .8 4 1.p1.*a. k++) { i f (matches(text. la m´thode matches pourrait ne pas terminer. } else { for (int k = i+1 . q ǫ q* q* m m L’inutilit´ de cette r`gle est particuli`rement flagrante. apr`s effacement des accents. .java.6 6 1. puisqu’une des pr´misses et la conclusion e e e e sont identiques.6 18.*e.*u.7 3 1. sans ˆtre ridicule.2 5 1. changer la ligne import java.*i.9 3. En effet. on ´vite le cas k = j ıne e qui correspond ` une division de la chaˆ test´e en pr´fixe vide et suffixe complet. k. e (2) Toujours dans le dictionnaire fran¸ais.9 12.*u)’ /usr/share/dict/french Grep ReGrep 1 2.5 18. k. 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 .*e|i.java (figure 5).5 9. Par exemple.*o.9 5 1.* en import regex. 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.0 7 1. pat.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. pour n = 3 nous ex´cutons la commande : e e e % java Grep ’(a.1 2 2. Par exemple.9 16.5 15.8 6 1. ). i. . pour n = 3 nous ex´cutons la e e commande : % java Grep ’(e|´|`|^). nous poue vons compiler par javac ReGrep. le second appel r´cursif e e matches(text. } return false .

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

5. Un fois atteint e e e la fin du mot. 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 ǫ. Induction facile sur la structure des motifs. En revenant aux d´finitions. 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. Alors on a : q* c ó m ⇐⇒ En toute rigueur. Par ailleurs. 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. Posons p = q*. il faut tenir compte d’une autre d´composition possible du mot c ó m u e en pr´fixe vide et suffixe complet. il ne reste qu’` v´rifier si le motif d´riv´ filtre le mot vide. par induction. UNE AUTRE APPROCHE DU FILTRAGE 151 Posons p = p1 ó p2 et supposons en outre p1 ǫ.2 Filtrage par la d´rivation des motifs e Pour savoir si un motif p filtre un mot m. Le pr´dicat N (p) se calcule inductivement ainsi : e = = = = = = faux vrai faux N (p1 ) ∨ N (P2 ) N (p1 ) ∧ N (P2 ) vrai Preuve. e e . on peut tout simplement it´rer sur les caract`res du e e mot. 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. en calculant les d´rivations successives de p par les caract`res consomm´s. a e e e Plus pr´cis´ment.   m = m1 ó m2 q c ó m1  q* m2 ǫ. 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.5. On peut ensuite conclure par induction. e puis par d´finition.

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

. Soit le motif q = . // 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.charAt(k). e e e Solution.. p1 etc.+)+X et le mot XX=· · · = e e et justifier le r´sultat de la derni`re exp´rience. } 153 On observe que.*(. k++) { d = Re. Par ailleurs la d´rivation =−1 ó q vaut q | q. Mais on enl`ve les motifs () des e e e s´quences. e e i f (Re. Notons p0 . UNE AUTRE APPROCHE DU FILTRAGE return true . e ea e on a p2 = qX. les motifs sont d´j` bien assez compliqu´s comme cela. regStart = 0 . la suite des motifs d´riv´s Il faut d’abord d´river deux fois par X. } return -1 .+)+X p1 = (.nullable(d)) return k+1 . mais la e d´rivation de X par = vaut ∅. e e e Exercice 8 Calculer la succession des motifs d´riv´s pour le motif X(. pour une position start donn´e. e e e p0 = X(. p1 = ()(.4 et une consommation tr`s e e e importante de la m´moire dans la derni`re exp´rience. private int findShortestPrefix(int start) { Re d = pat . } } mStart = mEnd = -1 .5. k < text.nullable(d)) return start .derivate(text. 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.*)*. d) . la nouvelle m´thode find trouve bien la plus e e longue sous-chaˆ filtr´e. 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. Une implantation qui donnerait la plus petite sous-chaˆ filtr´e est ıne e ıne e plus simple. i f (Re.*(. // Trouv´ le pr´fixe vide for (int k = start ..+)+X. Le motif p filtre le mot vide.+)+X p2 = . return false .*)*X Pour calculer p2 on a exprim´ p+ comme pp*. i f (d == null) return -1 .length() .

EXPRESSIONS REGULIERES .154 ´ ` CHAPITRE VI.

mots. c’est-`-dire un ensemble particulier de mots. δ. L’´tude des automates a commenc´ e e e e vers la fin des ann´es cinquante. un peu plus “th´orique” et un peu moins algorithmique que les pr´c´dents. q0 . logique. e 2 Rappel : alphabets. Parmi les a mots de Σ∗ on distingue le mot vide not´ ǫ. l’automate lit les symboles du mot un par un et va d’´tat en e e e ´tat selon les transitions. L’alphabet Σ est un ensemble de caract`res e (ou symboles). C’est donc une notion fondamentale de l’informatique. th´orie des e e graphes. Le mot vide est l’unique mot de longueur z´ro. e e e Avant de donner une d´finition plus formelle des concepts d´crits ci-dessus. Le mot lu est soit accept´ par l’automate soit rejet´. F ) constitu´ des ´l´ments suivants e e e ee un alphabet fini (Σ) un ensemble fini d’´tats (Q) e 155 . 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. L’ensemble des mots sur Σ est not´ Σ∗ . c e e par nature. langages et probl`mes e Nous reprenons ici les notations du chapitre VI. tr`s utilis´s en informatique. alg`bre. reli´s entre eux par des “transitions” qui sont marqu´es par des symboles. qui permettent e e e de mod´liser un grand nombre de syst`mes (informatiques). un mot est une suite finie de caract`res. Σ. etc. Ce chapitre est. e e Un langage est un sous-ensemble de Σ∗ .) 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. etc. De fa¸on tr`s informelle. un automate est un ensemble “d’´tats e c e e ´ du syst`me”. 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´). e e 3 Automates finis d´terministes e Un automate fini d´terministe est un quintupl´ (Q. e e e Les automates sont des objets math´matiques.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.). Elle se base sur de nombreuses techniques (topologie. Etant e e e donn´ un “mot” fourni en entr´e.

c) ∈ F } 3.1 Fonctionnement d’un automate fini d´terministe e L’automate prend en entr´e un mot et l’accepte ou la rejette. 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). a) = 1. a ∈ Σ correspond ` l’´tat indiqu´ ` l’intersection de la ligne q et a e ea de la colonne a.. l’´tat correspondant ` la lecture e e a du dernier caract`re du mot) est un ´tat de F .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. δ. c′ a) = δ(δ(q. LES AUTOMATES 3. a) Nous pouvons maintenant d´finir le langage L(A) accept´ par un automate fini d´terministe e e e A = (Q. F ). ˆ A partir d’un ´tat q en lisant le mot vide ǫ on reste dans l’´tat q.e. ˆ L(A) = {c|δ(q0 . a e Une ligne correspond ` un ´tat de l’automate (l’´tat initial est pr´c´d´ d’une fl`che “→” . e →1 ∗2 a 1 1 b 2 2 Il correspond ` l’automate (Q. 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 ` la lecture de chaque symbole. . a) pour q ∈ Q. Σ. δ(2. F ) avec a Q = {1. δ(1.e. nous c e ˆ introduisons la fonction de transition ´tendue aux mots. a) = 1. c′ ). δ(2. On dit aussi qu’il le reconnaˆ e ıt ou ne le reconnaˆ pas. b) = 2. Σ.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. δ(q. Exemple 1 Consid´rons la table de transition ci-dessous. 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. pour d´finir exactement le langage reconnu par un automate. ∀q ∈ Q. δ(q. δ. e e De fa¸on plus formelle. δ. et un ´tat ´ Etant donn´ un mot c se terminant par a ∈ Σ (i. ǫ) = q e e ′ a avec c′ ∈ Σ ∪ {ǫ}). q0 .. e e a langage. i. 2} Σ = {a. e e e e le mot est reconnu si et seulement si le dernier ´tat (i. q0 . c) = δ(q. Elle se d´finit r´cursivement comme e e e suit. b} δ(1. 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. a e e e e e e l’´tat final d’une ´toile “∗”) e e La valeur δ(q. c = c e e ˆ ˆ ˆ q de Q. 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.

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

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. Σ. u Solution. il peut y e e e avoir plusieurs transitions avec le mˆme symbole. e e e Les automates non-d´terministes permettent de mod´liser facilement des probl`mes come e e plexes. Ces derniers peuvent ˆtre e e e exponentiellement plus grand que les automates non d´terministe dont ils sont issus. le fonctionnement d’un tel automate n’est e donc pas totalement (( d´termin´ )). 1}∗ |n1 (x) ≡ 0 mod 4} o` n1 (x) est le nombre d’occurrence du symbole 1 dans le mot x. δ.158 δ → ∗ q0 q1 q2 q3 0 q2 q3 q0 q1 1 q1 q0 q3 q2 CHAPITRE VII. Solution. Ils peuvent ˆtre convertis en des automates finis d´terministe. 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´. car on ne sait pas quel ´tat l’automate va choisir. LES AUTOMATES Dessiner l’automate et montrer qu’il accepte “110101”. e Un automate fini non-d´terministe est un quintupl´ : (Q. q0 . F ) e e un alphabet fini (Σ) un ensemble fini d’´tats (Q) e .

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

b} qui terminent par bab. Face ` lui. le joueur oe peut retourner autant de jetons qu’il le souhaite.160 CHAPITRE VII. Σ 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. A chaque tour. Le but du jeu est d’avoir les quatre jetons du cˆt´ blanc. Pour cela. mais sans les d´placer. puis effectue une rotation du . 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”. un plateau sur lequel sont dispos´s en carr´ quatre jetons. ı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. blancs d’un cˆt´ e a e e oe et noirs de l’autre. Le joueur a les yeux e e band´s. 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. le maˆ e ıtre de jeu annonce si la configuration obtenue est gagnante ou pas. c} d´crits par l’expression r´guli`re (a + b + c)∗ b(a + b + c). ıt Exercice 11 Mod´lisation d’un jeu (d’apr`s la page de Jean-Eric Pin). b. Σ Solution.

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

Plus pr´cis´ment. soit en lisant − soit enfin en ne lisant rien. 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. La table de transition associ´e ` cet automate est alors : e a →A B C D ∗E ∗F ǫ {B} ∅ ∅ ∅ ∅ ∅ + {B} ∅ ∅ ∅ ∅ ∅ − {B} ∅ ∅ ∅ ∅ ∅ .3 Les ǫ transitions Rappelons qu’ǫ repr´sente le mot vide. on omet la “. C D 1. · · · . on ne cr´e pas imm´diatement tous les ´tats de l’automate e e e fini d´terministe. a) e + si S ′ n’est pas d´j` dans Qd . a – pour tout symbole a ∈ Σ. ee – ajouter S ` Qd . on peut passer de A ` B soit en e a e e a lisant +. E} ∅ ∅ 2 ∅ {C} ∅ {D. +. Ainsi. suivi d’une suite de chiffres. 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. lors de la conversion. · · · . Cette facilit´ permet e a e de programmer facilement des automates complexes. . ∅ ∅ {D} ∅ ∅ ∅ 0 ∅ {F } {C} {D} ∅ ∅ 1 ∅ {C} ∅ {D. 9 La transition de l’´tat A ` l’´tat B est r´gie par ǫ. Bien entendu. E} ∅ ∅ . construisons un automate fini non d´terministe qui e reconnaˆ les nombres d´cimaux.162 CHAPITRE VII. e 4. La seconde chaˆ ne se termine pas par “0”. 9 A ǫ. LES AUTOMATES En pratique. − B 1. on souhaite pouvoir e e ´crire le nombre d´cimal en commen¸ant par un “+” ou un “-’. + calculer l’´tat S ′ = q∈S δn (q. le “+” ou le “-” sont optionnels.”. 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. 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. 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). E} ∅ ∅ ··· ··· ··· ··· ··· ··· ··· 9 ∅ {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). 9 0 F 0. −. – choisir un ´l´ment S de E (S est donc un sous ensemble de Qn ). · · · . e e Exemple 4 Pour illustrer les ǫ transitions. Si seconde chaˆ est vide. 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’une e e c virgule et d’une suite de chiffres. ıne ıne 0. Une table de transition peut ˆtre associ´e ` un automate contenant des ǫ transition.

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´.´ ` 5. e e e Nous n’aborderons pas ici cette ´limination. e e e a 6. b a 4 5 b 6 a 7 Il suffit alors d’assembler ces automates avec une simple ǫ transition. 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. En effet.1 Mod`le e Le mod`le de donn´es est alors tr`s simple : e e e . Comme pour les automates non-d´terministes que l’on peut toujours d´terminiser. 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. Sans perte de g´n´ralit´. 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. 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. il est e e toujours possible d’´liminer les ǫ transition et d’obtenir un automate fini d´terministe ´quivalent. ıt a. 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. 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. a.

delta = d. finaux = f. } } class Automate { // ´tat initial e int q0.val.suivant. e a e L’ensemble des ´tats finaux est une liste d’entiers. Liste(int v. a ıtre ıne e Soit donc en Java les deux classes List et Automate. // ´tats finaux e Automate(int q. } } 6.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. Liste suivant. le mot que l’on cherche ` reconnaˆ est une chaˆ de caract`res String mot. e Enfin. // fonction de transition Liste finaux.val == v || estDans(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). int v) { // Le test d’appartenance i f (x == null) return false. Ainsi e e e delta[q][c] est la liste des ´tats atteignables ` partir de q en lisant le caract`re c. } La fonction accepter(String mot. class Liste { int val. Liste f. 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. else return 1 + longueur(x. int k) { i f (k == 1) return x. int i. Automate a) { . suivant = x.164 CHAPITRE VII. else return x. k-1).suivant. int q). Liste[][]d) { q0 = q.suivant). } static boolean estDans(Liste x. static boolean accepter(String mot. Liste[][] delta. } // Le k `me ´l´ment e e e static int kieme(Liste x. v). Automate e a. LES AUTOMATES L’´tat initial de l’automate est indiqu´ par un entier q0. else return kieme(x. Liste x) { val = v.

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

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

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. 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). Les travaux fondamentaux de Post et de Turing datent de la fin des ann´es 30. 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”.Annexe A Le coˆ t d’un algorithme u Ce chapitre rappelle les diff´rents moyens d’´valuer le coˆt d’un algorithme. la plupart du temps. 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). affectation. On admet commun´ment que toute op´ration ´l´mentaire prend un temps constant. 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. Notons que pour qu’un algorithme puisse s’ex´cuter. e e e ee Attention. Nous ne disposons pas. on calcule le nombre d’op´rations ´l´mentaires a e ee (op´rations arithm´tiques. 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. Ceci permet de les conserver. le nombre n d’´l´ments ` trier pour un algorithme de tri. Plutˆt que de calculer le nombre d’op´rations associ´ ` chaque donn´e. pour ce cours. 167 . les ` e e e e donn´es (d’entr´es. instruction de contrˆle. etc. Les algorithmes peuvent. e Quel que soit la mesure choisie. Les mesures d’efficacit´ sont donc des fonctions des donn´es e e e e e d’entr´e. la longueur n d’une liste pour une inversion.) effectu´es pour traiter la e e o e donn´e d. il est clair que la qualit´ de l’algorithme n’est pas absolue e mais d´pend des donn´es d’entr´e. des e outils qui permettent d’exposer tr`s rigoureusement la notion d’algorithme et de complexit´ ale e gorithmique.). On souhaite g´n´ralement qu’il soit aussi rapide et n’utilise pas trop de e e e e m´moire. la taille n. etc. e e e e de les utiliser et de les modifier. ˆ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´. de sortie et interm´diaires) sont structur´es. m d’une ee a matrice pour le calcul du d´terminant. e e u 1 Une d´finition tr`s informelle des algorithmes e e La formalisation de la notion d’algorithme est assez tardive. lui aussi bien sp´cifi´.

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

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

int dest){ i f (n == 1){ // on sait le faire System. (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. Quelle est la complexit´ en moyenne ? e e n tableaux. Dans le cas contraire.1. 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.out. } Notons C(n) le nombre d’instructions ´l´mentaires pour calculer hanoi(n. static boolean trouve(int[] T. dest). System. u 3. o` λ est une constante. l’algorithme proc`de ` exactement n it´rations. int temp. ini. dest. hanoi(n-1. Parmi ceux-ci. Au total. i < T. return. LE COUT D’UN ALGORITHME de taille n + 1 de ini vers dest. 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. temp. temp. 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). ee Nous avons alors C(n + 1) ≤ 2C(n) + λ.170 ˆ ANNEXE A.println("deplace" + ini + " " + dest). public static void hanoi(int n. Consid´rons maintenant l’algorithme de c e e recherche ci-dessous qui cherche une valeur v dans T. // sinon recursion } hanoi(n . il suffit de d´placer une tour de taille n de ini vers temp. 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. temp). ini. int v) { for (int i = 0. nous avons une complexit´ moyenne de e a e e C= Or ∀x. int ini. En pratique. i++) i f (T[i] == v) return true.4 Recherche d’un nombre dans un tableau. dest). } La complexit´ dans le pire cas est clairement O(n).out. ini.println("deplace" + ini + " " + dest). (k − 1)n ne contiennent pas v et Remarquons que nous avons k dans ce cas. .length. return false. un e disque de ini versdest et finalement la tour de hauteur n detemp versdest. Tour de Hanoi est exponentielle.

. 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. 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.e. Il est toujours n´cessaire de proc´der ` des analyses exp´rimentales avant de e e a e choisir le “meilleur” algorithme. 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. i. a e .ˆ ´ ˆ ´ 4.

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

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

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

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

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

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

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

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

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

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. t==v : " + (t == v)) . String w = v + v . si et seulement si elles e e ee ee e pointent vers la mˆme zone de m´moire. Il existe une r´f´rence qui ne pointe nulle part null. le programme e e int [] t = {1. CONSTRUCTIONS DE BASE produit l’´tat m´moire simplifi´ suivant. 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. 3} . e e e e e Si l’´galit´ de deux scalaires ne pose aucun probl`me. ee int [] t = null . Autrement dit. 2. Les r´f´rences t et u sont ´gales parce qu’elles pointent ee e vers le mˆme objet. nous pouvons l’employer partout o` une ee u r´f´rence est attendue. int [] v = {1.out. 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 !=. e ee Cela peut se comprendre si on revient aux ´tats m´moire simplifi´s. Un essai par exemple de t[0] d´clenche une erreur ` l’ex´cution. } } . t==w : " + (t == w)) . 2. 3} . int [] t = {1. t==v : false. String v = "cou" . System.println(t[1]) . u[1] = 4 .out. 2. System. L’´galit´ physique donne parfois des r´sultats sure e e e e prenants. affiche t==u : true.out. Soit le programme Test simple suivant class Test { public static void main (String [] arg) { String t = "coucou" . Les r´f´rences t et v qui pointent vers des objets distincts sont distinctes.println("t==u : " + (t == u) + ". 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. e e e t u 1 2 3 v 1 2 3 On dit parfois que == est l’´galit´ physique. String u = "coucou" . 3} . int [] u = t . System. int [] u = t .3.println("t==u : " + (t == u) + ".

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

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

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

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

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

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

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

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

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

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

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

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

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

java:447) at java.parseInt(Integer.main(Test.4.println(i*i). nous e obtenons ceci : % java Square bonga Exception in thread "main" java.read(Square. } } Nous obtenons.main(Square. System.println(a[3]).java:48) at java. } } Le programme Square est cens´ afficher le carr´ de l’entier pass´ sur la ligne de commande. EXCEPTIONS 195 trois cases.parseInt(Integer.ArrayIndexOutOfBoundsException: 3 at Test.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). l’indice fautif. et surtout la ligne du programme qui a lanc´ l’exception.out.NumberFormatException.lang. 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. // Lire l’entier en base 10. e class Square { static int read(String s) { return Integer. jusqu’` la m´thode main.out.lang. 5}. L’affichage a e offre quelques bonus. le syst`me d’ex´cution ne peut plus rien faire de raisonnable et il fait ´chouer le e e e programme. 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. % java Test Exception in thread "main" java.forInputString(NumberFormatException. class Test { public static void main(String args[]) int [] a = {2. voir 6. Lancer une exception ne signifie pas du tout que le programme s’arrˆte imm´diatement. 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). Pour pr´ciser cet effet de propagation ee e e consid´rons l’exemple suivant d’un programme Square.1 } public static void main(String[] args) int i = read(args[0]).lang. System.Integer.NumberFormatException: For input string: "bonga" at java.1.lang.java:7) { { .Integer.java:497) at Square.java:3) at Square.lang.parseInt(s). 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.

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

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

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

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

for ( . try { Reader in = new BufferedReader (new FileReader(name1)) . la technique du tampon s’applique ´galement. qui emploie les entr´es-sorties bufferis´es.5. e Pour ce qui est d’un fichier ouvert en lecture.io. puis un BufferedWriter. in. Writer out = new BufferedWriter (new FileWriter name) . e e BufferedReader). Dans ce cas. Writer out = new BufferedWriter (new FileWriter (name2)) . e e import java. Voici une autre version Cp2 de la commande cp ´crite en e Java. On peut aussi vider le tampon plus directement en appelant m´thode flush() des flux bufferis´s. et qui a reste un Writer (resp.2.* .read() . la copie du e e . Toutefois. Les flux bufferis´s BufferedWriter et BufferedReader offrent aussi une vue des fichier e texte comme ´tant compos´s de lignes. e e e 5.close() .write(c) .println("Malaise : " + e. 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. System. les PrintStream de la biblioth`que sont tr`s inefficaces. Il existe une m´thode Newline pour ´crire une fin e e e e le ligne. } } } 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. La m´thode print de System. On utilise souvent BufferedReader e pour cette fonctionnalit´ de lecture ligne ` ligne. name2 = arg[1] . n’est e e e e ıne pas toujours tr`s pratique. Malheue e reusement. e avec les mˆmes b´n´fices en terme d’efficacit´. ´crire caract`re par caract`re (ou mˆme par chaˆ avec write(String str)). ce que fait la m´thode close() de fermeture des e flux. les lectures (read) se font dans le e e e e tampon.exit(2) .5 5. out. Par exemple. Reader). avant de fermer effectivement le flux. i f (c == -1) break .getMessage()) .out est bien plus commode. qui est rempli ` partir du fichier quand une demande de lecture trouve un tampon a vide.close() . qui se construit simplement ` partir d’un Writer (resp. ) { int c = in. et une m´thode readLine pour lire une ligne. Reader). 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.3). } out.err. } catch (IOException e) { System. 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. class Cp2 { public static void main(String [] arg) { String name1 = arg[0].´ 5. e a e Un flux bufferis´ en ´criture (resp. .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. ENTREES-SORTIES 203 le tampon avant de terminer le programme. lecture) est un objet de la classe BufferedWriter (resp.

} } La nouvelle m´thode sieve ´crit dans le PrintWriter out dont l’initialisation est rendue p´nible e e e par les exceptions possibles.println("Malaise: " + e.out. Par ailleurs. . System.txt") . } while (t[p]) . on aurait tr`s bien pu (( emballer )) la sortie standard dans un PrintWriter. sieve(n. 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. et non pas dans le e fichier primes.txt. k += p) t[k] = true . try { PrintWriter out = new PrintWriter ("primes. } catch (IOException e) { System. int p = 2 . out. } } L’application du crible d’Eratosth`ne est na¨ mais le programme est inefficace d’abord ` cause e ıve. k += p) t[k] = true . les PrintWriter sont bufferis´s par d´faut. int p = 2 .close() . PrintWriter out) throws IOException { boolean [] t = new boolean [n+1] . e par new PrintWriter(System.out). /* Chercher le prochain p premier */ do { p++ . e e static void sieve(int 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. On s’en rend compte en modifiant sieve pour utiliser un PrintWriter. do { p++ . out) .exit(2) . . ) { out.err. a de ses affichages. k <= n . } } 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.204 ANNEXE B.out. for ( . a static void sieve(int n) { boolean [] t = new boolean [n+1] . i f (p > n) return . Les objets de la classe PrintWriter sont des Writer (flux de caract`res) qui offrent les sorties e format´es. k <= n . for (int k = p+p . Pour ce qui est du temps d’ex´cution des essais rapides e . si l’on avait voulu ´crire dans la sortie standard. } while (t[p]) . Il y a plusieurs constructeurs des PrintWriter.println(p) .println(p) . for ( . // Afficher p premier /* Identifier les multiples de p */ for (int k = p+p . ) { System.getMessage()) . Par exemple. . . qui prennent divers e ee arguments. i f (p > n) return . 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.

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

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

ou comme on dit la Application Programmer Intere face Specification. En particulier.sun. 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.4). QUELQUES CLASSES DE BIBLIOTHEQUE // System. "UTF-8") . employer a e ce constructeur synth´tique revient ` : e a new PrintWriter (new OutputStreamWriter (new FileOutputStream (name))) . new PrintWriter (String name) ouvre direce tement le fichier name. Finalement.. Et bien. ` l’aide des constructeurs (( naturels )) a de ces classes. en th´orie nous savons lire et ´crire tout Unicode. On se rend vite compte de l’existence de ce tampon si on e ea oublie de fermer un FileWriter.3 Comme on e a tendance a tout simplifier on a parfois des surprises : par exemple. il y aura toujours d´codage et encodage.5. 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. La biblioth`que associ´e ` un e e e a langage de programmation est diffus´e avec le compilateur.` 6. 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. puisque le coˆt irr´ductible de la u e v´ritable sortie est d´j` amorti par un tampon. les FileWriter (voir 5.out (un PrintStream) est aussi un OutputStream Writer outChar = new OutputStreamWriter (System. Par exemple. Nous donnons donc des extraits de cette documentation assortis de quelques commentaires personnels. par exemple un Scanner ou un PrintWriter etc. C’est inutilement inefficace et mˆme dangereux certains d´codages pouvant parfois ´chouer. il faut encore pouvoir e e entrer ces caract`res au clavier et les visualiser dans une fenˆtre. The resulting bytes are accumulated in a buffer before being written to the underlying output stream. 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.out. 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. syst`me d’ex´cution etc. Cette section entend surtout vous aider ` prendre la bonne habitude a de consulter la documentation du langage que vous utilisez. 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 . 4 http://java. 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. la version web du polycopi´ comporte des liens vers la documentation en ligne. il aurait e e e mieux valu employer les flux de byte.com/j2se/1. Si on en croit la documentation : Each invocation of a write() method causes the encoding converter to be invoked on the given character(s). 207 Notons qu’une fois obtenu un Reader ou un Writer nous pouvons fabriquer ce dont nous avons besoin.0/docs/api 3 . 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. En pratique. mais c’est une autre histoire e e qui ne regarde plus Java. 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. e On objectera que la biblioth`que est ´norme. mais ` quelques optimisations internes toujours possibles pr`s. qui prennent un Reader ou un Writer en argument. mais ce n’est qu’une apparence.3) poss`dent en fait d´j` un tampon. 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).

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

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") .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. que les Reader ne poss`dent pas. 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. il existe diverses sous-classes (voir 2.3) de Reader qui permettent de construire concr`tement des objets. 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. Si on ne ferme pas les e e e fluxs dont on ne sert plus. InputStreamReader (lecteur sur un flux d’octets) et FileReader (lecteur sur un fichier). Une fois la source construite (un objet de la classe Random). public BufferedReader(Reader in) Construit un flux d’entr´e bufferis´ ` partir d’un lecteur quelconque. 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.in) . 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). La m´thode fonctionne pour les trois e e conventions de fin de ligne (’\n’. afin d’am´liorer les performances (voir 5. e Le constructeur le plus simple prend un Reader en argument. Quand la fin d’un flux est atteinte. 6.3 6. En revanche. Il s’agit par entre autres e des classes StringReader (lecteur sur une chaˆ ıne).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. afin de g´rer le tampon interne.3. en raison de l’´puisement de ces ressources. Mais e e un BufferedReader offre quelques fonctionnalit´s en plus de celles des Reader. e 6.4). 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´. tampons internes). MORCEAUX DE JAVA pouvoir identifier la fin du flux facilement. et un Reader sur l’entr´e standard par e Reader in = new InputStreamReader (System. ’\r’ ou ’\r’ suivi de ’\n’) et le ou les caract`res (( fin de ligne )) ne sont pas renvoy´s.1 Package java.212 ANNEXE B. e e e e e La lecture d’une ligne se fait par une nouvelle m´thode. qui peuvent se faire passer pour des Reader. mais aussi un aspect chaotique pas ´vident ` sp´cifier e e a e pr´cis´ment). diverses m´thodes e e e . e e public String readLine() throws IOException Renvoie une ligne de texte ou null en fin de flux. e e public int read() throws IOException Cette m´thode n’est pas h´rit´e mais red´finie.2. 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. il faut fermer le flux. on risque de ne pas pouvoir en ouvrir d’autres.

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

214 ANNEXE B. . Cette expression n’´choue jamais. . alors la seconde condition n’est pas e e e ´valu´e. Plus pr´cis´ment. cas si xs vaut null alors l’acc`s xs. . conjonction) vaut true (resp.next n’est pas tent´. 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 . . pour savoir si e la liste xs contient au moins deux ´l´ments. false). 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. on peut ´crire ee e . et la disjonction (resp. 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. alors e e e e e2 n’est pas ´valu´e. e1 && e2 ) e1 vaut true (resp. C’est un idiome e e de profiter de ce comportement pour ´crire des conditions concises. Par exemple. xs != null && xs. false ) . MORCEAUX DE JAVA 7.next != null .3 Connecteurs ( paresseux ) ( ) Les op´rateurs || et && sont respectivement la disjonction (ou logique) et la conjonction (et e logique).

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

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

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

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

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