You are on page 1of 252

Introduction ` linformatique a

Ecole polytechnique

Franois Morain c

11 avril 2011

Table des mati`res e


I Introduction ` la programmation a
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11
13 13 13 14 15 15 16 16 18 18 18 19 21 21 21 22 22 22 23 23 24 24 26 27 28 28 28 28 31 31 32 32 33 34

1 Les premiers pas en Java 1.1 Le premier programme . . . . . . . . . . . 1.1.1 Ecriture et excution . . . . . . . . e 1.1.2 Analyse de ce programme . . . . . 1.2 Faire des calculs simples . . . . . . . . . . 1.3 Types primitifs . . . . . . . . . . . . . . . 1.4 Dclaration des variables . . . . . . . . . . e 1.5 Aectation . . . . . . . . . . . . . . . . . 1.6 Oprations . . . . . . . . . . . . . . . . . e 1.6.1 R`gles dvaluation . . . . . . . . . e e 1.6.2 Incrmentation et dcrmentation e e e 1.7 Mthodes . . . . . . . . . . . . . . . . . . e 2 Suite dinstructions 2.1 Expressions boolennes . . . . . . . e 2.1.1 Oprateurs de comparaisons e 2.1.2 Connecteurs . . . . . . . . . 2.2 Instructions conditionnelles . . . . 2.2.1 If-else . . . . . . . . . . . . 2.2.2 Forme compacte . . . . . . 2.2.3 Aiguillage . . . . . . . . . . 2.3 Itrations . . . . . . . . . . . . . . e 2.3.1 Boucles pour (for) . . . . 2.3.2 Itrations tant que . . . . . e 2.3.3 Itrations rpter tant que . e e e 2.4 Terminaison des programmes . . . 2.5 Instructions de rupture de contrle o 2.6 Exemples . . . . . . . . . . . . . . 2.6.1 Mthode de Newton . . . . e 3 Mthodes : thorie et pratique e e 3.1 Pourquoi crire des mthodes e e 3.2 Comment crire des mthodes e e 3.2.1 Syntaxe . . . . . . . . 3.2.2 Le type spcial void . e 3.2.3 La surcharge . . . . . . . . . . . . . . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 3.3 3.4

` TABLE DES MATIERES Visibilit des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Quelques conseils pour crire un (petit) programme . . . . . . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 36 39 39 40 43 44 45 45 47 49 50 55 57 57 57 58 59 60 60 61 61 62 62 63 63 63 65 66 67 67 69 69 71 72 74 75 75 76 79 79 80 81 81 81

4 Tableaux 4.1 Dclaration, construction, initialisation . . . e 4.2 Reprsentation en mmoire et consquences e e e 4.3 Tableaux ` plusieurs dimensions, matrices . a 4.4 Les tableaux comme arguments de fonction 4.5 Exemples dutilisation des tableaux . . . . . 4.5.1 Algorithmique des tableaux . . . . . 4.5.2 Un peu dalg`bre linaire . . . . . . e e 4.5.3 Le crible dEratosthene . . . . . . . 4.5.4 Jouons ` la bataille range . . . . . a e 4.5.5 Pile . . . . . . . . . . . . . . . . . .

5 Classes, objets 5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Dclaration et cration . . . . . . . . . . . . e e 5.1.2 Objet et rfrence . . . . . . . . . . . . . . ee 5.1.3 Constructeurs . . . . . . . . . . . . . . . . . 5.2 Autres composants dune classe . . . . . . . . . . . 5.2.1 Mthodes de classe et mthodes dobjet . . e e 5.2.2 Passage par rfrence . . . . . . . . . . . . ee 5.2.3 Variables de classe . . . . . . . . . . . . . . 5.2.4 Utiliser plusieurs classes . . . . . . . . . . . 5.3 Autre exemple de classe . . . . . . . . . . . . . . . 5.4 Public et private . . . . . . . . . . . . . . . . . . . 5.5 Un exemple de classe prdnie : la classe String e e 5.5.1 Proprits . . . . . . . . . . . . . . . . . . . ee 5.5.2 Arguments de main . . . . . . . . . . . . . 5.6 Pour aller plus loin . . . . . . . . . . . . . . . . . . 6 Rcursivit e e 6.1 Premiers exemples . . . . . . . . . . . . . . . 6.2 Des exemples moins lmentaires . . . . . . . ee 6.2.1 Ecriture binaire des entiers . . . . . . 6.2.2 Les tours de Hanoi . . . . . . . . . . . 6.3 Un pi`ge subtil : les nombres de Fibonacci . . e 6.4 Fonctions mutuellement rcursives . . . . . . e 6.4.1 Pair et impair sont dans un bateau . . 6.4.2 Dveloppement du sinus et du cosinus e 6.5 Le probl`me de la terminaison . . . . . . . . . e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7 Introduction ` la complexit des algorithmes a e 7.1 Complexit des algorithmes . . . . . . . . . . . e 7.2 Calculs lmentaires de complexit . . . . . . . ee e 7.3 Quelques algorithmes sur les tableaux . . . . . 7.3.1 Recherche du plus petit lment . . . . ee 7.3.2 Recherche dichotomique . . . . . . . . .

` TABLE DES MATIERES 7.3.3 Recherche simultane du maximum et du minimum e Diviser pour rsoudre . . . . . . . . . . . . . . . . . . . . . e 7.4.1 Recherche dune racine par dichotomie . . . . . . . . 7.4.2 Exponentielle binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5 82 84 84 85

7.4

II

Structures de donnes classiques e


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87
89 90 90 91 92 93 93 94 95 96 96 97 97 98 101 101 101 102 102 103 103 104 104 104 108 110 117 117 119 119 120 121 125 127 127 127 128 129 129

8 Listes cha ees n 8.1 Oprations lmentaires sur les listes . . . . . . e ee 8.1.1 Cration . . . . . . . . . . . . . . . . . . e 8.1.2 Achage . . . . . . . . . . . . . . . . . 8.1.3 Longueur . . . . . . . . . . . . . . . . . 8.1.4 Le i-`me lment . . . . . . . . . . . . . e ee 8.1.5 Ajouter des lments en queue de liste . ee 8.1.6 Copier une liste . . . . . . . . . . . . . . 8.1.7 Suppression de la premi`re occurrence . e 8.2 Interlude : tableau ou liste ? . . . . . . . . . . . 8.3 Partages . . . . . . . . . . . . . . . . . . . . . . 8.3.1 Insertion dans une liste trie . . . . . . e 8.3.2 Inverser les `ches . . . . . . . . . . . . e 8.4 Exemple de gestion de la mmoire au plus juste e 9 Arbres 9.1 Arbres binaires . . . . . . . . . . . 9.1.1 Reprsentation en machine e 9.1.2 Complexit . . . . . . . . . e 9.1.3 Les trois parcours classiques 9.2 Arbres gnraux . . . . . . . . . . e e 9.2.1 Dnitions . . . . . . . . . e 9.2.2 Reprsentation en machine e 9.3 Exemples dutilisation . . . . . . . 9.3.1 Expressions arithmtiques . e 9.3.2 Arbres binaires de recherche 9.3.3 Les tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10 Graphes 10.1 Dnitions . . . . . . . . . . . . . . . . . e 10.2 Reprsentation en machine . . . . . . . e 10.2.1 Reprsentation par une matrice . e 10.2.2 Reprsentation par un tableau de e 10.3 Recherche des composantes connexes . . 10.4 Conclusion . . . . . . . . . . . . . . . .

. . . . . . . . . . . . listes . . . . . . . . . . . . . . . . . .

11 Ranger linformation. . . pour la retrouver 11.1 Recherche en table . . . . . . . . . . . . . . 11.1.1 Recherche linaire . . . . . . . . . . e 11.1.2 Recherche dichotomique . . . . . . . 11.1.3 Utilisation dindex . . . . . . . . . . 11.2 Trier . . . . . . . . . . . . . . . . . . . . . .

` TABLE DES MATIERES 11.2.1 Tris lmentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 ee 11.2.2 Un tri rapide : le tri par fusion . . . . . . . . . . . . . . . . . . . 133 11.3 Hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

III

Introduction au gnie logiciel e


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

141
143 143 143 143 145 145 150 150 150 150 151 158 159 159 163 163 163 164 166 167 168 173 173 173 174 174 175 175 177 177 177 178 178 178 184 184 184 185

12 Comment crire un programme e 12.1 Pourquoi du gnie logiciel ? . . . . . . . . . e 12.2 Principes gnraux . . . . . . . . . . . . . . e e 12.2.1 La cha de production logicielle . . ne 12.2.2 Architecture dtaille . . . . . . . . e e 12.2.3 Aspects organisationnels . . . . . . . 12.2.4 En guise de conclusion provisoire. . . 12.3 Un exemple dtaill . . . . . . . . . . . . . e e 12.3.1 Le probl`me . . . . . . . . . . . . . . e 12.3.2 Architecture du programme . . . . . 12.3.3 Programmation . . . . . . . . . . . . 12.3.4 Tests exhaustifs du programme . . . 12.3.5 Est-ce tout ? . . . . . . . . . . . . . 12.3.6 Calendrier et formule de Zeller . . . 13 Introduction au gnie logiciel en Java e 13.1 Modularit . . . . . . . . . . . . . . . e 13.2 Les interfaces de Java . . . . . . . . . 13.2.1 Piles . . . . . . . . . . . . . . . 13.2.2 Files dattente . . . . . . . . . 13.2.3 Les gnriques . . . . . . . . . e e 13.3 Retour au calcul du jour de la semaine

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

14 Modlisation de linformation e 14.1 Modlisation et ralisation . . . . . . . . . . . . . e e 14.1.1 Motivation . . . . . . . . . . . . . . . . . 14.1.2 Exemple : les donnes . . . . . . . . . . . e 14.2 Conteneurs, collections et ensembles . . . . . . . 14.2.1 Collections squentielles . . . . . . . . . . e 14.2.2 Collections ordonnes . . . . . . . . . . . e 14.3 Associations . . . . . . . . . . . . . . . . . . . . . 14.4 Information hirarchique . . . . . . . . . . . . . . e 14.4.1 Exemple : arbre gnalogique . . . . . . . e e 14.4.2 Autres exemples . . . . . . . . . . . . . . 14.5 Quand les relations sont elles-mmes des donnes e e 14.5.1 Un exemple : un rseau social . . . . . . . e 14.6 Automates . . . . . . . . . . . . . . . . . . . . . 14.6.1 Lexemple de la machine ` caf . . . . . . a e 14.6.2 Dnitions . . . . . . . . . . . . . . . . . e 14.6.3 Reprsentations dautomate dterministe e e

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

` TABLE DES MATIERES

IV

Problmatiques classiques en informatique e


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

187
189 189 194 194 195 198 203 205 205 206 207 207 207 209 209 210 213 213 213 214 214 216 217 217 217 218 220 220 220 226 226 227 228

15 Recherche exhaustive 15.1 Rechercher dans du texte . . . . . . . . 15.2 Le probl`me du sac-`-dos . . . . . . . . e a 15.2.1 Premi`res solutions . . . . . . . . e 15.2.2 Deuxi`me approche . . . . . . . e 15.2.3 Code de Gray* . . . . . . . . . . 15.2.4 Retour arri`re (backtrack) . . . . e 15.3 Permutations . . . . . . . . . . . . . . . 15.3.1 Fabrication des permutations . . 15.3.2 Enumration des permutations . e 15.4 Les n reines . . . . . . . . . . . . . . . . 15.4.1 Prlude : les n tours . . . . . . . e 15.4.2 Des reines sur un chiquier . . . e 15.5 Les ordinateurs jouent aux checs . . . . e 15.5.1 Principes des programmes de jeu 15.5.2 Retour aux checs . . . . . . . . e

16 Polynmes et transforme de Fourier o e 16.1 La classe Polynome . . . . . . . . . . . . . . . . . . 16.1.1 Dnition de la classe . . . . . . . . . . . . . e 16.1.2 Cration, achage . . . . . . . . . . . . . . . e 16.1.3 Prdicats . . . . . . . . . . . . . . . . . . . . e 16.1.4 Premiers tests . . . . . . . . . . . . . . . . . . 16.2 Premi`res fonctions . . . . . . . . . . . . . . . . . . . e 16.2.1 Drivation . . . . . . . . . . . . . . . . . . . . e 16.2.2 Evaluation ; schma de Horner . . . . . . . . e 16.3 Addition, soustraction . . . . . . . . . . . . . . . . . 16.4 Deux algorithmes de multiplication . . . . . . . . . . 16.4.1 Multiplication na ve . . . . . . . . . . . . . . 16.4.2 Lalgorithme de Karatsuba . . . . . . . . . . 16.5 Multiplication ` laide de la transforme de Fourier* a e 16.5.1 Transforme de Fourier . . . . . . . . . . . . e 16.5.2 Application ` la multiplication de polynmes a o 16.5.3 Transforme rapide . . . . . . . . . . . . . . . e

Annexes
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

231
233 233 234 234 235 236 237 238 238

A Complments e A.1 Exceptions . . . . . . . . . . . . A.2 La classe MacLib . . . . . . . . . A.2.1 Fonctions lmentaires . . ee A.2.2 Rectangles . . . . . . . . A.2.3 La classe Maclib . . . . A.2.4 Jeu de balle . . . . . . . . A.3 La classe TC . . . . . . . . . . . . A.3.1 Fonctionnalits, exemples e

` TABLE DES MATIERES A.3.2 La classe Efichier . . . . . . . . . . . . . . . . . . . . . . . . . 241

Table des gures

250

Introduction
Les audacieux font fortune ` Java. a Ce polycopi sadresse ` des l`ves de premi`re anne ayant peu ou pas de connaise a ee e e sances en informatique. Une partie de ce cours constitue une introduction gnrale ` e e a linformatique, aux logiciels, matriels, environnements informatiques et ` la science e a sous-jacente. Une autre partie consiste ` tablir les bases de la programmation et de lalgoa e rithmique, en tudiant un langage. On introduit des structures de donnes simples : e e scalaires, cha nes de caract`res, tableaux, et des structures de contrle lmentaires e o ee comme litration, la rcursivit. e e e Nous avons choisi Java pour cette introduction ` la programmation car cest un a langage typ assez rpandu qui permet de sinitier aux diverses constructions prsentes e e e dans la plupart des langages de programmation modernes. Il est prsent de plus en plus e dans les tlphones portables (avec Android). ee ` A ces cours sont coupls des sances de travaux dirigs et pratiques qui sont beaue e e coup plus quun complment au cours, puisque cest en crivant des programmes que e e lon apprend linformatique. Comment lire ce polycopi ? La premi`re partie dcrit les principaux traits dun e e e langage de programmation (ici Java), ainsi que les principes gnraux de la programe e mation simple. Une deuxi`me partie prsente quelques grandes classes de probl`mes e e e que les ordinateurs traitent plutt bien. o Un passage indiqu par une toile (*) peut tre saut en premi`re lecture. e e e e e

10 Remerciements pour la version 3.0 :

` TABLE DES MATIERES

P. Chassignet ma aid a rdiger le nouveau chapitre 14, quil en soit sinc`rement ree e e Duris pour sa relecture attentive de la partie gnie logicielle (nouvelle merci. Merci ` E. e a e elle aussi). Enn, rien nest possible sans des relecteurs : L. Castelli Aleardi, Y. Ponty, S. Redon, O. Serre. Remerciements pour les versions 20012010 : G. Guillerm ma aid pour le chapitre Internet, J. Marchand pour le courrier e lectronique, T. Besanon pour NFS. Quils en soient remerci ici, ainsi que E. Thom e c e e pour ses coups de main, V. Mnissier-Morain pour son aide. Je remercie galement les e e relecteurs de la prsente version : T. Clausen, E. Duris, C. Gwiggner, J.-R. Reinhard ; e E. Waller, ce dernier tant galement le bta-testeur de mes transparents damphis, ce e e e qui les a rendus dautant plus justes. Remerciements pour la version initiale (2000) : Je remercie chaleureusement Jean-Jacques Lvy et Robert Cori pour mavoir permis de rutiliser des parties de e e leurs polycopis anciens ou nouveaux. e
A Le polycopi a t crit avec L TEX, il est consultable ` ladresse : e eee a

http://www.enseignement.polytechnique.fr/informatique/INF311

Les erreurs seront corriges d`s quelles me seront signales et les mises ` jour ventuelles e e e a e seront eectues sur la version mise sur le web. e

Polycopi, version 3.0, 11 avril 2011 e

Premi`re partie e

Introduction ` la programmation a

11

Chapitre 1

Les premiers pas en Java


Dans ce chapitre on donne quelques lments simples de la programmation avec le ee langage Java : types, variables, aectations, fonctions. Ce sont des traits communs ` a tous les langages de programmation.

1.1
1.1.1

Le premier programme
Ecriture et excution e

Commenons par un exemple simple de programme. Cest un classique, il sagit c simplement dacher Bonjour ! ` lcran. a e // Voici mon premier programme public class Premier{ public static void main(String[] args){ System.out.println("Bonjour !"); return; } } Pour excuter ce programme il faut commencer par le copier dans un chier. Pour e cela on utilise un diteur de texte (par exemple nedit) pour crer un chier de nom e e Premier.java (le mme nom que celui qui suit class). Ce chier doit contenir le e texte du programme. Apr`s avoir tap le texte, on doit le traduire (les informaticiens e e disent compiler) dans un langage que comprend lordinateur. Cette compilation se fait a ` laide de la commande1 unix% javac Premier.java Ceci a pour eet de faire construire par le compilateur un chier Premier.class, que la machine virtuelle de Java rendra comprhensible pour lordinateur : e unix% java Premier
1

Une ligne commenant par unix% indique une commande tape en Unix. c e

13

14 On voit sacher : Bonjour !

CHAPITRE 1. LES PREMIERS PAS EN JAVA

1.1.2

Analyse de ce programme

Un langage de programmation est comme un langage humain. Il y a un ensemble de lettres avec lesquelles on forme des mots. Les mots forment des phrases, les phrases des paragraphes, ceux-ci forment des chapitres qui rassembls donnent naissance ` un e a livre. Lalphabet de Java est peu ou prou lalphabet que nous connaissons, avec des lettres, des chires, quelques signes de ponctuation. Les mots seront les mots-clefs du langage (comme class, public, etc.), ou formeront les noms des variables que nous utiliserons plus loin. Les phrases seront pour nous des instructions, les paragraphes des fonctions (appeles mthodes dans la terminologie des langages ` objets). Les chapitres e e a seront les classes, les livres des programmes que nous pourrons excuter et utiliser. e Le premier chapitre dun livre est lamorce du livre et ne peut gnralement tre e e e saut. En Java, un programme dbute toujours ` partir dune mthode spciale, appele e e a e e e main et dont la syntaxe immuable est : public static void main(String[] args) Nous verrons plus loin ce que veulent dire les mots magiques public, static et void, args contient quant ` lui des arguments quon peut passer au programme. Reprenons a la mthode main : e public static void main(String[] args){ System.out.println("Bonjour !"); return; } Les accolades { et } servent ` constituer un bloc dinstructions ; elles doivent ena glober les instructions dune mthode, de mme quune paire daccolades doit englober e e lensemble des mthodes dune classe. e Notons quen Java les instructions se terminent toutes par un ; (point-virgule). Ainsi, dans la suite le symbole I signiera soit une instruction (qui se termine donc par ;) soit une suite dinstructions (chacune nissant par ;) places entre accolades. e La mthode eectuant le travail est la mthode System.out.println qui appare e tient ` une classe prdnie, la classe System. En Java, les classes peuvent sappeler a e e les unes les autres, ce qui permet une approche modulaire de la programmation : on na pas ` rcrire tout le temps la mme chose. a e e Notons que nous avons crit les instructions de chaque ligne en respectant un e dcalage bien prcis (on parle dindentation). La mthode System.out.println e e e tant excute ` lintrieur de la mthode main, elle est dcale de plusieurs blancs e e e a e e e e (ici 4) sur la droite. Lindentation permet de bien structurer ses programmes, elle est systmatiquement utilise partout. e e La derni`re instruction prsente dans la mthode main est linstruction return; e e e que nous comprendrons pour le moment comme voulant dire : rendons la main ` lutia lisateur qui nous a lanc. Nous en prciserons le sens ` la section 1.7. e e a La derni`re chose ` dire sur ce petit programme est quil contient un commentaire, e a repr par // et se terminant a la n de la ligne. Les commentaires ne sont utiles ee `

1.2. FAIRE DES CALCULS SIMPLES

15

qu` des lecteurs (humains) du texte du programme, ils nauront aucun eet sur la a compilation ou lexcution. Ils sont tr`s utiles pour comprendre le programme. e e

1.2

Faire des calculs simples

On peut se servir de Java pour raliser les oprations dune calculette lmentaire : e e ee on aecte la valeur dune expression ` une variable et on demande ensuite lachage a de la valeur de la variable en question. Bien entendu, un langage de programmation nest pas fait uniquement pour cela, toutefois cela nous donne quelques exemples de programmes simples ; nous passerons plus tard ` des programmes plus complexes. a // Voici mon deuxi`me programme e public class PremierCalcul{ public static void main(String[] args){ int a; a = 5 * 3; System.out.println(a); a = 287 % 7; System.out.println(a); return; } } Dans ce programme on voit appara une variable de nom a qui est dclare au tre e e dbut. Comme les valeurs quelle prend sont des entiers elle est dite de type entier. Le e mot int2 qui prc`de le nom de la variable est une dclaration de type. Il indique que la e e e variable est de type entier et ne prendra donc que des valeurs enti`res lors de lexcution e e du programme. Par la suite, on lui aecte deux fois une valeur qui est ensuite ache. e Les rsultats achs seront 15 et 0. Dans lopration a % b, le symbole % dsigne e e e e lopration modulo qui est le reste de la division euclidienne de a par b (quand a et b e sont positifs). Insistons un peu sur la faon dont le programme est excut par lordinateur. Celuic e e ci lit les instructions du programme une ` une en commenant par la mthode main, et a c e les traite dans lordre o` elles apparaissent. Il sarrte d`s quil rencontre linstruction u e e return;, qui est gnralement la derni`re prsente dans une mthode. Nous reviene e e e e drons sur le mode de traitement des instructions quand nous introduirons de nouvelles constructions (itration, rcursion). e e

1.3

Types primitifs

Un type en programmation prcise lensemble des valeurs que peut prendre une e variable ; les oprations que lon peut eectuer sur une variable dpendent de son type. e e Le type des variables que lon utilise dans un programme Java doit tre dclar. e e e Parmi les types possibles, les plus simples sont les types primitifs. Il y a peu de types primitifs : les entiers, les rels, les caract`res et les boolens. e e e
2

Une abrviation de langlais integer, le g tant prononc comme un j franais. e e e c

16

CHAPITRE 1. LES PREMIERS PAS EN JAVA

Les principaux types entiers sont int et long, le premier utilise 32 bits (un bit vaut 0 ou 1, cest un chire binaire) pour reprsenter un nombre ; sachant que le premier bit e est rserv au signe, un int fait rfrence ` un entier de lintervalle [231 , 231 1]. Si e e ee a lors dun calcul, un nombre dpasse cette valeur le rsulat obtenu nest pas utilisable. e e Le type long permet davoir des mots de 64 bits (entiers de lintervalle [263 , 263 1]) et on peut donc travailler sur des entiers plus grands. Il y a aussi les types byte et short qui permettent dutiliser des mots de 8 et 16 bits. Les oprations sur les int e sont toutes les oprations arithmtiques classiques : les oprations de comparaison : e e e gal (==), dirent de (!=), plus petit (<), plus grand (>) et les oprations de calcul e e e comme addition (+), soustraction (-), multiplication (*), division (/), reste (%). Dans ce dernier cas, prcisons que a/b calcule le quotient de la division euclidienne de a par e b et que a % b en calcule le reste. Par suite int q = 2/3; contient le quotient de la division euclidienne de 2 par 3, cest-`-dire 0. a Les types rels (en fait, des nombres dont le dveloppement binaire est ni) sont e e float et double, le premier se contente dune prcision dite simple, le second donne e la possibilit dune plus grande prcision, on dit que lon a une double prcision. e e e Les caract`res sont dclars par le type char au standard Unicode. Ils sont cods e e e e sur 16 bits et permettent de reprsenter toutes les langues de la plan`te (les caract`res e e e habituels des claviers des langues europennes se codent uniquement sur 8 bits). Le e standard Unicode respecte lordre alphabtique. Ainsi le code de a est infrieur ` celui e e a de d, et celui de A ` celui de D. a Le type des boolens est boolean et ses deux valeurs possibles sont true et false. e Les oprations sont et, ou, et non ; elles se notent respectivement &&, ||, !. Si a et e b sont deux boolens, le rsultat de a && b est true si et seulement si a et b sont tous e e deux gaux ` true. Celui de a || b est true si et seulement si lun de a ou b est e a gal ` true. Enn !a est true quand a est false et rciproquement. Les boolens e a e e sont utiliss dans les conditions dcrites au chapitre suivant. e e

1.4

Dclaration des variables e

La dclaration du type des variables est obligatoire en Java, mais elle peut se faire e a ` lintrieur dune mthode et pas ncessairement au dbut de celle-ci. Une dclaration e e e e e se prsente sous la forme dun nom de type suivi soit dun nom de variable, soit dune e suite de noms de variables spars par des virgules. En voici quelques exemples : e e int a, b, c; float x; char ch; boolean u, v;

1.5

Aectation

On a vu quune variable a un nom et un type. Lopration la plus courante sur les e variables est laectation dune valeur. Elle scrit : e

1.5. AFFECTATION

17

double
T

float
T

long
T

int '
T

char

short
T

byte

Fig. 1.1 Coercions implicites. x = E; o` E est une expression qui peut contenir des constantes et des variables. Lors dune u aectation, lexpression E est value et le rsultat de son valuation est aect ` la e e e e e a variable x. Lorsque lexpression E contient des variables leur contenu est gal ` la e a derni`re valeur qui leur a t aecte. e ee e Par exemple, laectation x = x + a; consiste ` augmenter la valeur de x de la quantit a. Par exemple a e x = 12; x = x + 5; donnera ` x la valeur 17. a Pour une aectation x = E; le type de lexpression E et celui de la variable x doivent tre compatibles. Dans un tr`s e e petit nombre de cas cette exigence nest pas applique, il sagit alors des conversions e implicites de types. Les conversions implicites suivent la gure 1.1. Pour toute opration, e on convertit toujours au plus petit commun majorant des types des oprandes. Des e conversions explicites sont aussi possibles, et recommandes dans le doute. On peut les e faire par lopration dite de coercion (cast) suivante e x = (nom-type) E;

18

CHAPITRE 1. LES PREMIERS PAS EN JAVA

Lexpression E est alors convertie dans le type indiqu entre parenth`ses devant lexprese e sion. Loprateur = daectation est un oprateur comme les autres dans les expressions. e e Il subit donc les mmes lois de conversion. Toutefois, il se distingue des autres oprations e e par le type du rsultat. Pour un oprateur ordinaire, le type du rsultat est le type e e e commun obtenu par conversion des deux oprandes. Pour une aectation, le type du e rsultat est le type de lexpression ` gauche de laectation. Il faut donc faire une e a conversion explicite sur lexpression de droite pour que le rsultat soit cohrent avec le e e type de lexpression de gauche. Cest le cas de lexemple int x = (int)12.7; o` le ottant 12.7 sera converti en entier, soit 12. u

1.6

Oprations e

La plupart des oprations arithmtiques courantes sont disponibles en Java, ainsi e e que les oprations sur les boolens (voir chapitre suivant). Ces oprations ont un ordre e e e de priorit correspondant aux conventions usuelles. e

1.6.1

R`gles dvaluation e e

Les principales oprations sont +,-,*,/, % pour laddition, soustraction, multie plication, division et le reste de la division (modulo). Il y a des r`gles de priorit, ainsi e e lopration de multiplication a une plus grande priorit que laddition, cela signie que e e les multiplications sont faites avant les additions. La prsence de parenth`ses permet e e de mieux contrler le rsultat. Par exemple 3 + 5 * 6 est valu ` 33 ; par contre o e e e a e ea e (3 + 5) * 6 est valu ` 48. Une expression a toujours un type et le rsultat de son valuation est une valeur ayant ce type. e On utilise souvent des raccourcis pour les instructions du type x = x + a; quon a tendance ` crire de faon quivalente, mais plus compacte : ae c e x += a;

1.6.2

Incrmentation et dcrmentation e e e

Soit i une variable de type int. On peut lincrmenter, cest-`-dire lui additionner e a 1 ` laide de linstruction : a i = i + 1; Cest une instruction tellement frquente (particuli`rement dans lcriture des boucles, e e e cf. chapitre suivant), quil existe deux raccourcis : i++ et ++i. Dans le premier cas, il sagit dune post-incrmentation, dans le second dune pr-incrmentation. Expliquons e e e la dirence entre les deux. Le code e i = 2; j = i++; donne la valeur 3 ` i et 2 ` j, car le code est quivalent ` : a a e a

1.7. METHODES i = 2; j = i; i = i + 1; on incrmente en tout dernier lieu. Par contre : e i = 2; j = ++i; est quivalent quant ` lui ` : e a a i = 2; i = i + 1; j = i;

19

et donc on termine avec i=3 et j=3. e Il existe aussi des raccourcis pour la dcrmentation : i = i-1 peut scrire aussi e e i-- ou --i avec les mmes r`gles que pour ++. e e

1.7

Mthodes e

Le programme suivant, qui calcule la circonfrence dun cercle en mthode de son e e rayon, contient deux mthodes, main, que nous avons dj` rencontre, ainsi quune e ea e nouvelle mthode, circonference, qui prend en argument un rel r et retourne la e e valeur de la circonfrence, qui est aussi un rel : e e // Calcul de circonfrence e public class Cercle{ static float pi = (float)Math.PI; public static float circonference(float r){ return 2. * pi * r; } public static void main(String[] args){ float c = circonference(1.5); e System.out.print("Circonfrence: System.out.println(c); return; } } De faon gnrale, une mthode peut avoir plusieurs arguments, qui peuvent tre de c e e e e types dirents et retourne une valeur (dont le type doit tre aussi prcis). Certaines e e e e mthodes ne retournent aucune valeur. Elles sont alors dclares de type void. Cest le e e e cas particulier de la mthode main de notre exemple. Pour bien indiquer dans ce cas le e point o` la mthode renvoie la main ` lappelant, nous utiliserons souvent un return; u e a explicite, qui est en fait optionnel. Il y a aussi des cas o` il ny a pas de param`tres u e lorsque la mthode eectue toujours les mmes oprations sur les mmes valeurs. e e e e ");

20

CHAPITRE 1. LES PREMIERS PAS EN JAVA

Len-tte dune mthode dcrit le type du rsultat dabord puis les types des pae e e e ram`tres qui gurent entre parenth`ses. e e Les programmes que lon a vu ci-dessus contiennent une seule mthode appele e e main. Lorsquon eectue la commande java Nom-classe, cest la mthode main se e trouvant dans cette classe qui est excute en premier. e e Une mthode peut appeler une autre mthode ou tre appele par une autre me e e e e thode, il faut alors donner des arguments aux param`tres dappel. e Ce programme contient deux mthodes dans une mme classe, la premi`re mthode e e e e a un param`tre r et utilise la constante PI qui se trouve dans la classe Math, cette e constante est de type double il faut donc la convertir au type float pour aecter sa valeur ` un nombre de ce type. a Le rsultat est fourni, on dit plutt retourn ` lappelant par return. Lappelant e o ea est ici la mthode main qui apr`s avoir eectu lappel, ache le rsultat. e e e e

Chapitre 2

Suite dinstructions
Dans ce chapitre on sintresse ` deux types dinstructions : les instructions condie a tionnelles, qui permettent deectuer une opration dans le cas o` une certaine condition e u est satisfaite et les itrations qui donnent la possibilit de rpter plusieurs fois la mme e e e e e instruction (pour des valeurs direntes des variables !). e

2.1

Expressions boolennes e

Le point commun aux diverses instructions dcrites ci-dessous est quelles utilisent e des expressions boolennes, cest-`-dire dont lvaluation donne lune des deux valeurs e a e true ou false.

2.1.1

Oprateurs de comparaisons e

Les oprateurs boolens les plus simples sont e e == gal e != dirent e < plus petit > plus grand <= plus petit ou gal e >= plus grand ou gal e

Le rsultat dune comparaison sur des variables de type primitif : e a == b est gal ` true si lvaluation de la variable a et de la variable b donnent le mme e a e e rsultat, il est gal ` false sinon. Par exemple, (5-2) == 3 a pour valeur true, e e a mais 22/7 == 3.14159 a pour valeur false. Remarque : Attention ` ne pas crire a = b qui est une aectation et pas une coma e paraison. Loprateur != est loppos de ==, ainsi a != b prend la valeur true si lvae e e luation de a et de b donne des valeurs direntes. e Les oprateurs de comparaison <, >, <=, >= ont des signications videntes lorsquil e e sagit de comparer des nombres. Noter quils peuvent aussi servir ` comparer des caa ract`res ; pour les caract`res latins courants cest lordre alphabtique qui est exprim. e e e e 21

22

CHAPITRE 2. SUITE DINSTRUCTIONS

2.1.2

Connecteurs

On peut construire des expressions boolennes comportant plusieurs comparateurs e en utilisant les connecteurs &&, qui signie et, || qui signie ou et ! qui signie non. Ainsi C1 && C2 est valu ` true si et seulement si les deux expressions C1 et e e a C2 le sont. De mme C1 || C2 est valu ` true si lune deux expressions C1 ou C2 e e ea lest. Par exemple !( ((a<c) && (c<b) && (b<d)) || ((c<a) && (a<d) && (d<b)) ) est une faon de tester si deux intervalles [a, b] et [c, d] sont disjoints ou contenus lun c dans lautre. R`gle dvaluation : en Java, lvaluation de lexpression C1 && C2 seectue dans e e e lordre C1 puis C2 si ncessaire ; ainsi si C1 est value ` false alors C2 nest pas e e e a value. Cest aussi le cas pour C1 || C2 qui est value ` true si cest le cas pour e e e e a C1 et ceci sans que C2 ne soit value. Par exemple lvaluation de lexpression e e e (3 > 4) && (2/0 > 0) donne pour rsultat false alors que e (2/0 > 0) && (3 > 4) donne lieu ` une erreur provoque par la division par zro et levant une exception (voir a e e annexe).

2.2

Instructions conditionnelles

Il sagit dinstructions permettant de neectuer une opration que si une certaine e condition est satisfaite ou de programmer une alternative entre deux options.

2.2.1

If-else

La plus simple de ces instructions est celle de la forme : if(C) I1 else I2 Dans cette criture C est une expression boolenne (attention ` ne pas oublier les e e a parenth`ses autour) ; I1 et I2 sont formes ou bien dune seule instruction ou bien e e dune suite dinstructions ` lintrieur dune paire daccolades { }. On rappelle que a e chaque instruction de Java se termine par un point virgule ( ;, symbole qui fait donc partie de linstruction). Par exemple, les instructions if(a >= 0) b = 1; else b = -1;

2.2. INSTRUCTIONS CONDITIONNELLES permettent de calculer le signe de a et de le mettre dans b.

23

La partie else I2 est facultative, elle est omise si la suite I2 est vide cest ` dire a sil ny a aucune instruction ` excuter dans le cas o` C est value ` false. a e u e e a On peut avoir plusieurs branches spares par des else if comme par exemple e e dans : if(a == 0 ) else if (a < 0) else if (a > -5) else x x x x = = = = 1; 2; 3; 4;

qui donne 4 valeurs possibles pour x suivant les valeurs de a.

2.2.2

Forme compacte

Il existe une forme compacte de linstruction conditionnelle utilise comme un e oprateur ternaire (` trois oprandes) dont le premier est un boolen et les deux autres e a e e sont de type primitif. Cet oprateur scrit C ? E1 : E2. Elle est utilise quand un e e e if else para lourd, par exemple pour le calcul dune valeur absolue : t x = (a > b)? a - b : b - a;

2.2.3

Aiguillage

Quand diverses instructions sont ` raliser suivant les valeurs que prend une vaa e riable, plusieurs if imbriqus deviennent lourds ` mettre en uvre, on peut les reme a placer avantageusement par un aiguillage switch. Un tel aiguillage a la forme suivante dans laquelle x est une variable dun type primitif (entier, caract`re ou boolen, pas e e rel) et a,b,c sont des constantes reprsentant des valeurs que peut prendre cette vae e riable. Lors de lexcution les valeurs apr`s chaque case sont testes lune apr`s lautre e e e e jusqu` obtenir celle prise par x ou arriver ` default, ensuite toutes les instructions a a sont excutes en squence jusqu` la n. Par exemple dans linstruction : e e e a switch(x){ case a : I1 case b : I2 case c : I3 default : I4 } Si la variable x est value ` b alors toutes les suites dinstructions I2, I3, I4 e e a seront excutes, ` moins que lune dentre elles ne contienne un break qui interrompt e e a cette suite. Si la variable est value ` une valeur dirente de a,b,c cest la suite I4 e e a e qui est excute. e e Pour sortir de linstruction avant la n, il faut passer par une instruction break . Le programme suivant est un exemple typique dutilisation :
switch(c){ case s: System.out.println("samedi est un jour de week-end");

24

CHAPITRE 2. SUITE DINSTRUCTIONS


break; case d: System.out.println("dimanche est un jour de week-end"); break; default: System.out.print(c); System.out.println(" nest pas un jour de week-end"); break; }

permet dacher les jours du week-end. Noter labsence daccolades dans les dirents e cas. Si lon crit plutt de faon errone en oubliant les break : e o c e
switch(c){ case s: System.out.println("samedi est un jour de week-end"); case d: System.out.println("dimanche est un jour de week-end"); default: System.out.print(c); System.out.println(" nest pas un jour de week-end"); break; }

on obtiendra, dans le cas o` c svalue ` s : u e a samedi est un jour de week-end dimanche est un jour de week-end s nest pas un jour de week-end

2.3

Itrations e

Une itration permet de rpter plusieurs fois la mme suite dinstructions. Elle est e e e e utilise pour valuer une somme, une suite rcurrente, le calcul dun plus grand commun e e e diviseur par exemple. Elle sert aussi pour eectuer des traitements plus informatiques comme la lecture dun chier. On a lhabitude de distinguer les boucles pour (for) des boucles tant-que (while). Les premi`res sont utilises lorsquon conna lors de e e t, lcriture du programme, le nombre de fois o` les oprations doivent tre itres, les e u e e ee secondes servent ` exprimer des tests darrt dont le rsultat nest pas prvisible ` a e e e a lavance. Par exemple, le calcul dune somme de valeurs pour i variant de 1 ` n rel`ve a e plutt de la catgorie boucle-pour, celui du calcul dun plus grand commun diviseur o e par lalgorithme dEuclide rel`ve plutt dune boucle tant-que. e o

2.3.1

Boucles pour (for)

Litration de type boucle-pour en Java est un peu droutante pour ceux qui la e e dcouvrent pour la premi`re fois. Lexemple le plus courant est celui o` on excute une e e u e suite doprations pour i variant de 1 ` n, comme dans : e a

2.3. ITERATIONS

25

int i; for(i = 1; i <= n; i++) System.out.println(i); Ici, on a ach tous les entiers entre 1 et n. Prenons lexemple de n = 2 et droulons e e les calculs faits par lordinateur : tape 1 : i vaut 1, il est plus petit que n, on excute linstruction e e System.out.println(i); et on incrmente i ; e tape 2 : i vaut 2, il est plus petit que n, on excute linstruction e e System.out.println(i); et on incrmente i ; e tape 3 : i vaut 3, il est plus grand que n, on sort de la boucle. e Une forme encore plus courante est celle o` on dclare i dans la boucle : u e for(int i = 1; i <= n; i++) System.out.println(i); Dans ce cas, on na pas acc`s ` la variable i en dehors du corps de la boucle. e a Un autre exemple est le calcul de la somme
n i=1

1 i

qui se fait par double s = 0.0; for(int i = 1; i <= n; i++) s = s + 1/((double)i); La conversion explicite en double est ici ncessaire, car sinon la ligne plus natue relle : s = s + 1/i; conduit ` valuer dabord 1/i comme une opration enti`re, autrement dit le quotient ae e e de 1 par i, i.e., 0. Et la valeur nale de s serait toujours 1.0. La forme gnrale est la suivante : e e for(Init; C; Inc) I Dans cette criture Init est une initialisation (pouvant comporter une dclaration), e e Inc est une incrmentation, et C un test darrt, ce sont des expressions qui ne se e e terminent pas par un point virgule. Quant ` I, cest le corps de la boucle constitu a e dune seule instruction ou dune suite dinstructions entre accolades. Init est excute e e en premier, ensuite la condition C est value si sa valeur est true alors la suite e e dinstructions I est excute suivie de linstruction dincrmentation Inc et un nouveau e e e tour de boucle reprend avec lvaluation de C. Noter que Init (tout comme Inc) peut e tre compose dune seule expression ou bien de plusieurs, spares par des , (virgules). e e e e

26

CHAPITRE 2. SUITE DINSTRUCTIONS

Noter que les instructions Init ou Inc de la forme gnrale (ou mme les deux) e e e peuvent tre vides. Il ny a alors pas dinitialisation ou pas dincrmentation ; linie e tialisation peut, dans ce cas, gurer avant le for et lincrmentation ` lintrieur de e a e I. Insistons sur le fait que la boucle for(int i = 1; i <= n; i++) System.out.println(i); peut galement scrire : e e for(int i = 1; i <= n; i++) { System.out.println(i); } pour faire ressortir le bloc dinstructions, ou encore : for(int i = 1; i <= n; i++){ System.out.println(i); } ce qui fait gagner une ligne...

2.3.2

Itrations tant que e

Une telle instruction a la forme suivante : while(C) I o` C est une condition et I une instruction ou un bloc dinstructions. Litration u e value C et excute I si le rsultat est true, cette suite est rpte tant que lvaluation e e e e ee e de C donne la valeur true. Un exemple classique de lutilisation de while est le calcul du pgcd de deux nombres par lalgorithme dEuclide. Cet algorithme consiste ` remplacer le calcul de pgcd(a, b) a par celui de pgcd(b, r) o` r est le reste de la division de a par b et ceci tant que r = 0. u while(b r = a = b = } != 0){ a % b; b; r;

Examinons ce quil se passe avec a = 28, b = 16. tape 1 : b = 16 est non nul, on excute le corps de la boucle, et on calcule r = 12 ; e e tape 2 : a = 16, b = 12 est non nul, on calcule r = 4 ; e tape 3 : a = 12, b = 4, on calcule r = 0 ; e tape 4 : a = 4, b = 0 et on sort de la boucle. e Notons enn que boucles pour ou tant-que sont presque toujours interchangeables. Ainsi une forme quivalente de e

2.3. ITERATIONS

27

double s = 0.0; for(int i = 1; i <= n; i++) s += 1/((double)i); est double s = 0.0; int i = 1; while(i <= n){ s += 1/((double)i); i++; } mais que la premi`re forme est plus compacte que la seconde. On a tendance ` utiliser e a une boucle for quand on peut prvoir le nombre ditrations, et while dans les autres e e cas.

2.3.3

Itrations rpter tant que e e e

Il sagit ici deectuer linstruction I et de ne la rpter que si la condition C est e e vrie. La syntaxe est : e e do I while(C) ` A titre dexemple, le probl`me de Syracuse est le suivant : soit m un entier plus e grand que 1. On dnit la suite un par u0 = m et e un+1 = un 2 si un est pair, 3un + 1 sinon.

(la notation n 2 dsigne le quotient de la division euclidienne de n par 2). Il est e conjectur, mais non encore prouv que pour tout m, la suite prend la valeur 1 au bout e e dun temps ni. Pour vrier numriquement cette conjecture, on crit le programme Java suivant : e e e public class Syracuse{ public static void main(String[] args){ int n = Integer.parseInt(args[0]); do{ if((n n else n } while(n return; } } % 2) == 0) /= 2; = 3*n+1; > 1);

28 que lon appelle par : unix% java Syracuse 101

CHAPITRE 2. SUITE DINSTRUCTIONS

Linstruction magique Integer.parseInt(args[0]); permet de rcuprer la vae e leur de lentier 101 pass sur la ligne de commande. e

2.4

Terminaison des programmes

Le programme que nous venons de voir peut tre considr comme trange, voire e ee e dangereux. En eet, si la conjecture est fausse, alors le programme ne va jamais sarrter, on dit quil ne termine pas. Le probl`me de la terminaison des programmes e e est fondamental en programmation. Il faut toujours se demander si le programme quon crit va terminer. Dun point de vue thorique, il est impossible de trouver un algoe e rithme pour faire cela (cf. chapitre 6). Dun point de vue pratique, on doit examiner chaque boucle ou itration et prouver que chacune termine. e Voici quelques erreurs classiques, qui toutes simulent le mouvement perptuel : e int i = 0; while(true) i++; ou bien for(i = 0; i >= 0; i++) ; On sattachera ` prouver que les algorithmes que nous tudions terminent bien. a e

2.5

Instructions de rupture de contrle o

Il y a trois telles instructions qui sont return, break et continue. Linstruction return doit tre utilise dans toutes les fonctions qui calculent un rsultat (cf. chapitre e e e suivant). Les deux autres instructions de rupture sont beaucoup moins utilises et peuvent e tre omises en premi`re lecture. Linstruction break permet dinterrompre une suite e e dinstructions dans une boucle pour passer ` linstruction qui suit la boucle dans le a texte du programme. Linstruction continue a un eet similaire ` celui de break, mais redonne le a contrle ` litration suivante de la boucle au lieu den sortir. o a e

2.6
2.6.1

Exemples
Mthode de Newton e

On rappelle que si f est une fonction susamment raisonnable de la variable relle e x, alors la suite xn+1 = xn f (xn ) f (xn )

2.6. EXEMPLES

29

converge vers une racine de f ` partir dun point de dpart bien choisi. a e Si f (x) = x2 a avec a > 0, la suite converge vers a. Dans ce cas particulier, la rcurrence scrit : e e xn+1 = xn (x2 a)/(2xn ) = n 1 2 xn + a xn .

La suite (xn ) converge, car elle est dcroissante, minore par a. On en dduit que e e e la suite |xn+1 xn | tend vers 0. On it`re la suite en partant de x0 = a, et on sarrte e e quand la dirence entre deux valeurs conscutives est plus petite que > 0 donn. e e e Cette faon de faire est plus stable numriquement (et moins coteuse) que de tester c e u |x2 a| . Si on veut calculer 2 par cette mthode en Java, on crit : e e n public class Newton{ public static void main(String[] args){ double a = 2.0, x, xold, eps; x = a; eps = 1e-10; do{ // recopie de la valeur ancienne xold = x; // calcul de la nouvelle valeur x = (xold+a/xold)/2; System.out.println(x); } while(Math.abs(x-xold) > eps); System.out.print("Sqrt(a)="); System.out.println(x); return; } } ce qui donne : 1.5 1.4166666666666665 1.4142156862745097 1.4142135623746899 1.4142135623730949 Sqrt(a)=1.4142135623730949 On peut galement vrier le calcul en comparant avec la fonction Math.sqrt() de e e Java. Comment prouve-t-on que cet algorithme termine ? On aborde ici un point parfois pineux. En eet, nous avons montr que la suite (xn ) tend mathmatiquement vers e e e une limite, et que donc, mathmatiquement, la dirence |xn xn1 | tend vers 0. Les e e ottants utiliss par les langages de programmation sont une pauvre approximation e des rels. Il convient donc dtre prudent dans leur utilisation, ce qui est un sujet de e e recherche et dapplications tr`s riche et important. e Pour illustrer les probl`mes de convergence, essayez de faire varier la valeur de la e constante eps du programme.

30

CHAPITRE 2. SUITE DINSTRUCTIONS

Exercice 1. On consid`re la suite calculant 1/ a par la mthode de Newton, en e e utilisant f (x) = a 1/x2 : xn+1 = xn (3 ax2 ). n 2

Ecrire une fonction Java qui calcule cette suite, et en dduire le calcul de a. Cette e suite converge-t-elle plus ou moins vite que la suite donne ci-dessus ? e

Chapitre 3

Mthodes : thorie et pratique e e


Nous donnons dans ce chapitre un aperu gnral sur lutilisation des mthodes c e e e (fonctions) dans un langage de programmation classique, sans nous occuper de la problmatique objet, sur laquelle nous reviendrons dans le chapitre 5. e

3.1

Pourquoi crire des mthodes e e

Reprenons lexemple du chapitre prcdent : e e public class Newton{ public static void main(String[] args){ double a = 2.0, x, xold, eps; x = a; eps = 1e-10; do{ // recopie de la valeur ancienne xold = x; // calcul de la nouvelle valeur x = (xold+a/xold)/2; System.out.println(x); } while(Math.abs(x-xold) > eps); System.out.print("Sqrt(a)="); System.out.println(x); return; } } Nous avons crit le programme implantant lalgorithme de Newton dans la mthode e e dappel (la mthode main). Si nous avons besoin de faire tourner lalgorithme pour e plusieurs valeurs de a dans le mme temps, nous allons devoir recopier le programme e a ` chaque fois. Le plus simple est donc dcrire une mthode ` part, qui ne fait que les e e a calculs lis ` Newton : e a public class Newton2{ 31

32

CHAPITRE 3. METHODES : THEORIE ET PRATIQUE

static double sqrtNewton(double a, double eps){ double xold, x = a; do{ // recopie de la valeur ancienne xold = x; // calcul de la nouvelle valeur x = (xold+a/xold)/2; // System.out.println(x); // (1) pour deboguer } while(Math.abs(x-xold) > eps); return x; } public static void main(String[] args){ double r; r = sqrtNewton(2, 1e-10); System.out.print("Sqrt(2)="); System.out.println(r); r = sqrtNewton(3, 1e-10); System.out.print("Sqrt(3)="); System.out.println(r); } } Remarquons galement que nous avons spar le calcul proprement dit de lachage e e e du rsultat. Si lon a besoin de dboguer les calculs successifs, on peut dcommenter la e e e ligne (1) dans le code. Ecrire des mthodes remplit plusieurs rles : au-del` de la possibilit de rutilisation e o a e e des mthodes ` dirents endroits du programme, le plus important est de clarier la e a e structure du programme, pour le rendre lisible et comprhensible par dautres personnes e que le programmeur original.

3.2
3.2.1

Comment crire des mthodes e e


Syntaxe

Une mthode prend des arguments en param`tres et donne en gnral un rsultat. e e e e e Elle se dclare par : e
public static typeRes nomFonction(type1 nom1, ..., typek nomk)

Dans cette criture typeRes est le type du rsultat. e e La signature dune mthode est constitue de la suite ordonne des types des pae e e ram`tres. e

3.2. COMMENT ECRIRE DES METHODES

33

Le rsultat du calcul de la mthode doit tre indiqu apr`s un return. Il est e e e e e obligatoire de prvoir une telle instruction dans toutes les branches dune mthode. e e Lexcution dun return a pour eet dinterrompre le calcul de la mthode en rendant e e le rsultat ` lappelant. e a On fait appel ` une mthode par a e nomFonction(var1, var2, ... , vark) En gnral cet appel se situe dans une aectation. e e En rsum, une syntaxe tr`s courante est la suivante : e e e
public static typeRes nomFonction(type1 nom1, ..., typek nomk){ typeRes r; r = ...; return r; } ... public static void main(String[] args){ type1 n1; type2 n2; ... typek nk; typeRes s; ... s = nomFonction(n1, n2, ..., nk); ... return; }

3.2.2

Le type spcial void e

Le type du rsultat peut tre void, dans ce cas la mthode ne rend pas de rsultat. e e e e Elle op`re par eet de bord, par exemple en achant des valeurs ` lcran ou en modie a e ant des variables globales. Il est dconseill dcrire des mthodes qui proc`dent par e e e e e eet de bord, sauf bien entendu pour les achages. Un exemple typique est celui de la procdure principale : e // Voici mon premier programme public class Premier{ public static void main(String[] args){ System.out.println("Bonjour !"); return; } } Notons que le return nest pas obligatoire dans une mthode de type void, ` e a moins quelle ne permette de sortir de la mthode dans un branchement. Nous la mete trons souvent pour marquer lendroit o` on sort de la mthode, et par souci dhou e mognit de lcriture. e e e e

34

CHAPITRE 3. METHODES : THEORIE ET PRATIQUE

3.2.3

La surcharge

En Java on peut dnir plusieurs mthodes qui ont le mme nom ` condition que e e e a leurs signatures soient direntes. On appelle surcharge cette possibilit. Le compilateur e e doit tre ` mme de dterminer la mthode dont il est question ` partir du type des e a e e e a param`tres dappel. En Java, loprateur + est surcharg : non seulement il permet e e e de faire des additions, mais il permet de concatner des cha e nes de caract`res (voir la e section 5.5 pour plus dinformation). Par exemple, reprenant le programme de calcul de racine carre, on aurait pu crire : e e public static void main(String[] args){ double r; r = sqrtNewton(2, 1e-10); System.out.println("Sqrt(2)=" + r); }

3.3

Visibilit des variables e

Les arguments dune mthode sont passs par valeurs, cest ` dire que leur valeurs e e a sont recopies lors de lappel. Apr`s la n du travail de la mthode les nouvelles valeurs, e e e qui peuvent avoir t attribues ` ces variables, ne sont plus accessibles. ee e a Ainsi il nest pas possible dcrire une mthode qui change les valeurs de deux e e e variables passes en param`tre, sauf ` procder par des moyens dtourns peu recome e a e e e mands. e Reprenons lexemple donn au premier chapitre : e // Calcul de circonfrence e public class Cercle{ static float pi = (float)Math.PI; public static float circonference(float r) { return 2. * pi * r; } public static void main(String[] args){ float c = circonference (1.5); System.out.print("Circonfrence: e System.out.println(c); return; } } La variable r prsente dans la dnition de circonference est instancie au e e e moment de lappel de la mthode par la mthode main. Tout se passe comme si le e e programme ralisait laectation r = 1.5 au moment dentrer dans f. e Dans lexemple prcdent, la variable pi est une variable de classe, ce qui veut dire e e quelle est connue et partage par toutes les mthodes prsentes dans la classe (ainsi e e e ");

3.3. VISIBILITE DES VARIABLES

35

que par tous les objets de la classe, voir chapitre 5), ce qui explique quon peut lutiliser dans la mthode circonference. e Pour des raisons de propret des programmes, on ne souhaite pas quil existe beaue coup de ces variables de classe. Lidal est que chaque mthode travaille sur ses propres e e variables, indpendamment des autres mthodes de la classe, autant que cela soit pose e sible. Regardons ce qui se passe quand on crit : e public class Essai{ public static int f(int n){ int m = n+1; return 2*m; } public static void main(String[] args){ System.out.print("rsultat="); e System.out.println(f(4)); return; } } La variable m nest connue (on dit vue) que par la mthode f. En particulier, on ne e peut lutiliser dans la mthode main ou toute autre mthode qui serait dans la classe. e e Compliquons encore : public class Essai{ public static int f(int n){ int m = n+1; return 2*m; } public static void main(String[] args){ int m = 3; System.out.print("rsultat="); e System.out.print(f(4)); System.out.print(" m="); System.out.println(m); return; } } Quest-ce qui sache ` lcran ? On a le choix entre : a e rsultat=10 m=5 e ou rsultat=10 m=3 e

36

CHAPITRE 3. METHODES : THEORIE ET PRATIQUE

Dapr`s ce quon vient de dire, la variable m de la mthode f nest connue que de f, e e donc pas de main et cest la seconde rponse qui est correcte. On peut imaginer que e la variable m de f a comme nom rel m-de-la-mthode-f, alors que lautre a pour e e nom m-de-la-mthode-main. Le compilateur et le programme ne peuvent donc pas e faire de confusion.

3.4

Quelques conseils pour crire un (petit) programme e

Un beau programme est dicile ` dcrire, ` peu pr`s aussi dicile ` caractriser a e a e a e quun beau tableau, ou une belle preuve. Il existe quand mme quelques r`gles simples. e e Le premier lecteur dun programme est soi-mme. Si je narrive pas ` me relire, il est e a dicile de croire que quelquun dautre le pourra. On peut tre amen ` crire un e e a e programme, le laisser dormir pendant quelques mois, puis avoir ` le rutiliser. Si le a e programme est bien crit, il sera facile ` relire. e a Grosso modo, la dmarche dcriture de petits ou gros programmes est ` peu pr`s la e e a e mme, ` un facteur dchelle pr`s. On dcoupe en tranches indpendantes le probl`me e a e e e e e a e ` rsoudre, ce qui conduit ` isoler des mthodes ` crire. Une fois cette architecture a e a e mise en place, il ny a plus qu` programmer chacune de celles-ci. Mme apr`s un a e e dcoupage a priori du programme en mthodes, il arrive quon soit amen ` crire e e e a e dautres mthodes. Quand le dcide-t-on ? De faon gnrale, pour ne pas dupliquer du e e c e e code. Une autre r`gle simple est quun morceau de code ne doit jamais dpasser une e e page dcran. Si cela arrive, on doit couper en deux ou plus. La clart y gagnera. e e La mthode main dun programme Java doit ressembler ` une sorte de table des e a mati`res de ce qui va suivre. Elle doit se contenter dappeler les principales mthodes e e du programme. A priori, elle ne doit pas faire de calculs elle-mme. e Les noms de mthode (comme ceux des variables) ne doivent pas se rsumer ` une e e a lettre. Il est tentant pour un programmeur de succomber ` la facilit et dimaginer a e pouvoir programmer toutes les mthodes du monde en rutilisant sans cesse les mmes e e e noms de variables, de prfrence avec un seul caract`re par variable. Faire cela conduit ee e rapidement ` crire du code non lisible, ` commencer par soi. Ce style de programmation ae a est donc proscrit. Les noms doivent tre pertinents. Nous aurions pu crire le programme e e concernant les cercles de la faon suivante : c public class D{ static float z = (float)Math.PI; public static float e(float s){ return 2. * z * s; } public static void main(String[] args){ float y = e(1.5); System.out.println(y); return; } } ce qui aurait rendu la chose un peu plus dicile ` lire. a

3.4. QUELQUES CONSEILS POUR ECRIRE UN (PETIT) PROGRAMME

37

Un programme doit tre ar : on crit une instruction par ligne, on ne mgotte e ee e e pas sur les lignes blanches. De la mme faon, on doit commenter ses programmes. e c Il ne sert ` rien de mettre des commentaires triviaux ` toutes les lignes, mais tous a a les points diciles du programme doivent avoir en regard quelques commentaires. Un bon dbut consiste ` placer au-dessus de chaque mthode que lon crit quelques lignes e a e e dcrivant le travail de la mthode, les param`tres dappel, etc. Que dire de plus sur le e e e sujet ? Le plus important pour un programmeur est dadopter rapidement un style de programmation (nombre despaces, placement des accolades, etc.) et de sy tenir. Finissons avec un programme horrible, qui est le contre-exemple typique ` ce qui a prc`de : e e public class mystere{public static void main(String[] args){ int z= Integer.parseInt(args[0]);doif((z%2)==0) z /=2; else z=3*z+1;while(z>1);}}

38

CHAPITRE 3. METHODES : THEORIE ET PRATIQUE

Chapitre 4

Tableaux
La possibilit de manipuler des tableaux se retrouve dans tous les langages de proe grammation ; toutefois Java, qui est un langage avec des objets, manipule les tableaux dune faon particuli`re que lon va dcrire ici. c e e

4.1

Dclaration, construction, initialisation e

Lutilisation dun tableau permet davoir ` sa disposition un tr`s grand nombre a e de variables en utilisant un seul nom et donc en eectuant une seule dclaration. En e eet, si on dclare un tableau de nom tab et de taille n contenant des valeurs de type e typ, on a ` sa disposition les variables tab[0],tab[1], ..., tab[n-1] qui se a comportent comme des variables ordinaires de type typ. En Java, on spare la dclaration dune variable de type tableau, la construction e e eective dun tableau et linitialisation du tableau. La dclaration dune variable de type tableau de nom tab dont les lments sont e ee de type typ, seectue par1 : typ[] tab; Lorsque lon a dclar un tableau en Java on ne peut pas encore lutiliser compl`e e e tement. Il est en eet interdit par exemple daecter une valeur aux variables tab[i], car il faut commencer par construire le tableau, ce qui signie quil faut rserver de la e place en mmoire (on parle dallocation mmoire) avant de sen servir. e e Lopration de construction seectue en utilisant un new, ce qui donne : e tab = new typ[taille]; Dans cette instruction, taille est une constante enti`re ou une variable de type entier e dont lvaluation doit pouvoir tre eectue ` lexcution. Une fois quun tableau est e e e a e cr avec une certaine taille, celle-ci ne peut plus tre modie. ee e e On peut aussi regrouper la dclaration et la construction en une seule ligne par : e typ[] tab = new typ[taille];
ou de mani`re quivalente par typ tab[] ;. Nous prfrerons la premi`re faon de faire car elle e e ee e c respecte la convention suivant laquelle dans une dclaration, le type dune variable gure compl`tement e e avant le nom de celle-ci. La seconde correspond ` ce qui se fait en langage C. a
1

39

40

CHAPITRE 4. TABLEAUX Lexemple de programme le plus typique est le suivant : int[] tab = new int[10]; for(int i = 0; i < 10; i++) tab[i] = i;

Pour des tableaux de petite taille on peut en mme temps construire et initialiser un e tableau et initialiser les valeurs contenues dans le tableau. Lexemple suivant regroupe les 3 oprations de dclaration, construction et initialisation de valeurs en utilisant une e e aectation suivie de {, } : int[] tab = {1,2,4,8,16,32,64,128,256,512,1024}; La taille dun tableau tab peut sobtenir grce ` lexpression tab.length. Coma a pltons lexemple prcdent : e e e int[] tab = {1,2,4,8,16,32,64,128,256,512,1024}; for(int i = 0; i < tab.length; i++) System.out.println(tab[i]); Insistons encore une fois lourdement sur le fait quun tableau tab de n lments ee en Java commence ncessairement ` lindice 0, le dernier lment accessible tant e a ee e tab[n-1]. Si tab est un tableau dont les lments sont de type typ, on peut alors considrer ee e tab[i] comme une variable et eectuer sur celle-ci toutes les oprations admissibles e concernant le type typ, bien entendu lindice i doit tre infrieur ` la taille du tae e a bleau donne lors de sa construction. Java vrie cette condition ` lexcution et une e e a e exception est leve si elle nest pas satisfaite. e Donnons un exemple simple dutilisation dun tableau. Recherchons le plus petit lment dans un tableau donn : ee e public static int plusPetit(int[] x){ int k = 0, n = x.length; for(int i = 1; i < n; i++) // invariant : k est lindice du plus petit // {\e}l{\e}ment de x[0..i-1] if(x[i] < x[k]) k = i; return x[k]; }

4.2

Reprsentation en mmoire et consquences e e e

La mmoire accessible au programme peut tre vue comme un ensemble de cases e e qui vont contenir des valeurs associes aux variables quon utilise. Cest le compilae teur qui se charge dassocier aux noms symboliques les cases correspondantes, qui sont repres par des numros (des indices dans un grand tableau, appels encore adresses). ee e e

4.2. REPRESENTATION EN MEMOIRE ET CONSEQUENCES

41

Le programmeur moderne na pas ` se soucier des adresses relles, il laisse ce soin au a e compilateur (et au programme). Aux temps historiques, la programmation se faisait en manipulant directement les adresses mmoire des objets, ce qui tait pour le moins peu e e confortable2 . Quand on crit : e int i = 3, j; une case mmoire3 est rserve pour chaque variable, et celle pour i remplie avec la e e e valeur 3. Quand on excute : e j = i; le programme va chercher la valeur prsente dans la case aecte ` i et la recopie dans e e a la case correspondant ` j. a Que se passe-t-il maintenant quand on dclare un tableau ? e int[] tab; Le compilateur rserve de la place pour la variable tab correspondante, mais pour le e moment, aucune place nest rserve pour les lments qui constitueront tab. Cest ce e e ee qui explique que quand on crit : e public class Bug1{ public static void main(String[] args){ int[] tab; tab[0] = 1; } } on obtient ` lexcution : a e java.lang.NullPointerException at Bug1.main(Bug1.java:5) Cest une erreur tellement frquente que les compilateurs rcents dtectent ce genre de e e e probl`me ` la compilation (si possible). e a Quand on manipule un tableau, on travaille en fait de faon indirecte avec lui, c comme si on utilisait une armoire pour ranger ses aaires. Il faut toujours imaginer qucrire e tab[2] = 3; veut dire au compilateur retrouve lendroit o` tu as stock tab en mmoire et met ` u e e a jour la case dindice 2 avec 3. En fait, le compilateur se rappelle dabord o` il a rang u e son armoire, puis en dduit quel tiroir utiliser. e La valeur dune variable tableau est une rfrence, cest-`-dire ladresse o` elle est ee a u range en mmoire. Par exemple, la suite dinstructions suivante va avoir leet indiqu e e e dans la mmoire : e
Temprons un peu : dans des applications critiques (cartes ` puce par exemple), on sait encore e a descendre ` ce niveau l`, quand on sait mieux que le compilateur comment grer la mmoire. Ce sujet a a e e dpasse le cadre du cours, mais est enseign en anne 2. e e e 3 Une case mmoire pour un int de Java est forme de 4 octets conscutifs. e e e
2

42 int[] t; t @0 t @10 @10 : t[0] 0 t @10 @10 : t[0] 2

CHAPITRE 4. TABLEAUX

null

t=new int[3];

@14 : t[1] 0

@18 : t[2] 0

t[0]=2;

@14 : t[1] 0

@18 : t[2] 0

Apr`s allocation, le tableau t est repr par son adresse @10, et les trois cases par e ee les adresses @10, @14 et @18. On remplit alors t[0] avec le nombre 2. Expliquons maintenant ce qui se passe quand on crit, avec lide de faire une copie e e de t : int[] t = {1, 2, 3}; // t = @500 int[] u = t; // u = @500 u[0] = 0; System.out.println(t[0]); Les variables u et t dsignent le mme tableau, en programmation on dit quelles e e font rfrence au mme tableau, cest-`-dire ` la mme suite demplacements dans la ee e a a e mmoire e t t.length 3

t[0] 0

t[1] 2

t[2] 3

Cest ce qui explique que modier u[0], cest modier lemplacement mmoire rfe ee renc par u, emplacement qui est galement rfrenc par t. Et donc le programme e e ee e a ache la nouvelle valeur de t[0], ` savoir 0. Si on souhaite recopier le contenu dun tableau dans un autre il faut crire une e fonction : public static int[] copier(int[] x){ int n = x.length; int[] y = new int[n]; for(int i = 0; i < n; i++) y[i] = x[i]; return y; } Noter aussi que lopration de comparaison de deux tableaux x == y est value e e e a ` true dans le cas o` x et y rfrencent le mme tableau (par exemple si on a eectu u ee e e

` 4.3. TABLEAUX A PLUSIEURS DIMENSIONS, MATRICES

43

laectation y = x). Si on souhaite vrier lgalit des contenus, il faut crire une e e e e fonction particuli`re : e public static boolean estEgal(int[] x, int[] y){ if(x.length != y.length) return false; for(int i = 0; i < x.length; i++) if(x[i] != y[i]) return false; return true; } Dans cette fonction, on compare les lments terme ` terme et on sarrte d`s que ee a e e deux lments sont distincts, en sortant de la boucle et de la fonction dans le mme ee e mouvement.

4.3

Tableaux ` plusieurs dimensions, matrices a

Un tableau ` plusieurs dimensions est considr en Java comme un tableau de taa ee bleaux. Par exemple, les matrices sont des tableaux ` deux dimensions, plus prcisment a e e des tableaux de lignes. Leur dclaration peut se faire par : e typ[][] tab; On doit aussi le construire ` laide de new. Linstruction a tab = new typ[N][M]; construit un tableau ` deux dimensions, qui est un tableau de N lignes ` M colonnes. a a Linstruction tab.length retourne le nombre de lignes, alors que tab[i].length retourne la longueur du tableau tab[i], cest-`-dire le nombre de colonnes. a On peut aussi, comme pour les tableaux ` une dimension, faire une aectation de a valeurs en une seule fois : int[2][3] tab = {{1,2,3},{4,5,6}}; qui dclare et initialise un tableau ` 2 lignes et 3 colonnes. On peut crire de faon e a e c quivalente : e int[][] tab = {{1,2,3},{4,5,6}}; Comme une matrice est un tableau de lignes, on peut fabriquer des matrices bizarres. Par exemple, pour dclarer une matrice dont la premi`re ligne a 5 colonnes, la deuxi`me e e e ligne 1 colonne et la troisi`me 2, on crit e e public static void main(String[] args){ int[][] M = new int[3][]; M[0] = new int[5]; M[1] = new int[1]; M[2] = new int[2]; }

44 Par contre, linstruction : int[][] N = new int[][3];

CHAPITRE 4. TABLEAUX

est incorrecte. On ne peut dnir un tableau de colonnes. e On peut continuer ` crire un petit programme qui se sert de cela : ae class Tab3{ static void ecrire(int[] t){ for(int j = 0; j < t.length; j++) System.out.println(t[j]); } public static void main(String[] args){ int[][] M = new int[3][]; M[0] = new int[5]; M[1] = new int[1]; M[2] = new int[2]; for(int i = 0; i < M.length; i++) ecrire(M[i]); } }

4.4

Les tableaux comme arguments de fonction

Les valeurs des variables tableaux (les rfrences) peuvent tre passes en argument, ee e e on peut aussi les retourner : class Tab2{ static int[] construire(int n){ int[] t = new int[n]; for(int i = 0; i < n; i++) t[i] = i; return t; } public static void main(String[] args){ int[] t = construire(3); for(int i = 0; i < t.length; i++) System.out.println(t[i]); } } Considrons maintenant le programme suivant : e

4.5. EXEMPLES DUTILISATION DES TABLEAUX

45

public class Test{ public static void f(int[] t){ t[0] = -10; return; } public static void main(String[] args){ int[] t = {1, 2, 3}; f(t); System.out.println("t[0]="+t[0]); return; } } Que sache-t-il ? Pas 1 comme on pourrait le croire, mais 10. En eet, nous voyons l` un exemple de passage par rfrence : le tableau t nest pas recopi ` lentre a ee ea e de la fonction f, mais on a donn ` la fonction f la rfrence de t, cest-`-dire le moyen ea ee a de savoir o` t est gard en mmoire par le programme. On travaille donc sur le tableau u e e t lui-mme. Cela permet dviter des recopies fastidieuses de tableaux, qui sont souvent e e tr`s gros. La lisibilit des programmes peut sen ressentir, mais cest la faon courante e e c de programmer.

4.5
4.5.1

Exemples dutilisation des tableaux


Algorithmique des tableaux

Nous allons crire des fonctions de traitement de probl`mes simples sur des tableaux e e contenant des entiers. Commenons par remplir un tableau avec des entiers alatoires de [0, M [, on crit : c e e public class Tableaux{ static int M = 128; // initialisation public static int[] aleatoire(int N){ int[] t = new int[N]; for(int i = 0; i < N; i++) t[i] = (int)(M * Math.random()); return t; } } Ici, il faut convertir de force le rsultat de Math.random()*M en entier de mani`re e e explicite, car Math.random() retourne un double.

46

CHAPITRE 4. TABLEAUX

Pour tester facilement les programmes, on crit aussi une fonction qui ache les e lments dun tableau t, un entier par ligne : ee ` // affichage a lcran e public static void afficher(int[] t){ for(int i = 0; i < t.length; i++) System.out.println(t[i]); return; } Le tableau t tant donn, un nombre m est-il lment de t ? On crit pour cela e e ee e une fonction qui retourne le plus petit indice i pour lequel t[i]=m sil existe et 1 si aucun indice ne vrie cette condition : e // retourne le plus petit i tel que t[i] = m sil existe // et -1 sinon. public static int recherche(int[] t, int m){ for(int i = 0; i < t.length; i++) if(t[i] == m) return i; return -1; } Passons maintenant ` un exercice plus complexe. Le tableau t contient des entiers a de lintervalle [0, M 1] qui ne sont ventuellement pas tous distincts. On veut savoir e quels entiers sont prsents dans le tableau et ` combien dexemplaire. Pour cela, on e a introduit un tableau auxiliaire compteur, de taille M , puis on parcourt t et pour chaque valeur t[i] on incrmente la valeur compteur[t[i]]. e ` A la n du parcourt de t, il ne reste plus qu` acher les valeurs non nulles contenues a dans compteur : public static void afficher(int[] compteur){ for(int i = 0; i < M; i++) if(compteur[i] > 0){ System.out.print(i+" est utilis "); e System.out.println(compteur[i]+" fois"); } } public static void compter(int[] t){ int[] compteur = new int[M]; for(int i = 0; i < M; i++) compteur[i] = 0; for(int i = 0; i < t.length; i++) compteur[t[i]] += 1; afficher(compteur); }

4.5. EXEMPLES DUTILISATION DES TABLEAUX

47

4.5.2

Un peu dalg`bre linaire e e

Un tableau est la structure de donne la plus simple qui puisse reprsenter un e e vecteur. Un tableau de tableaux reprsente une matrice de mani`re similaire. Ecrivons e e un programme qui calcule lopration de multiplication dun vecteur par une matrice ` e a gauche. Si v est un vecteur colonne ` m lignes et A une matrice n m, alors w = Av a est un vecteur colonne ` n lignes. On a : a
m1

wi =
k=0

Ai,k vk

pour 0

i < n. On crit dabord la multiplication : e

static double[] multMatriceVecteur(double[][] A, double[] v){ int n = A.length; int m = A[0].length; double[] w = new double[n]; // calcul de w = A * v for(int i = 0; i < n; i++){ w[i] = 0; for(int k = 0; k < m; k++) w[i] += A[i][k] * v[k]; } return w; } static double[] multMatriceVecteur(double[][] A, double[] v){ int n = A.length; int m = A[0].length; double[] w = new double[n]; // calcul de w = A * v for(int i = 0; i < n; i++){ w[i] = 0; for(int k = 0; k < m; k++) w[i] += A[i][k] * v[k]; } return w; } puis le programme principal : public static void main(String[] args){

48

CHAPITRE 4. TABLEAUX int n = 3, m = 4; double[][] A = new double[n][m]; // A est n x m double[] v = new double[m]; // v est m x 1 double[] w; // initialisation de A for(int i = 0; i < n; i++) for(int j = 0; j < m; j++) A[i][j] = Math.random(); // initialisation de v for(int i = 0; i < m; i++) v[i] = Math.random(); w = multMatriceVecteur(A, v); // (*) // affichage for(int i = 0; i < n; i++) System.out.println("w["+i+"]="+w[i]); return; }

public static void main(String[] args){ int n = 3, m = 4; double[][] A = new double[n][m]; // A est n x m double[] v = new double[m]; // v est m x 1 double[] w; // initialisation de A for(int i = 0; i < n; i++) for(int j = 0; j < m; j++) A[i][j] = Math.random(); // initialisation de v for(int i = 0; i < m; i++) v[i] = Math.random(); w = multMatriceVecteur(A, v); // (*) // affichage for(int i = 0; i < n; i++) System.out.println("w["+i+"]="+w[i]); return; }

4.5. EXEMPLES DUTILISATION DES TABLEAUX

49

On peut rcrire la fonction de multiplication en passant les arguments par eets de e bord sans avoir ` crer le rsultat dans la fonction, ce qui peut tre coteux quand on a e e e u doit eectuer de nombreux calculs avec des vecteurs. On crit alors plutt : e o static void multMatriceVecteur(double[] w, double[][] A, double[] v){ int n = A.length; int m = A[0].length; // calcul de w = A * v for(int i = 0; i < n; i++){ w[i] = 0; for(int k = 0; k < m; k++) w[i] += A[i][k] * v[k]; } } static void multMatriceVecteur(double[] w, double[][] A, double[] v){ int n = A.length; int m = A[0].length; // calcul de w = A * v for(int i = 0; i < n; i++){ w[i] = 0; for(int k = 0; k < m; k++) w[i] += A[i][k] * v[k]; } } Dans le programme principal, on remplacerait la ligne (*) par : w = new double[n]; multMatriceVecteur(w, A, v);

4.5.3

Le crible dEratosthene

On cherche ici ` trouver tous les nombres premiers de lintervalle [1, N ]. La solution a dj` connue des Grecs consiste ` crire tous les nombres de lintervalle les uns ` la suite ea ae a des autres. Le plus petit nombre premier est 2. On raye alors tous les multiples de 2 plus grands que 2 de lintervalle, ils ne risquent pas dtre premiers. Le premier nombre e qui na pas t ray au-del` du nombre premier courant est lui-mme premier, cest ee e a e le suivant ` traiter. On raye ainsi les multiples de 3 sauf 3, etc. On sarrte quand on a e

50

CHAPITRE 4. TABLEAUX

sapprte ` liminer les multiples de p > (rappelons que tout nombre non premier e ae N N ). plus petit que N a un diviseur premier Comment modliser le crible ? On utilise un tableau de boolens estpremier, e e de taille N + 1, qui reprsentera lintervalle [1, N ]. Il est initialis ` true au dpart, e ea e ` la n du calcul, p car aucun nombre nest ray. A e 2 est premier si et seulement si estpremier[p] == true. On trouve le programme complet dans la gure 4.1. Remarquons que la ligne kp = 2*p; peut tre avantageusement remplace par e e kp = p*p; car tous les multiples de p de la forme up avec u < p ont dj` t rays du tableau ` eaee e a une tape prcdente. e e e Il existe de nombreuses astuces permettant dacclrer le crible. Notons galement ee e que lon peut se servir du tableau des nombres premiers pour trouver les petits facteurs de petits entiers. Ce nest pas la meilleure mthode connue pour trouver des nombres e premiers ou factoriser les nombres qui ne le sont pas. Revenez donc me voir en cours de Cryptologie si a vous intresse. c e

4.5.4

Jouons ` la bataille range a e

On peut galement se servir de tableaux pour reprsenter des objets a priori plus e e compliqus. Nous allons dcrire ici une variante simplie du cl`bre jeu de bataille, e e e ee que nous appelerons bataille range. La r`gle est simple : le donneur distribue 32 cartes e e (numrotes de 1 ` 32) ` deux joueurs, sous la forme de deux piles de cartes, face sur e e a a ` le dessous. A chaque tour, les deux joueurs, appels Alice et Bob, retournent la carte e du dessus de leur pile. Si la carte dAlice est plus forte que celle de Bob, elle marque un point ; si sa carte est plus faible, cest Bob qui marque un point. Gagne celui des deux joueurs qui a marqu le plus de points ` la n des piles. e a Le programme de jeu doit contenir deux phases : dans la premi`re, le programme bat e et distribue les cartes entre les deux joueurs. Dans un second temps, le jeu se droule. e Nous allons stocker les cartes dans un tableau donne[0..32[ avec la convention que la carte du dessus se trouve en position 31. Pour la premi`re phase, battre le jeu revient ` fabriquer une permutation au hae a sard des lments du tableau donne. Lalgorithme le plus ecace pour cela utilise un ee gnrateur alatoire (la fonction Math.random() de Java, qui renvoie un rel alatoire e e e e e entre 0 et 1), et fonctionne selon le principe suivant. On commence par tirer un indice j au hasard entre 0 et 31 et on permute donne[j] et donne[31]. On continue alors avec le reste du tableau, en tirant un indice entre 0 et 30, etc. La fonction Java est alors (nous allons ici systmatiquement utiliser le passage par rfrence des tableaux) : e ee static void battre(int[] donne){ int n = donne.length, i, j, tmp; for(i = n-1; i > 0; i--){ // on choisit un entier j de [0..i]

4.5. EXEMPLES DUTILISATION DES TABLEAUX

51

// Retourne le tableau des nombres premiers // de lintervalle [2..N] public static int[] Eratosthene(int N){ boolean[] estpremier = new boolean[N+1]; int p, kp, nbp; // initialisation for(int n = 2; n < N+1; n++) estpremier[n] = true; // boucle d{\e}limination p = 2; while(p*p <= N){ // elimination des multiples de p // on a dj` elimin les multiples de q < p e a e kp = 2*p; // (cf. remarque) while(kp <= N){ estpremier[kp] = false; kp += p; } // recherche du nombre premier suivant do{ p++; } while(!estpremier[p]); } // comptons tous les nombres premiers <= N nbp = 0; for(int n = 2; n <= N; n++) if(estpremier[n]) nbp++; // mettons les nombres premiers dans un tableau int[] tp = new int[nbp]; for(int n = 2, i = 0; n <= N; n++) if(estpremier[n]) tp[i++] = n; return tp; } Fig. 4.1 Crible dEratosthene.

52

CHAPITRE 4. TABLEAUX j = (int)(Math.random() * (i+1)); // on permute donne[i] et donne[j] tmp = donne[i]; donne[i] = donne[j]; donne[j] = tmp; } } static void battre(int[] donne){ int n = donne.length, i, j, tmp; for(i = n-1; i > 0; i--){ // on choisit un entier j de [0..i] j = (int)(Math.random() * (i+1)); // on permute donne[i] et donne[j] tmp = donne[i]; donne[i] = donne[j]; donne[j] = tmp; } }

La fonction qui cre une donne ` partir dun paquet de n cartes est alors : e a static int[] creerJeu(int n){ int[] jeu = new int[n]; for(int i = 0; i < n; i++) jeu[i] = i+1; battre(jeu); return jeu; } static int[] creerJeu(int n){ int[] jeu = new int[n]; for(int i = 0; i < n; i++) jeu[i] = i+1; battre(jeu); return jeu; } et nous donnons maintenant le programme principal : public static void main(String[] args){ int[] donne;

4.5. EXEMPLES DUTILISATION DES TABLEAUX

53

donne = creerJeu(32); afficher(donne); jouer(donne); } public static void main(String[] args){ int[] donne; donne = creerJeu(32); afficher(donne); jouer(donne); } Nous allons maintenant jouer. Cela se passe en deux temps : dans le premier, le donneur distribue les cartes entre les deux joueurs, Alice et Bob. Dans le second, les deux joueurs jouent, et on ache le nom du vainqueur, celui-ci tant dtermin ` partir du signe du e e ea gain dAlice (voir plus bas) : static void jouer(int[] donne){ int[] jeuA = new int[donne.length/2]; int[] jeuB = new int[donne.length/2]; int gainA; distribuer(jeuA, jeuB, donne); gainA = jouerAB(jeuA, jeuB); if(gainA > 0) System.out.println("A gagne"); else if(gainA < 0) System.out.println("B gagne"); else System.out.println("A et B sont ex aequo"); } static void jouer(int[] donne){ int[] jeuA = new int[donne.length/2]; int[] jeuB = new int[donne.length/2]; int gainA; distribuer(jeuA, jeuB, donne); gainA = jouerAB(jeuA, jeuB); if(gainA > 0) System.out.println("A gagne"); else if(gainA < 0) System.out.println("B gagne"); else System.out.println("A et B sont ex aequo"); } Le tableau donne[0..31] est distribu en deux tas, en commenant par Alice, qui va e c recevoir les cartes de rang pair, et Bob celles de rang impair. Les cartes sont donnes e a ` partir de lindice 31 :

54

CHAPITRE 4. TABLEAUX

` // donne[] contient les cartes quon distribue a partir ` // de la fin. On remplit jeuA et jeuB a partir de 0 static void distribuer(int[] jeuA,int[] jeuB,int[] donne){ int iA = 0, iB = 0; for(int i = donne.length-1; i >= 0; i--) if((i % 2) == 0) jeuA[iA++] = donne[i]; else jeuB[iB++] = donne[i]; } ` // donne[] contient les cartes quon distribue a partir ` // de la fin. On remplit jeuA et jeuB a partir de 0 static void distribuer(int[] jeuA,int[] jeuB,int[] donne){ int iA = 0, iB = 0; for(int i = donne.length-1; i >= 0; i--) if((i % 2) == 0) jeuA[iA++] = donne[i]; else jeuB[iB++] = donne[i]; } On sintresse au gain dAlice, obtenu par la dirence entre le nombre de cartes gagnes e e e par Alice et celles gagnes par Bob. Il sut de mettre ` jour cette variable au cours du e a calcul : static int jouerAB(int[] jeuA, int[] jeuB){ int gainA = 0; for(int i = jeuA.length-1; i >= 0; i--) if(jeuA[i] > jeuB[i]) gainA++; else if(jeuA[i] < jeuB[i]) gainA--; return gainA; } static int jouerAB(int[] jeuA, int[] jeuB){ int gainA = 0; for(int i = jeuA.length-1; i >= 0; i--) if(jeuA[i] > jeuB[i])

4.5. EXEMPLES DUTILISATION DES TABLEAUX gainA++; else if(jeuA[i] < jeuB[i]) gainA--; return gainA; }

55

Exercice. (Programmation du jeu de bataille) Dans le jeu de bataille (toujours avec les cartes 1..32), le joueur qui remporte un pli le stocke dans une deuxi`me pile ` ct e a oe de sa pile courante, les cartes tant stockes dans lordre darrive (la premi`re arrive e e e e e tant mise au bas de la pile), formant une nouvelle pile. Quand il a ni sa premi`re e e pile, il la remplace par la seconde et continue ` jouer. Le jeu sarrte quand un des deux a e joueurs na plus de cartes. Programmer ce jeu.

4.5.5

Pile

On a utilis ci-dessus un tableau pour stocker une pile de cartes, la derni`re arrive e e e tant utilise aussitt. Ce concept de pile est fondamental en informatique. e e o Par exemple, considrons le programme Java : e public static int g(int n){ return 2*n; } public static int f(int n){ return g(n)+1; } public static void main(String[] args){ int m = f(3); // (1) return; } Quand la fonction main sexcute, lordinateur doit excuter linstruction (1). Pour e e ce faire, il garde sous le coude cette instruction, appelle la fonction f, qui appelle ellemme la fonction g, puis revient ` ses moutons en remplissant la variable m. Garder sous e a le coude se traduit en fait par le stockage dans une pile des appels de cette information. g f main m = f(3) main g(3)+1 m = f(3) f main 2*3 g(3)+1 m = f(3)

Fig. 4.2 Pile des appels. Le programme main appelle la fonction f avec largument 3, et f elle-mme appelle e g avec largument 3, et celle-ci retourne la valeur 6, qui est ensuite retourne ` f, et e a ainsi de suite : Cette notion sera complte au chapitre 6. ee

56

CHAPITRE 4. TABLEAUX

f main

7 m = f(3) main m = 7

Fig. 4.3 Pile des appels (suite).

Chapitre 5

Classes, objets
Ce chapitre est consacr ` lorganisation gnrale dune classe en Java, car jusquici ea e e nous nous sommes plutt intresss aux direntes instructions de base du langage. o e e e

5.1

Introduction

Nous avons pour le moment utilis des types primitifs, ou des tableaux constitus e e dlments du mme type (primitif). En fonction des probl`mes, on peut vouloir agrger ee e e e des lments de types dirents, en crant ainsi de nouveaux types. ee e e

5.1.1

Dclaration et cration e e

On peut crer de nouveaux types en Java. Cela se fait par la cration dune classe, e e qui est ainsi la description abstraite dun ensemble. Par exemple, si lon veut reprsenter e les points du plan, on crira : e public class Point{ int abs, ord; } Un objet de la classe sera une instance de cette classe. Par certains cts, cest dj` oe ea ce quon a vu avec les tableaux : int[] t; dclare une variable t qui sera de type tableau dentiers. Comme pour les tableaux, on e devra allouer de la place pour un objet, par la mme syntaxe : e public static void main(String[] args){ Point p; // (1) p = new Point(); p.abs = 1; p.ord = 2; } 57 // (2)

58

CHAPITRE 5. CLASSES, OBJETS

` On a dclar ` la ligne (1) une variable p de type Point. A la ligne (2), on cre e ea e e e une instance de la classe Point, un objet ; pour tre plus prcis, on a fait appel au constructeur implicite de la classe Point (nous verrons dautres constructeurs explicites plus loin). La variable p est une rfrence ` cet objet, tout comme pour les tableaux. ee a abs et ord sont des champs dun objet de la classe ; p.abs et p.ord se manipulent comme des variables de type entier.

5.1.2

Objet et rfrence ee

ee a Insistons sur le fait quune variable de type Point contient une rfrence ` lobjet cr en mmoire, comme dans le cas des tableaux. ee e Dun point de vue graphique, on a ainsi (comme pour les tableaux) : Point p; p @0 p @100 @100 : .abs 0 p @100 @100 : .abs 1

null

r=new Point();

@104 : .ord 0

p.abs = 1;

@104 : .ord 0

Cela explique que la recopie dobjet doive tre considre avec soin. Le programme e ee suivant : public static void main(String[] args){ Point p = new Point(), q; p.abs = 1; p.ord = 2; q = p; q.abs = 3; q.ord = 4; System.out.print(p.abs); System.out.print(q.abs); } va acher 3 3 La variable q contient une rfrence ` lobjet dj` rfrenc par p. Pour recopier le ee a ea ee e contenu de p, il faut crire ` la place : e a

5.1. INTRODUCTION

59

public static void main(String[] args){ Point p = new Point(), q; p.abs = 1; p.ord = 1; q = new Point(); q.abs = p.abs; q.ord = q.abs; System.out.print(p.abs); System.out.print(q.abs); } Remarquons que tester p == q ne teste en fait que lgalit des rfrences, pas e e ee lgalit des contenus. Dans lexemple, les deux rfrences sont direntes, mme si les e e ee e e deux contenus sont gaux. e Il est utile de garder ` lesprit quun objet est cr une seule fois pendant lexcution a ee e dun programme. Dans la suite du programme, sa rfrence peut tre passe aux autres ee e e variables du mme type. e

5.1.3

Constructeurs

Par dfaut, chaque classe est quipe dun constructeur implicite, comme dans e e e lexemple donn prcdemment. On peut galement crer un constructeur explicite, e e e e e pour simplier lcriture de la cration dobjets. Par exemple, on aurait pu crire : e e e public class Point{ int abs; int ord; public Point(int a, int o){ this.abs = a; this.ord = o; } public static void main(String[] args){ Point p = new Point(1, 2); } } Le mot clef this fait rfrence ` lobjet qui vient dtre cr. ee a e ee Remarque : quand on dclare un constructeur explicite, on perd automatiquement le e constructeur implicite. Dans lexemple prcdent, un appel : e e Point p = new Point(); provoquera une erreur ` la compilation. a

60

CHAPITRE 5. CLASSES, OBJETS

5.2

Autres composants dune classe

Une classe est bien plus quun simple type. Elle permet galement de regrouper e les fonctions de base oprant sur les objets de la classe, ainsi que divers variables ou e constantes partages par toutes les mthodes, mais aussi les objets. e e

5.2.1

Mthodes de classe et mthodes dobjet e e

On nutilise pratiquement que des mthodes de classe dans ce cours. Une mthode de e e classe nest rien dautre que lexpression compl`te pour mthode, comme nous lavons e e utilis jusqu` prsent. Une telle mthode est associe ` la classe dans laquelle elle e a e e e a est dnie. Dun point de vue syntaxique, on dclare une telle fonction en la faisant e e prcder du mot rserv static. e e e e On peut ajouter ` la classe Point des mthodes sur les points et droites du plan, une a e autre pour manipuler des segments, une troisi`me pour les polygones etc. Cela donne e une structure modulaire, plus agrable ` lire, pour les programmes. Par exemple : e a public class Point{ ... public static void afficher(Point p){ System.out.print("("+p.x+", "+p.y+")"); } } Il existe galement des mthodes dobjet, qui sont associes ` un objet de la classe. e e e a Lappel se fait alors par NomObjet.NomFonction. Dans la description dune mthode e non statique on fait rfrence ` lobjet qui a servi ` lappel par le nom this. ee a a Achons les coordonnes dun point (voir 5.1.1 pour la dnition de la classe e e Point) : public void afficher(){ System.out.println(" Point de coordonn{\e}es " + this.abs +" " + this.ord); } qui sera utilis par exemple dans e p.afficher(); On a utilis la mthode dobjet pour acher p. Il existe une alternative, avec la mthode e e e toString(), dnie par : e public String toString(){ return "(" + this.abs + ", " + this.ord + ")"; } Elle permet dacher facilement un objet de type Point. En eet, si lon veut acher le point p, il sut alors dutiliser linstruction : System.out.print(p); et cela achera le point sous forme dun couple (x, y). Terminons par une mthode qui e retourne loppos dun point par rapport ` lorigine : e a

5.2. AUTRES COMPOSANTS DUNE CLASSE

61

public Point oppose(){ return new Point(- this.abs, - this.ord); }

5.2.2

Passage par rfrence ee

Le mme phnom`ne dj` dcrit pour les tableaux ` la section 4.4 se produit pour e e e ea e a les objets, ce que lon voit avec lexemple qui suit : public class Abscisse{ int x; public static void f(Abscisse a){ a.x = 2; return; } public static void main(String[] args){ Abscisse a = new Abscisse(); a.x = -1; f(a); System.out.println("a="+a.x); return; } } La rponse est a=2 et non a=-1. e

5.2.3

Variables de classe

Les variables de classes sont communes ` une classe donne, et se comportent comme a e les variables globales dans dautres langages. Considrons le cas (articiel) o` on veut e u garder en mmoire le nombre de points crs. On va utiliser une variable nbPoints e ee pour cela : public class Point{ int abs, ord; static int nbPoints = 0; public Point(){ nbPoints++; } public static void main(String[] args){ Point P = new Point();

62

CHAPITRE 5. CLASSES, OBJETS System.out.println("Nombre de points crees : " e + nbPoints); }

} Une constante se dclare en rajoutant le mot clef final : e final static int promotion = 2010; elle ne pourra pas tre modie par les mthodes de la classe, ni par aucune autre e e e classe.

5.2.4

Utiliser plusieurs classes

Lorsque lon utilise une classe dans une autre classe, on doit faire prcder les noms e e des mthodes du nom de la premi`re classe suivie dun point. e e public class Exemple{ public static void main(String[] args){ Point p = new Point(0, 0); Point.afficher(p); return; } } Attention : il est souvent impos par le compilateur quil ny ait quune seule classe e public par chier. Il nous arrivera cependant dans la suite du poly de prsenter e plusieurs classes publiques lune immdiatement ` la suite de lautre. e a

5.3

Autre exemple de classe

Les classes prsentes pour le moment agrgeaient des types identiques. On peut e e e dnir la classe des produits prsents dans un magasin par e e public class Produit{ String nom; int nb; double prix; } On peut alors grer un stock de produits, et donc faire des tableaux dobjets. On e doit dabord allouer le tableau, puis chaque lment : ee public class GestionStock{ public static void main(String[] args){ Produit[] s; s = new Produit[10]; s[0] = new Produit(); // place pour le tableau // place pour lobjet

5.4. PUBLIC ET PRIVATE s[0].nom = "ordinateur"; s[0].nb = 5; s[0].prix = 7522.50; } }

63

5.4

Public et private

Nous avons dj` rencontr le mot rserv public qui permet par exemple ` java ea e e e a de lancer un programme dans sa syntaxe immuable : public static void main(String[] args){...} On doit garder en mmoire que public dsigne les mthodes, champs, ou constantes e e e qui doivent tre visibles de lextrieur de la classe. Cest le cas de la mthode afficher e e e de la classe Point dcrite ci-dessus. Elles pourront donc tre appeles dune autre e e e classe, ici de la classe Exemple. Quand on ne souhaite pas permettre un appel de ce type, on dclare alors une e mthode avec le mot rserv private. Cela permet par exemple de protger certaines e e e e variables ou constantes qui ne doivent pas tre connues de lextrieur, ou bien encore de e e forcer lacc`s aux champs dun objet en passant par des mthodes publiques, et non par e e les champs eux-mmes. On en verra un exemple avec le cas des String au chapitre 5.6. e

5.5
5.5.1

Un exemple de classe prdnie : la classe String e e


Proprits e e

Une cha de caract`res est une suite de symboles que lon peut taper sur un clavier ne e ou lire sur un cran. La dclaration dune variable susceptible de contenir une cha e e ne de caract`res se fait par e String u; Un point important est que lon ne peut pas modier une cha de caract`res, on dit ne e quelle est non mutable. On peut par contre lacher, la recopier, accder ` la valeur dun e a des caract`res et eectuer un certain nombre doprations comme la concatnation, lobe e e tention dune sous-cha on peut aussi vrier lgalit de deux cha ne, e e e nes de caract`res. e La faon la plus simple de crer une cha est dutiliser des constantes comme : c e ne String s = "123"; On peut galement concatner des cha e e nes, ce qui est tr`s facile ` laide de loprateur e a e + qui est surcharg : e String s = "123" + "x" + "[]"; On peut galement fabriquer une cha ` partir de variables : e ne a int i = 3; String s = "La variable i vaut " + i;

64

CHAPITRE 5. CLASSES, OBJETS

qui permettra un achage agrable en cours de programme. Comment comprendre e cette syntaxe ? Face ` une telle demande, le compilateur va convertir la valeur de la a variable i sous forme de cha de caract`res qui sera ensuite concatne ` la cha ne e e e a ne constante. Dans un cas plus gnral, une expression telle que : e e MonObjet o; String s = "Voici mon objet : " + o; donnera le rsultat attendu si une mthode dobjet toString est disponible pour e e la classe MonObjet. Sinon, ladresse de o en mmoire est ache (comme pour les e e tableaux). On trouvera un exemple comment au chapitre 16. e Voici dautres exemples : String v = new String(u); recopie la cha u dans la cha v. ne ne int l = u.length(); donne la longueur de la cha u. Noter que length est une fonction sur les cha ne nes de caract`res, tandis que sur les tableaux, cest une valeur ; ceci explique la dirence e e dcriture : les parenth`ses pour la fonction sur les cha e e nes de caract`res sont absentes e dans le cas des tableaux. char x = u.charAt(i); donne ` x la valeur du i-`me caract`re de la cha u, noter que le premier caract`re a e e ne e sobtient par u.charAt(0). On peut simuler (articiellement) le comportement de la classe String de la faon c suivante, ce qui donne un exemple dutilisation de private : public class Chaine{ private char[] s; public Chaine(char[] t){ this.s = new char[t.length]; for(int i = 0; i < t.length; i++) this.s[i] = t[i]; } // s.length() public int longueur(){ return s.length; } // s.charAt(i) public char caractere(int i){ return s[i]; } }

5.5. UN EXEMPLE DE CLASSE PREDEFINIE : LA CLASSE STRING public class TestChaine{ public static void main(String[] args){ char[] t = {a, b, c}; Chaine str = new Chaine(t); System.out.println(str.caractere(0)); // correct System.out.println(str.s[0]); // erreur } }

65

Ainsi, on sait accder au i-i`me caract`re en lecture, mais il ny a aucun moyen e e e e a dy accder en criture. On a empch les classes extrieures ` Chaine daccder ` la e e e e e a reprsentation interne de lobjet. De cette faon, on peut changer celle-ci en fonction des e c besoins (cela sera expliqu de faon plus compl`te dans les cours de deuxi`me anne). e c e e e u.compareTo(v); e e e e entre deux String a pour rsultat un nombre entier ngatif si u prc`de v dans lordre lexicographique (celui du dictionnaire), 0 si les cha nes u et v sont gales, et un nombre e positif si v prc`de u. e e w = u.concat(v); // equivalent de w = u + v; construit une nouvelle cha obtenue par concatnation de u suivie de v. Noter ne e que v.concat(u) est une cha dirente de la prcdente. ne e e e

5.5.2

Arguments de main

La mthode main qui gure dans tout programme que lon souhaite excuter e e doit avoir un param`tre de type tableau de cha e nes de caract`res. On dclare alors e e la mthode par e public static void main(String[] args) Pour comprendre lintrt de tels param`tres, supposons que la mthode main se ee e e trouve ` lintrieur dun programme qui commence par public class Classex. On a e peut alors utiliser les valeurs et variables args.length, args[0], args[1], . . . ` a lintrieur de la procdure main. Celles-ci correspondent aux cha e e nes de caract`res qui e suivent java Classex lorsque lutilisateur demande dexcuter son programme. e Par exemple si on a crit une procdure main : e e public static void main(String[] args){ for(int i = args.length-1; i >= 0; i--) System.out.print(args[i] + " "); System.out.println(); } et quune fois celle-ci compile on demande lexcution par e e java Classex marquise damour me faites mourir on obtient comme rsultat e

66 mourir faites me damour marquise

CHAPITRE 5. CLASSES, OBJETS

Noter que lon peut transformer une cha de caract`res u compose de chires ne e e dcimaux en un entier par la fonction Integer.parseInt() comme dans le proe gramme suivant : public class Additionner{ public static void main(String[] args){ if(args.length != 2) System.out.println("mauvais nombre darguments"); else{ int s = Integer.parseInt(args[0]); s += Integer.parseInt(args[1]); System.out.println (s); } } } On peut alors demander java Additionner 1052 958 linterprteur rpond : e e 2010 Notons quil existe dautres fonctions de conversion en double, long, etc..

5.6

Pour aller plus loin

La programmation objet est un paradigme de programmation dans lequel les programmes sont dirigs par les donnes. Au niveau de programmation du cours, cette e e faon de programmer appara essentiellement comme une dirence syntaxique. Il c t e faudra attendre les cours de deuxi`me anne (INF431) pour voir lintrt de la proe e ee grammation objet, avec la notion dhritage. e

Chapitre 6

Rcursivit e e
Jusqu` prsent, nous avons programm des fonctions simples, qui ventuellement a e e e en appelaient dautres. Rien nempche dimaginer quune fonction puisse sappeler e elle-mme. Cest ce quon appelle une fonction rcursive. Lintrt dune telle fonction e e ee peut ne pas appara tre clairement au premier abord, ou encore faire peur. Dun certain point de vue, elles sont en fait proches du formalisme de la relation de rcurrence e en mathmatique. Bien utilises, les fonctions rcursives permettent dans certains cas e e e dcrire des programmes beaucoup plus lisibles, et permettent dimaginer des algoe rithmes dont lanalyse sera facile et limplantation rcursive aise. Nous introduirons e e ainsi plus tard un concept fondamental de lalgorithmique, le principe de diviser-pourrsoudre. Les fonctions rcursives seront indispensables dans le traitement des types e e rcursifs, qui seront introduits en INF-421. e Finalement, on verra que lintroduction de fonctions rcursives ne se limite pas e a ` une nouvelle syntaxe, mais quelle permet daborder des probl`mes importants de e linformatique, comme la non-terminaison des probl`mes ou lindcidabilit de certains e e e probl`mes. e

6.1

Premiers exemples

Lexemple le plus simple est celui du calcul de n!. Rappelons que 0! = 1! = 1 et que n! = n (n 1)!. De mani`re itrative, on crit : e e e public static int factorielle(int n){ int f = 1; for(int k = n; k > 1; k--) f *= k; return f; } qui implante le calcul par accumulation du produit dans la variable f. De mani`re rcursive, on peut crire : e e e public static int fact(int n){ if(n == 0) return 1; // cas de base else return n * fact(n-1); 67

68 }

CHAPITRE 6. RECURSIVITE

On a coll daussi pr`s que possible ` la dnition mathmatique. On commence e e a e e par le cas de base de la rcursion, puis on crit la relation de rcurrence. e e e La syntaxe la plus gnrale dune fonction rcursive est : e e e public static <type_de_retour> <nomFct>(<args>){ [dclaration de variables] e [test darrt] e [suite dinstructions] [appel de <nomFct>(<args>)] [suite dinstructions] return <rsultat>; e } Regardons dun peu plus pr`s comment fonctionne un programme rcursif, sur e e lexemple de la factorielle. Lordinateur qui excute le programme voit quon lui dee mande de calculer fact(3). Il va en eet stocker dans un tableau le fait quon veut cette valeur, mais quon ne pourra la calculer quapr`s avoir obtenu la valeur e de fact(2). On proc`de ainsi (on dit quon empile les appels dans ce tableau, qui est e une pile) jusqu` demander la valeur de fact(0) (voir gure 6.1). a

fact(1) fact(2) fact(3) 3 n ? fact fact(3) 2 3 n ? ? fact fact(2) fact(3)

1 2 3 n

? ? ? fact

Fig. 6.1 Empilement des appels rcursifs. e Arriv au bout, il ne reste plus qu` dpiler les appels, pour de proche en proche e a e pouvoir calculer la valeur de fact(3), cf. gure 6.2.

0 1 2 3 n

1 ? ? ? fact 1 2 3 n 1 ? ? fact 2 3 n 2 ? fact 3 n 6 fact

Fig. 6.2 Dpilement des appels rcursifs. e e La rcursivit ne marche que si on ne fait pas dborder cette pile dappels. Imaginez e e e que nous ayons crit : e

6.2. DES EXEMPLES MOINS ELEMENTAIRES

69

public static int fact(int n){ if(n == 0) return 1; // cas de base else return n * fact(n+1); } Nous aurions rempli la pi`ce du sol au plafond sans atteindre la n du calcul. On e dit dans ce cas l` que la fonction ne termine pas. Cest un probl`me fondamental de a e linformatique de pouvoir prouver quune fonction (ou un algorithme) termine. On voit appara l` une caractristique primordiale de la programmation, qui nous rapproche tre a e de ce que lon demande en mathmatiques. e Exercice. On consid`re la fonction Java suivante : e public static int f(int n){ if(n > 100) return n - 10; else return f(f(n+11)); } Montrer que la fonction retourne 91 si n 100 et n 10 si n > 100.

6.2

Des exemples moins lmentaires ee

Encore un mot sur le programme de factorielle. Il sagit dun cas facile de rcursivit e e terminale, cest-`-dire que ce nest jamais quune boucle for dguise. a e e

6.2.1

Ecriture binaire des entiers

Prenons un cas o` la rcursivit apporte plus. Rappelons que tout entier strictement u e e positif n peut scrire sous la forme e
p

n=
i=0

b i 2i = b 0 + b 1 2 + b 2 22 + + b p 2p ,

bi {0, 1}

avec p 0. Lalgorithme naturel pour rcuprer les chires binaires (les bi ) consiste ` e e a eectuer la division euclidienne de n par 2, ce qui nous donne n = 2q1 + b0 , puis celle de q par 2, ce qui fournit q1 = 2q2 + b1 , etc. Supposons que lon veuille acher ` lcran les a e chires binaires de n, dans lordre naturel, cest-`-dire les poids forts ` gauche, comme a a on le fait en base 10. Pour n = 13 = 1 + 0 2 + 1 22 + 1 23 , on doit voir 1101 La fonction la plus simple ` crire est : ae public static void binaire(int n){ while(n != 0){ System.out.println(n%2); n = n/2; }

70 return; } Malheureusement, elle ache plutt : o 1011

CHAPITRE 6. RECURSIVITE

cest-`-dire lordre inverse. On aurait pu galement crire la fonction rcursive : a e e e public static void binaireRec(int n){ if(n > 0){ System.out.print(n%2); binaireRec(n/2); } return; } qui ache elle aussi dans lordre inverse. Regardons une trace du programme, cesta `-dire quon en droule le fonctionnement, de faon analogue au mcanisme dempilee c e ment/dpilement : e 1. On ache 13 modulo 2, cest-`-dire b0 , puis on appelle binaireRec(6). a 2. On ache 6 modulo 2 (= b1 ), et on appelle binaireRec(3). 3. On ache 3 modulo 2 (= b2 ), et on appelle binaireRec(1). 4. On ache 1 modulo 2 (= b3 ), et on appelle binaireRec(0). Le programme sarrte e apr`s avoir dpil les appels. e e e Il sut de permuter deux lignes dans le programme prcdent e e public static void binaireRec2(int n){ if(n > 0){ binaireRec2(n/2); System.out.print(n%2); } return; } pour que le programme ache dans le bon ordre ! O` est le miracle ? Avec la mme u e trace : 1. On appelle binaireRec2(6). 2. On appelle binaireRec2(3). 3. On appelle binaireRec2(1). 4. On appelle binaireRec2(0), qui ne fait rien. 3. On revient au dernier appel, et maintenant on ache b3 = 1 mod 2 ; 2. on ache b2 = 3 mod 2, etc. Cest le programme qui nous a pargn la peine de nous rappeler nous-mmes dans e e e quel ordre nous devions faire les choses. On aurait pu par exemple les raliser avec e un tableau qui stockerait les bi avant de les acher. Nous avons laiss ` la pile de e a rcursivit cette gestion. e e

6.2. DES EXEMPLES MOINS ELEMENTAIRES

71

6.2.2

Les tours de Hanoi

Il sagit l` dun jeu inspir par une fausse lgende cre par le mathmaticien franais a e e ee e c Edouard Lucas. Il sagit de trois poteaux sur lesquels peuvent coulisser des rondelles de taille croissante. Au dbut du jeu, toutes les rondelles sont sur le mme poteau, e e classes par ordre dcroissant de taille ` partir du bas. Il sagit de faire bouger toutes e e a les rondelles, de faon ` les amener sur un autre poteau donn. On dplace une rondelle c a e e a ` chaque fois, et on na pas le droit de mettre une grande rondelle sur une petite. Par contre, on a le droit dutiliser un troisi`me poteau si on le souhaite. Nous appelerons e les poteaux D (dpart), M (milieu), A (arrive). e e La rsolution du probl`me avec deux rondelles se fait ` la main, ` laide des moue e a a vements suivants. La position de dpart est : e D M A

On commence par dplacer la petite rondelle sur le poteau M : e D M A

puis on met la grande en place sur le poteau A : D M A

et enn la petite rejoint la grande : D M A

72

CHAPITRE 6. RECURSIVITE

La solution gnrale sen dduit (cf. gure 6.3). Le principe est de solidariser les e e e n 1 premi`res rondelles. Pour rsoudre le probl`me, on fait bouger ce tas de n 1 e e e pi`ces du poteau D vers le poteau M (` laide du poteau A), puis on bouge la grande e a rondelle vers A, puis il ne reste plus qu` bouger le tas de M vers A en utilisant D. Dans a ce dernier mouvement, la grande rondelle sera toujours en dessous, ce qui ne crera pas e de probl`me. e Imaginant que les poteaux D, M, A sont de type entier, on arrive ` la fonction a suivante : public static void Hanoi(int n, int D, int M, int A){ if(n > 0){ Hanoi(n-1, D, A, M); System.out.println("On bouge "+D+" vers "+A); Hanoi(n-1, M, D, A); } }

6.3

Un pi`ge subtil : les nombres de Fibonacci e

Supposons que nous voulions crire une fonction qui calcule le n-i`me terme de la e e suite de Fibonacci, dnie par F0 = 0, F1 = 1 et e n 2, Fn = Fn1 + Fn2 .

Le programme naturellement rcursif est simplement : e public static int fib(int n){ if(n <= 1) return n; // cas de base else return fib(n-1)+fib(n-2); } On peut tracer larbre des appels pour cette fonction, qui gnralise la pile des appels : e e fib(4) fib(3) fib(2) fib(1) fib(0) Le programme marche, il termine. Le probl`me se situe dans le nombre dappels ` e a la fonction. Si on note A(n) le nombre dappels ncessaires au calcul de Fn , il est facile e de voir que ce nombre vrie la rcurrence : e e A(n) = A(n 1) + A(n 2) qui est la mme que celle de Fn . Rappelons le rsultat suivant : e e fib(2)

fib(1) fib(1) fib(0)

` 6.3. UN PIEGE SUBTIL : LES NOMBRES DE FIBONACCI

73

Fig. 6.3 Les tours de Hanoi.

74 Proposition 1 Avec = (1 + 0.618 . . . :

CHAPITRE 6. RECURSIVITE 5)/2 1.618 . . . (nombre dor), = (1 5)/2

1 Fn = n n = O(n ). 5 (La notation O() sera rappele au chapitre suivant.) e On fait donc un nombre exponentiel dappels ` la fonction. a Une faon de calculer Fn qui ne cote que n appels est la suivante. On calcule de c u proche en proche les valeurs du couple (Fi , Fi+1 ). Voici le programme : public static int fib(int n){ int i, u, v, w; // u = F(0); v = F(1) u = 0; v = 1; for(i = 2; i <= n; i++){ // u = F(i-2); v = F(i-1) w = u+v; u = v; v = w; } return v; } De meilleures solutions pour calculer Fn vous seront donnes en TD. e Exercice. (Fonction dAckerman) On la dnit de la faon e c n+1 Ack(m 1, 1) Ack(m, n) = Ack(m 1, Ack(m, n 1)) Montrer que Ack(1, n) = n + 2, Ack(2, n) = 2n + 3, Ack(3, n) = 8 2n 3, Ack(4, n) = 2 Ack(4, 4) > 2 22
2

suivante : si m = 0, si n = 0, sinon.

65536

> 10

80

nombre bien plus grand que le nombre estim de particules dans lunivers. e

6.4

Fonctions mutuellement rcursives e

Rien nempche dutiliser des fonctions qui sappellent les unes les autres, du moe ment que le programme termine. Nous allons en donner maintenant des exemples.

6.4. FONCTIONS MUTUELLEMENT RECURSIVES

75

6.4.1

Pair et impair sont dans un bateau

Commenons par un exemple un peu articiel : nous allons crire une fonction qui c e teste la parit dun entier n de la faon suivante : 0 est pair ; si n > 0, alors n est pair e c si et seulement si n 1 est impair. De mme, 0 nest pas impair, et n > 1 est impair si e et seulement si n 1 est pair. Cela conduit donc ` crire les deux fonctions : ae // n est pair ssi (n-1) est impair public static boolean estPair(int n){ if(n == 0) return true; else return estImpair(n-1); } // n est impair ssi (n-1) est pair public static boolean estImpair(int n){ if(n == 0) return false; else return estPair(n-1); } qui remplissent lobjectif x. e

6.4.2

Dveloppement du sinus et du cosinus e

Supposons que nous dsirions crire la formule donnant le dveloppement de sin nx e e e sous forme de polynme en sin x et cos x. On va utiliser les formules o sin nx = sin x cos(n 1)x + cos x sin(n 1)x, cos nx = cos x cos(n 1)x sin x sin(n 1)x avec les deux cas darrt : sin 0 = 0, cos 0 = 1. Cela nous conduit ` crire deux fonctions, e ae qui retournent des cha nes de caract`res crites avec les deux variables S pour sin x et e e C pour cos x. public static String DevelopperSin(int n){ if(n == 0) return "0"; else{ String g = "S*(" + DevelopperCos(n-1) + ")"; return g + "+C*(" + DevelopperSin(n-1) + ")"; } } public static String DevelopperCos(int n){ if(n == 0) return "1"; else{ String g = "C*(" + DevelopperCos(n-1) + ")"; return g + "-S*(" + DevelopperSin(n-1) + ")"; } } Lexcution de ces deux fonctions nous donne par exemple pour n = 3 : e

76

CHAPITRE 6. RECURSIVITE

sin(3*x)=S*(C*(C*(1)-S*(0))-S*(S*(1)+C*(0)))+C*(S*(C*(1)-S*(0))+C*(S*(1)+C*(0)))

Bien sr, lexpression obtenue nest pas celle ` laquelle nous sommes habitus. En u a e particulier, il y a trop de 0 et de 1. On peut crire des fonctions un peu plus compliques, e e qui donnent un rsultat simpli pour n = 1 : e e public static String DevelopperSin(int n){ if(n == 0) return "0"; else if(n == 1) return "S"; else{ String g = "S*(" + DevelopperCos(n-1) + ")"; return g + "+C*(" + DevelopperSin(n-1) + ")"; } } public static String DevelopperCos(int n){ if(n == 0) return "1"; else if(n == 1) return "C"; else{ String g = "C*(" + DevelopperCos(n-1) + ")"; return g + "-S*(" + DevelopperSin(n-1) + ")"; } } ce qui fournit : sin(3*x)=S*(C*(C)-S*(S))+C*(S*(C)+C*(S)) On nest pas encore au bout de nos peines. Simplier cette expression est une tche a complexe, qui sera traite au cours dInformatique fondamentale. e

6.5

Le probl`me de la terminaison e

Nous avons vu combien il tait facile dcrire des programmes qui ne sarrtent e e e jamais. On aurait pu rver de trouver des algorithmes ou des programmes qui prouvee raient cette terminaison ` notre place. Hlas, il ne faut pas rver. a e e Thor`me 1 (Gdel) Il nexiste pas de programme qui dcide si un programme quele e o e conque termine. Expliquons pourquoi de faon informelle, en trichant avec Java. Supposons que lon c dispose dune fonction Termine qui prend un programme crit en Java et qui ralise e e la fonctionnalit demande : Termine(fct) retourne true si fct termine et false e e sinon. On pourrait alors crire le code suivant : e public static void f(){ while(Termine(f)) ; }

` 6.5. LE PROBLEME DE LA TERMINAISON

77

Cest un programme bien curieux. En eet, termine-t-il ? Ou bien Termine(f) retourne true et alors la boucle while est active indniment, donc il ne termine e e pas. Ou bien Termine(f) retourne false et alors le programme ne termine pas, alors que la boucle while nest jamais eectue. Nous venons de rencontrer un proe bl`me indcidable, celui de larrt. Classier les probl`mes qui sont ou pas dcidables e e e e e reprsente une part importante de linformatique thorique. e e

78

CHAPITRE 6. RECURSIVITE

Chapitre 7

Introduction ` la complexit des a e algorithmes


Lutilisateur dun programme se demande souvent combien de temps mettra son programme ` sexcuter sur sa machine. Ce probl`me est concret, mais mal dni. Il a e e e dpend de la machine, du syst`me dexploitation, de ce quon fait en parall`le, etc. e e e Dun point de vue abstrait, il faut se demander comment fonctionne le programme, quel mod`le de calcul il utilise (squentiel, parall`le, vectoriel), etc.. On peut galement e e e e se xer un probl`me et se demander quelle est la mthode la plus rapide pour le e e rsoudre. Nous allons voir dans ce chapitre comment on peut commencer ` sattaquer e a au probl`me, en sintressant ` la complexit des algorithmes. e e a e

7.1

Complexit des algorithmes e

La complexit (temporelle) dun algorithme est le nombre doprations lmentaires e e ee (aectations, comparaisons, oprations arithmtiques) eectues par un algorithme. Ce e e e nombre sexprime en fonction de la taille n des donnes. On sintresse au cot exact e e u quand cest possible, mais galement au cot moyen (que se passe-t-il si on moyenne sur e u toutes les excutions du programme sur des donnes de taille n), au cas le plus favorable, e e ou bien au cas le pire. On dit que la complexit de lalgorithme est O(f (n)) o` f est e u dhabitude une combinaison de polynmes, logarithmes ou exponentielles. Ceci reprend o la notation mathmatique classique, et signie que le nombre doprations eectues e e e est born par cf (n), o` c est une constante, lorsque n tend vers linni. e u Considrer le comportement a linni de la complexit est justi par le fait que les e ` e e donnes dentre des algorithmes sont souvent de grande taille et quon se proccupe e e e surtout de la croissance de cette complexit en fonction de la taille des donnes. Une e e question systmatique ` se poser est : que devient le temps de calcul si on multiplie la e a taille des donnes par 2 ? De cette faon, on peut galement comparer des algorithmes e c e entre eux. Les algorithmes usuels peuvent tre classs en un certain nombre de grandes classes e e de complexit. e Les algorithmes sous-linaires, dont la complexit est en gnral en O(log n). Cest e e e e le cas de la recherche dun lment dans un ensemble ordonn ni de cardinal n. ee e 79

` 80 CHAPITRE 7. INTRODUCTION A LA COMPLEXITE DES ALGORITHMES Les algorithmes en complexit O(n) (algorithmes linaires) ou en O(n log n) sont e e considrs comme rapides, comme lvaluation de la valeur dune expression comee e pose de n symboles ou les algorithmes optimaux de tri. e Plus lents sont les algorithmes de complexit situe entre O(n2 ) et O(n3 ), cest e e le cas de la multiplication des matrices et du parcours dans les graphes. Au del`, les algorithmes polynomiaux en O(nk ) pour k > 3 sont considrs comme a ee lents, sans parler des algorithmes exponentiels (dont la complexit est suprieure e e a ` tout polynme en n) que lon saccorde ` dire impraticables d`s que la taille o a e des donnes est suprieure ` quelques dizaines dunits. e e a e La recherche de lalgorithme ayant la plus faible complexit, pour rsoudre un e e probl`me donn, fait partie du travail rgulier de linformaticien. Il ne faut toutefois pas e e e tomber dans certains exc`s, par exemple proposer un algorithme excessivement alame biqu, dveloppant mille astuces et ayant une complexit en O(n1,99 ), alors quil existe e e e un algorithme simple et clair de complexit O(n2 ). Surtout, si le gain de lexposant de e n saccompagne dune perte importante dans la constante multiplicative : passer dune complexit de lordre de n2 /2 ` une complexit de 1010 n log n nest pas vraiment une e a e amlioration. Les crit`res de clart et de simplicit doivent tre considrs comme aussi e e e e e ee importants que celui de lecacit dans la conception des algorithmes. e

7.2

Calculs lmentaires de complexit ee e

Donnons quelques r`gles simples concernant ces calculs. Tout dabord, le cot dune e u suite de deux instructions est la somme des deux cots : u T (P ; Q) = T (P ) + T (Q). Plus gnralement, si lon ralise une itration, on somme les dirents cots : e e e e e u
n1

T (for(i = 0 ; i < n ; i++) P(i) ;) =


i=0

T (P (i)).

Si f et g sont deux fonctions positives relles, on crit e e f = O(g) si et seulement si le rapport f /g est born ` linni : ea n0 , K, n n0 , 0 f (n) Kg(n).

Autrement dit, f ne cro pas plus vite que g. t Autres notations : f = (g) si f = O(g) et g = O(f ). Les r`gles de calcul simples sur les O sont les suivantes (noublions pas que nous e travaillons sur des fonctions de cot, qui sont ` valeur positive) : si f = O(g) et u a f = O(g ), alors f + f = O(g + g ), f f = O(gg ). On montre galement facilement que si f = O(nk ) et h = e (approximer la somme par une intgrale). e
n i=1 f (i),

alors h = O(nk+1 )

7.3. QUELQUES ALGORITHMES SUR LES TABLEAUX

81

7.3
7.3.1

Quelques algorithmes sur les tableaux


Recherche du plus petit lment ee

Reprenons lexemple suivant : public static int plusPetit(int[] x){ int k = 0, n = x.length; for(int i = 1; i < n; i++) // invariant : k est lindice du plus petit // {\e}l{\e}ment de x[0..i-1] if(x[i] < x[k]) k = i; // (1) return k; } Dans cette fonction, on excute n 1 tests de comparaison. La complexit est donc e e n 1 = O(n). On peut galement se demander combien de fois on passe dans la ligne (1) du e programme. Cette question est mal pose, car elle dpend fortement des donnes e e e dentre. Si le tableau x est tri dans lordre croissant, 0 fois ; sil est class dans e e e lordre dcroissant, n fois. On a donc prouv le cas le meilleur et le cas le pire. On e e peut galement prouver, mais cest plus dicile, que la complexit en moyenne (ceste e a `-dire si on fait la moyenne sur le nombre dexcutions sur toutes les donnes toutes e e les permutations de taille n) est O(log n).

7.3.2

Recherche dichotomique

Si t est un tableau dentiers de taille n, dont les lments sont tris (par exemple ee e par ordre croissant) on peut crire une fonction qui cherche si un entier donn se trouve e e dans le tableau. Comme le tableau est tri, on peut procder par dichotomie : cherchant e e a ` savoir si x est dans t[g..d[, on calcule m = (g + d)/2 et on compare x ` t[m]. Si a x = t[m], on a gagn, sinon on ressaie avec t[g..m[ si t[m] > x et dans t[m+1..d[ e e sinon. Voici la fonction Java correspondante : // on cherche si x est dans t[g..d[; si oui on retourne ind // tel que t[ind] = x; si non, on retourne -1. public static int rechercheDichotomique(long[] t, long x, int g, int d){ int ind = -1; while(g < d){ // tant que [g..d[ nest pas vide int m = (g+d)/2; if(t[m] == x){ ind = m; break; // on sort }

` 82 CHAPITRE 7. INTRODUCTION A LA COMPLEXITE DES ALGORITHMES else if(t[m] > x) // on cherche dans [g..m[ d = m; else // on cherche dans [m+1..d[ g = m+1; } return ind; } public static int rechercheDicho(long[] t, long x){ return rechercheDichotomique(t, x, 0, t.length); } Notons que lon peut crire cette fonction sous forme rcursive, ce qui la rapproche e e de lide de dpart : e e // recherche de x dans t[g..d[ public static int dichoRec(long[] t, long x, int g, int d){ int m; if(g >= d) // lintervalle est vide return -1; m = (g+d)/2; if(t[m] == x) return m; else if(t[m] > x) return dichoRec(t, x, g, m); else return dichoRec(t, x, m+1, d); } Le nombre maximal de comparaisons ` eectuer pour un tableau de taille n est : a T (n) = 1 + T (n/2). Pour rsoudre cette rcurrence, on crit n = 2t , ce qui conduit ` e e e a T (2t ) = T (2t1 ) + 1 = = T (1) + t do` un cot en O(t) = O(log n). u u On verra dans les chapitres suivants dautres calculs de complexit, temporelle ou e bien spatiale.

7.3.3

Recherche simultane du maximum et du minimum e

Lide est de chercher simultanment ces deux valeurs, ce qui va nous permettre e e de diminuer le nombre de comparaisons ncessaires. La remarque de base est qutant e e donns deux entiers a et b, on les classe facilement ` laide dune seule comparaison, e a comme programm ici. La fonction retourne un tableau de deux entiers, dont le premier e sinterpr`te comme une valeur minimale, le second comme une valeur maximale. e

7.3. QUELQUES ALGORITHMES SUR LES TABLEAUX

83

// SORTIE: retourne un couple u = (x, y) avec // x = min(a, b), y = max(a, b) public static int[] comparerDeuxEntiers(int a, int b){ int[] u = new int[2]; if(a < b){ u[0] = a; u[1] = b; } else{ u[0] = b; u[1] = a; } return u; } Une fois cela fait, on proc`de rcursivement : on commence par chercher les couples e e min-max des deux moitis, puis en les comparant entre elles, on trouve la rponse sur e e le tableau entier : // min-max pour t[g..d[ public static int[] minMaxAux(int[] t, int g, int d){ int gd = d-g; if(gd == 1){ // min-max pour t[g..g+1[ = t[g], t[g] int[] u = new int[2]; u[0] = u[1] = t[g]; return u; } else if(gd == 2) return comparerDeuxEntiers(t[g], t[g+1]); else{ // gd > 2 int m = (g+d)/2; int[] tg = minMaxAux(t, g, m); // min-max de t[g..m[ int[] td = minMaxAux(t, m, d); // min-max de t[m..d[ int[] u = new int[2]; if(tg[0] < u[0] = else u[0] = if(tg[1] > u[1] = else u[1] = return u; } } td[0]) tg[0]; td[0]; td[1]) tg[1]; td[1];

` 84 CHAPITRE 7. INTRODUCTION A LA COMPLEXITE DES ALGORITHMES Il ne reste plus qu` crire la fonction de lancement : ae public static int[] minMax(int[] t){ return minMaxAux(t, 0, t.length); } Examinons ce qui se passe sur lexemple int[] t = {1, 4, 6, 8, 2, 3, 6, 0}. On commence par chercher le couple min-max sur tg = {1, 4, 6, 8}, ce qui entra ne ltude de tgg = {1, 4}, do` ugg = (1, 4). De mme, ugd = (6, 8). On compare 1 et 6, e u e puis 4 et 8 pour nalement trouver ug = (1, 8). De mme, on trouve ud = (0, 6), soit e au nal u = (0, 8). Soit T (k) le nombre de comparaisons ncessaires pour n = 2k . On a T (1) = 1 et e T (2) = 2T (1) + 2. Plus gnralement, T (k) = 2T (k 1) + 2. Do` e e u T (k) = 22 T (k 2) + 22 + 2 = = 2u T (k u) + 2u + 2u1 + + 2 soit T (k) = 2k1 T (1) + 2k1 + + 2 = 2k1 + 2k 2 = n/2 + n 2 = 3n/2 2.

7.4

Diviser pour rsoudre e

Cest l` un paradigme fondamental de lalgorithmique. Quand on ne sait pas rsoua e dre un probl`me, on essaie de le couper en morceaux qui seraient plus faciles ` traiter. e a Nous allons donner quelques exemples classiques, qui seront complts par dautres ee dans les chapitres suivants du cours.

7.4.1

Recherche dune racine par dichotomie

On suppose que f : [a, b] R est continue et telle que f (a) < 0, f (b) > 0 : f (x) a b x

Il existe donc une racine x0 de f dans lintervalle [a, b], quon veut dterminer de e sorte que |f (x0 )| pour donn. Lide est simple : on calcule f ((a + b)/2). En e e fonction de son signe, on explore [a, m] ou [m, b]. Par exemple, on commence par programmer la fonction f : public static double f(double x){ return x*x*x-2; }

7.4. DIVISER POUR RESOUDRE puis la fonction qui cherche la racine : // f(a) < 0, f(b) > 0 public static double racineDicho(double a, double b, double eps){ double m = (a+b)/2; double fm = f(m); if(Math.abs(fm) <= eps) return m; if(fm < 0) // la racine est dans [m, b] return racineDicho(m, b, eps); else // la racine est dans [a, m] return racineDicho(a, m, eps); }

85

7.4.2

Exponentielle binaire

Cet exemple va nous permettre de montrer que dans certains cas, on peut calculer la complexit dans le meilleur cas ou dans le pire cas, ainsi que calculer le comportement e de lalgorithme en moyenne. Supposons que lon doive calculer xn avec x appartenant ` un groupe quelconque. a On peut calculer cette quantit ` laide de n 1 multiplications par x, mais on peut ea faire mieux en utilisant les formules suivantes : x0 = 1, xn = Par exemple, on calcule x11 = x(x5 )2 = x(x(x2 )2 )2 , ce qui cote 5 multiplications (en fait 3 carrs et 2 multiplications). u e La fonction valuant xn avec x de type long correspondant aux formules prce e e dentes est : public static long Exp(long x, int n){ if(n == 0) return 1; else{ if((n%2) == 0){ long y = Exp(x, n/2); return y * y; } else{ long y = Exp(x, n/2); return x * y * y; } } } (xn/2 )2 si n est pair, x(x(n1)/2 )2 si n est impair.

` 86 CHAPITRE 7. INTRODUCTION A LA COMPLEXITE DES ALGORITHMES Soit E(n) le nombre de multiplications ralises pour calculer xn . En traduisant e e directement lalgorithme, on trouve que : E(n) = E(n/2) + 1 si n est pair, E(n/2) + 2 si n est impair.
t2 i i=0 bi 2

Ecrivons n > 0 en base 2, soit n = 2t1 + t 1, bi {0, 1}. On rcrit donc : e

= bt1 bt2 b0 = 2t1 + n avec

E(n) = E(bt1 bt2 b1 b0 ) = E(bt1 bt2 b1 ) + b0 + 1 = E(bt1 bt2 b2 ) + b1 + b0 + 2 = = E(bt1 ) + bt2 + + b0 + (t 1)


t2

=
i=0

bi + t

On pose (n ) = t2 bi . On peut se demander quel est lintervalle de variation de i=0 (n ). Si n = 2t1 , alors n = 0 et (n ) = 0, et cest donc le cas le plus favorable de ` lalgorithme. A loppos, si n = 2t 1 = 2t1 + 2t2 + + 1, (n ) = t 1 et cest le e cas le pire. Reste ` dterminer le cas moyen, ce qui conduit ` estimer la quantit : a e a e (n ) = 1 2t1
b0 {0,1} b1 {0,1} t2

bt2 {0,1} i=0

bi

On peut rcrire cela comme : e (n ) = 1 2t1 b0 + b1 + + bt2

o` les sommes sont toutes indexes par les 2t1 (t 1)-uplets forms par tous les u e e (b0 , b1 , . . . , bt2 ) possibles dans {0, 1}t1 . Toutes ces sommes sont gales par symtrie, e e do` : u t1 (n ) = t1 b0 . 2
b0 ,b1 ,...,bt2

Cette derni`re somme ne contient que les valeurs pour b0 = 1 et les bi restant prenant e toutes les valeurs possibles, do` nalement : u (n ) = t 1 t2 t 1 2 = . 2t1 2

Autrement dit, un entier de t 1 bits a en moyenne (t 1)/2 chires binaires gaux e a ` 1. En conclusion, lalgorithme a un cot moyen u 3 E(n) = t + (t 1)/2 = t + c 2 avec t = log2 n et c une constante.

Deuxi`me partie e

Structures de donnes classiques e

87

Chapitre 8

Listes cha ees n


Pour le moment, nous avons utilis des structures de donnes statiques, cest-`-dire e e a dont la taille est connue ` la compilation (tableaux de taille xe) ou bien en cours de a route, mais xe une fois pour toute, comme dans e public static int[] f(int n){ int[] t = new int[n]; return t; }

Cette criture est dj` un confort pour le programmeur, confort quon ne trouve e ea pas dans tous les langages. Parfois, on ne peut pas prvoir la taille des objets avant lexcution, et il est dans e e ce cas souhaitable davoir ` sa disposition des moyens dextension de la place mmoire a e quils occupent. Songez par exemple ` un syst`me de chiers sur disque. Il est hors de a e question dimposer ` un chier davoir une taille xe, et il faut prvoir de le construire a e par morceaux, en ayant la possibilit den rajouter si besoin est. e Cest l` quon trouve un intrt ` introduire des structures de donnes dont la taille a ee a e augmente (ou diminue) de faon dynamique, cest-`-dire ` lexcution (par rapport ` c a a e a statique, au moment de la compilation). Cest le cas des listes, qui vont tre dnies e e rcursivement de la faon image suivante : une liste est soit vide, soit contient une e c e information, ainsi quune autre liste. Pour un chier, on pourra allouer un bloc, qui contiendra les caract`res du chier, ainsi que la place pour un lien vers une autre zone e mmoire potentielle quon pourra rajouter si besoin pour tendre le chier. e e Dans certains langages, comme Lisp, tous les objets du langage (y compris les mthodes) sont des listes, appeles dans ce contexte des S-expressions. e e En Java, le lien entre cellules (ces blocs contenant linformation) ne sera rien dautre que la rfrence du bloc suivant en mmoire. Nous allons dans ce chapitre utiliser essenee e tiellement des listes dentiers pour simplier notre propos, et nous donnerons quelques exemples dutilisation ` la n du chapitre. a 89

90

CHAPITRE 8. LISTES CHA EES IN

8.1
8.1.1

Oprations lmentaires sur les listes e ee


Cration e

Une liste dentiers est une structure abstraite quon peut voir comme une suite de cellules contenant des informations et relies entre elles. Chaque cellule contient un e couple form dune donne, et de la rfrence ` la case suivante, comme dans un jeu e e ee a de piste. Cest le syst`me dexploitation de lordinateur qui retourne les adresses des e objets. Considrons lexemple suivant : le dbut de la liste se trouve ` ladresse @30, ` e e a a laquelle on trouve le couple (4, @10) o` 4 est la donne, @10 ladresse de la cellule u e suivante. On continue en trouvant ` ladresse @10 le couple (12, @20) qui conduit ` (3, a a @0) o` ladresse spciale @0 sert dadresse de n : u e @30 : @20 : @10 : (4, @10) (3, @0 ) (12, @20)

Cette reprsentation est tr`s proche de ce qui se passe en mmoire, mais elle nest e e e pas tr`s visuelle. Aussi a-t-on lhabitude de rajouter des `ches pour lier les cellules, la e e premi`re `che dsignant le point dentre dans le jeu de piste : e e e e

@30 : @20 : @10 :

(4, @10) (3, @0 ) (12, @20)

Il est parfois commode dutiliser ainsi les adresses explicites dans la mmoire pour e bien comprendre les mcanismes qui sont ` luvre. Dans la plupart des cas, et quand e a on a bien saisi le mcanisme, on peut abstraire les listes jusqu` obtenir : e a l = 4 12 3

ou de faon encore plus stylise : c e l = @30:4 -> @10:12 -> @20:3 -> @0 ou sans les adresses : l = 4 -> 12 -> 3 -> null

8.1. OPERATIONS ELEMENTAIRES SUR LES LISTES

91

avec la convention Java que null reprsente ladresse @0. On dit que le case contenant e 4 pointe sur la case contenant 12, etc. Faire des dessins est primordial quand on veut comprendre les structures de donnes e rcursives ! e Comment dclare-t-on une liste cha ee en Java ? Tr`s simplement : une liste est la e n e rfrence dune cellule, cellule qui contient la donne et la rfrence de la cellule suivante. ee e ee On donne ci-dessous la syntaxe et la ralisation de la structure dessine ci-dessus : e e public class Liste{ int contenu; Liste suivant; public Liste(int c, Liste suiv){ this.contenu = c; this.suivant = suiv; } public static void main(String[] args){ Liste l; // l = null = @0 l = new Liste(3, null); // l = @20 l = new Liste(12, l); // l = @10 l = new Liste(4, l); // l = @30 } } La liste l est construite tr`s simplement en rajoutant des lments en tte par e ee e lutilisation de new. Comme dans lcriture des mthodes rcursives, il est impratif de e e e e ne pas oublier le cas de base, ici de bien dmarrer ` partir de la cellule vide. e a Examinons ce qui se passe ligne ` ligne. Quand on cre la premi`re liste (ligne 1), a e e on obtient : l = @20:3 -> @0 La valeur de l est la rfrence en mmoire de la cellule construite, qui vaut ici @20. ee e Apr`s la ligne (2), on obtient : e l = @10:12 -> @20:3 -> @0 ` et l vaut @10. A la n du programme, on a : l = @30:4 -> @10:12 -> @20:3 -> @0 et l contient @30. (1) (2) (3)

8.1.2

Achage

Comment faire pour acher une liste ` lcran ? Il faut reproduire le parcours du a e jeu de piste dans la mmoire : tant quon na pas atteint la case spciale, on ache le e e contenu de la case courante, puis on passe ` la case suivante : a

92

CHAPITRE 8. LISTES CHA EES IN

public static void afficherContenu(Liste l){ Liste ll = l; while(ll != null){ TC.print(ll.contenu + " -> "); ll = ll.suivant; } TC.println("null"); } Dans ce premier programme, nous avons t ultra-conservateurs, en donnant limee pression quil ne faut pas toucher ` la variable l. Bien sr, il nen est rien, puisquon a u passe une rfrence ` une liste, par valeur et non par variable. Ainsi, le code suivant ee a est-il plus idiomatique et compact encore : public static void afficherContenu(Liste l){ while(l != null){ TC.print(l.contenu + " -> "); l = l.suivant; } TC.println("null"); } La liste originale nest pas perdue, puisquon a juste recopi son adresse dans le l e de la mthode afficherContenu. e

8.1.3

Longueur

Nous pouvons maintenant utiliser le mme schma pour calculer la longueur dune e e liste, cest-`-dire le nombre de cellules de la liste. Par convention, la liste vide a pour a longueur 0. Nous pouvons crire itrativement : e e public static int longueur(Liste l){ int lg = 0; while(l != null){ lg++; l = l.suivant; } return lg; } On peut crire galement ce mme calcul de faon rcursive, qui a lavantage dtre e e e c e e plus compacte, mais galement de coller de plus pr`s ` la dnition rcursive de la e e a e e liste : une liste est ou bien vide, ou bien elle contient au moins une cellule, sur laquelle on peut eectuer une opration avant dappliquer la mthode au reste de la liste. Cela e e donne la fonction public static int longueurRec(Liste l){ if(l == null) // test darrt e

8.1. OPERATIONS ELEMENTAIRES SUR LES LISTES return 0; else return 1 + longueurRec(l.suivant); }

93

8.1.4

Le i-`me lment e ee

Le schma de la mthode prcdente est tr`s gnral. On acc`de aux lments dune e e e e e e e e ee liste de faon itrative, en commenant par la tte. Si on veut renvoyer lentier contenu c e c e dans la i-`me cellule (avec i e 0, 0 pour llment en tte de liste), on est oblig de ee e e procder comme suit : e // on suppose i < longueur de la liste public static int ieme(Liste l, int i){ for(int j = 0; j < i; j++) l = l.suivant; return l.contenu; } On peut arguer que le code prcdent nest pas tr`s souple, puisquil demande ` e e e a lutilisateur de savoir par avance que lindice est plus petit que la longueur de la liste, qui nest pas toujours connue. Une mani`re plus prudente de procder est la suivante : e e on dcide que le i-`me lment dune liste est null dans le cas o` i est plus grand que e e ee u la longueur de la liste. On crit alors : e // on suppose i < longueur de la liste public static int ieme(Liste l, int i){ if(l == null) return null; if(i == 0) // on a demand la tte de la liste e e return l.contenu; else ` e // le i eme elment de la liste // est le (i-1) de l.suivant return ieme(l.suivant, i-1); }

8.1.5

Ajouter des lments en queue de liste ee

Le principe de la mthode est le suivant : si la liste est vide, on cre une nouvelle e e cellule que lon retourne ; sinon, on cherche la n de la liste et on rajoute une nouvelle cellule ` la n. a public static Liste ajouterEnQueue(Liste l, int c){ Liste ll; if(l == null) return new Liste(c, null); // l a un suivant

94

CHAPITRE 8. LISTES CHA EES IN ll = l; // cherchons la derni`re cellule e while(ll.suivant != null) ll = ll.suivant; ll.suivant = new Liste(c, null); return l; // pas ll... } Regardons ce qui se passe quand on ajoute 5 ` la n de la liste a

l = @30:4 -> @10:12 -> @20:3 -> null Lexcution pas ` pas donne : e a ll = @30; // ll.suivant = @10 ll = ll.suivant // valeur = @10; ll.suivant = @20 comme ll.suivant vaut null, on remplace alors cette valeur par la rfrence dune ee nouvelle cellule contenant 5 : ll.suivant = @60 et le schma nal est : e l = @30:4 -> @10:12 -> @20:3 -> @60:5 -> null La version itrative de la mthode est complexe ` lire. La version rcursive est e e a e beaucoup plus compacte : public static Liste ajouterEnQueueRec(Liste l, int c){ if(l == null) return new Liste(c, null); else{ // les modifications vont avoir lieu dans la // partie "suivante" de la liste l.suivant = ajouterEnQueueRec(l.suivant, c); return l; } } }

8.1.6

Copier une liste

Une premi`re version de la copie para simple ` crire : e t ae public static Liste copier(Liste l){ Liste ll = null; while(l != null){ ll = new Liste(l.contenu, ll); l = l.suivant; } return ll; }

8.1. OPERATIONS ELEMENTAIRES SUR LES LISTES mais cela copie ` lenvers. Si a l = 1 -> 2 -> 3 -> null la mthode cre : e e ll = 3 -> 2 -> 1 -> null

95

Pour copier ` lendroit, il est beaucoup plus compact de procder rcursivement : a e e si on veut copier l=(c, suivant), on copie ` lendroit la liste suivant ` laquelle a a on rajoute c en tte : e public static Liste copierALEndroit(Liste l){ if(l == null) return null; else return new Liste(l.contenu, copierALEndroit(l.suivant)); } Ex. Ecrire la mthode de faon itrative. e c e

8.1.7

Suppression de la premi`re occurrence e

Le probl`me ici est de fabriquer une liste l2 avec le contenu de la liste l1 dont on e a enlev la premi`re occurrence dune valeur donne, dans le mme ordre que la liste e e e e de dpart. On peut subdiviser le probl`me en deux sous-cas. Le premier correspond ` e e a celui o` la cellule concerne est en tte de liste. Ainsi : u e e l1 = @10:4 -> @30:12 -> @20:3 -> null dont on enl`ve 4 va donner : e l2 = @50:12 -> @60:3 -> null Dans le cas gnral, llment se trouve au milieu : e e ee l1 = @10:4 -> @30:12 -> @20:3 -> null si on enl`ve 3 : e l2 = @50:4 -> @60:12 -> @20 -> null Le principe est de copier le dbut de liste, puis denlever la cellule, puis de copier la e suite. On peut programmer rcursivement ou itrativement (bon exercice !). La mthode e e e rcursive peut scrire : e e public static Liste supprimerRec(Liste l, int c){ if(l == null) return null; if(l.contenu == c) return copierALEndroit(l.suivant);

96 else

CHAPITRE 8. LISTES CHA EES IN

return new Liste(l.contenu, supprimerRec(l.suivant, c)); } Ex. Modier la mthode prcdente de faon ` enlever toutes les occurrences de c dans e e e c a la liste.

8.2

Interlude : tableau ou liste ?

On utilise un tableau quand : on conna la taille (ou un majorant proche) ` lavance ; t a on a besoin daccder aux direntes cases dans un ordre quelconque (acc`s direct e e e a ` t[i]). On utilise une liste quand : on ne conna pas la taille a priori ; t on na pas besoin daccder souvent au i-`me lment ; e e ee on ne veut pas gaspiller de place. Bien sr, quand on ne connait pas la taille ` lavance, on peut se contenter de u a faire voluer la taille du tableau, comme dans lexemple caricatural ci-dessous o` on e u agrandit le tableau dune case ` chaque fois, en crant un nouveau tableau dans lequel a e on transvase le contenu du tableau prcdent : e e public static int[] creerTableau(int n){ int[] t, tmp; t = new int[1]; t[0] = 1; for(int i = 2; i <= n; i++){ tmp = new int[i]; for(int j = 0; j < t.length; j++) tmp[j] = t[j]; tmp[i-1] = i; t = tmp; } return t; } Il est clair quon doit faire l` n allocations de tableaux, et que les recopies successives a vont coter environ n2 oprations. On peut faire un peu mieux, par exemple en allouant u e un tableau de taille 2n ` chaque fois. a

8.3

Partages

Les listes sont un moyen abstrait de parcourir la mmoire. On peut imaginer des e jeux subtils qui nalt`rent pas le contenu des cases, mais la mani`re dont on interpr`te e e e le parcours. Donnons deux exemples.

8.3. PARTAGES

97

8.3.1

Insertion dans une liste trie e

Il sagit ici de crer une nouvelle cellule, quon va intercaler entre deux cellules e existantes. Considrons la liste : e l = @10:4 -> @30:12 -> @20:30 -> null qui contient trois cellules avec les entiers 4, 12, 30. On cherche ` insrer lentier 20, ce a e qui conduit ` crer la liste a e ll = @50:20 -> null On doit insrer cette liste entre la deuxi`me et la troisi`me cellule. Graphiquement, on e e e spare le dbut et la n de la liste en : e e l = @10:4 -> @30:12 -> @20:30 -> null

dcrochant chacun des wagons quon raccroche ` la nouvelle cellule e a l = @10:4 -> @30:12 -> do` : u l = @10:4 -> @30:12 -> @50:20 -> @20:30 -> null On programme selon ce principe une mthode qui ins`re un entier dans une liste dj` e e ea trie dans lordre croissant. Donnons le programme Java correspondant : e // on suppose l trie dans lordre croissant e public static Liste insertion(Liste l, int c){ if(l == null) return new Liste(c, null); // l = [contenu, suivant] if(l.contenu < c){ // on doit insrer c dans suivant e l.suivant = insertion(l.suivant, c); return l; } else // on met c en tte car <= l.contenu e return new Liste(c, l); } Ex. Ecrire une mthode de tri dun tableau en utilisant la mthode prcdente. e e e e [ @50:20 -> ] @20:30 -> null

8.3.2

Inverser les `ches e

Plus prcisment, tant donne une liste e e e e l = @10:(4, @30) -> @30:(12, @20) -> @20:(3, @0) -> @0 on veut modier les donnes de sorte que l dsigne maintenant : e e l = @20:(3, @30) -> @30:(12, @10) -> @10:(4, @0) -> @0

98

CHAPITRE 8. LISTES CHA EES IN

Le plus simple l` encore, est de penser en termes rcursifs. Si l est null, on retourne a e a null. Sinon, l = (c, l.suivant), on retourne l.suivant et on met c ` la n. Le code est assez complexe, et plus simple, une fois nest pas coutume, sous forme itrative : e public static Liste inverser(Liste l){ Liste lnouv = null, tmp; while(l != null){ // lnouv contient le dbut de la liste inverse e e tmp = l.suivant; // on prot`ge e l.suivant = lnouv; // on branche ` lnouv = l; // on met a jour l = tmp; // on reprend avec la suite de la liste } return lnouv; }

8.4

Exemple de gestion de la mmoire au plus juste e

Le probl`me est le suivant. Dans les logiciels de calcul formel comme Maple, on e travaille avec des polynmes, parfois gros, et on veut grer la mmoire au plus juste, o e e cest-`-dire quon ne veut pas stocker les coecients du polynme X 10000 + 1 dans un a o tableau de 10001 entiers, dont deux cases seulement serviront. Aussi adopte-t-on une reprsentation creuse avec une liste de monmes quon interpr`te comme une somme. e o e On utilise ainsi une liste de monmes par ordre dcroissant du degr. Le type de o e e base et son constructeur explicite sont alors : public class PolyCreux{ int degre, valeur; PolyCreux suivant; PolyCreux(int d, int v, PolyCreux p){ this.degre = d; this.valeur = v; this.suivant = p; } } Pour tester et dboguer, on a besoin dune mthode dachage : e e public static void Afficher(PolyCreux p){ while(p != null){ System.out.print("("+p.valeur+")*X"+p.degre); p = p.suivant; if(p != null) System.out.print("+"); } System.out.println();

8.4. EXEMPLE DE GESTION DE LA MEMOIRE AU PLUS JUSTE } On peut alors tester via : public class TestPolyCreux{ public static void main(String[] args){ PolyCreux p, q; p = new PolyCreux(0, 1, null); p = new PolyCreux(17, -1, p); p = new PolyCreux(100, 2, p); PolyCreux.Afficher(p); q = new PolyCreux(1, 3, null); q = new PolyCreux(17, 1, q); q = new PolyCreux(50, 4, q); PolyCreux.Afficher(q); } } Et on obtient : (2)*X100+(-1)*X17+(1)*X0 (4)*X50+(1)*X17+(3)*X1

99

o Ex. Ecrire une mthode Copier qui fabrique une copie dun polynme creux dans le e mme ordre. e Comment proc`de-t-on pour laddition ? On doit en fait crer une troisi`me liste e e e reprsentant la fusion des deux listes, en faisant attention au degr des monmes qui e e o entrent en jeu, et en retournant une liste qui prserve lordre des degrs des monmes. e e o La mthode implantant cette ide est la suivante : e e public static PolyCreux Additionner(PolyCreux p,PolyCreux q){ PolyCreux r; if(p == null) return Copier(q); if(q == null) return Copier(p); if(p.degre == q.degre){ r = Additionner(p.suivant, q.suivant); return new PolyCreux(p.degre, p.valeur + q.valeur,r); } else{ if(p.degre < q.degre){ r = Additionner(p, q.suivant); return new PolyCreux(q.degre, q.valeur, r); } else{ r = Additionner(p.suivant, q); return new PolyCreux(p.degre, p.valeur, r); }

100 } } Sur les deux polynmes donns, on trouve : o e

CHAPITRE 8. LISTES CHA EES IN

(2)*X100+(4)*X50+(0)*X17+(3)*X1+(1)*X0 Ex. Ecrire une mthode qui nettoie un polynme, cest-`-dire qui enl`ve les monmes e o a e o de valeur 0, comme dans lexemple donn ci-dessus. e

Chapitre 9

Arbres
Les arbres sont une structure tr`s classique utilise en informatique, pour ses proe e prits de reprsentation et de compaction. Nous allons donner les proprits de base ee e ee de ces objets.

9.1

Arbres binaires

Un arbre binaire permet de stocker des informations hirarchiques. Un arbre binaire e est dni comme tant soit vide, soit muni dune racine et dventuels ls gauche et e e e droit, qui sont eux-mmes des arbres. Ce sera le cas de larbre de la gure 9.1. e 9 8 6 5 4 7 1 2 0

Fig. 9.1 Exemple darbre. Les lments cercls sont appels des nuds de larbre. Le nud initial est appel ee e e e racine. Les nuds terminaux sont des feuilles. Tous les nuds sauf la racine ont un p`re, les feuilles nont pas de ls. e

9.1.1

Reprsentation en machine e

Dans un tableau Une des reprsentations possibles est celle dun tableau des p`res, ou ` chaque nud e e a on associe son p`re (qui est ncessairement unique). Larbre de la gure 9.1 serait ainsi e e 101

102 cod par e 0 1 2 4 5 6 7 8 9 2 2 9 7 6 8 8 9 1

CHAPITRE 9. ARBRES

o` 1 est un codage du fait que 9 est la racine de larbre. Ce type dimplantation ne u respecte pas lordre gauche/droite et nest pas utilisable dans tous les cas de gure. De plus, il faudrait conna le nombre de nuds de larbre. tre De faon dynamique c Pour conomiser de la place, il est plus pratique de dnir une structure dynamique e e qui implante un arbre. La classe correspondante est public class Arbre{ private int racine; private Arbre gauche, droit; public Arbre(int r, Arbre g, Arbre d){ this.racine = r; this.gauche = g; this.droit = d; } }

9.1.2

Complexit e

On appelle hauteur dun arbre le nombre maximal de nuds trouvs dans un chemin e entre la racine et une feuille. Si larbre contient n nuds, sa hauteur est majore par e n (cas dun arbre liforme une liste). La borne infrieure est log2 n, car cest la e hauteur dun arbre complet, dont toutes les feuilles sont ` la mme hauteur. Il existe a e des techniques pour quilibrer les arbres, mais nous ne les dtaillerons pas ici. Il faut e e savoir quen moyenne, une permutation alatoire de n lments est stockable dans un e ee arbre de hauteur O(log2 n).

9.1.3

Les trois parcours classiques

On dnit classiquement trois parcours darbre, qui permettent de considrer chaque e e nud dans un ordre particulier. Lordre prxe consid`re dabord la racine, puis les deux e e sous-arbres gauche et droit dans cet ordre ; lordre inxe parcourt le sous-arbre gauche, la racine, le sous-arbre droit ; lordre postxe consid`re le sous-arbre gauche, le droit, e puis la racine. Le code Java correspondant est : public static void afficherPrefixe(Arbre a){ if(a != null){ System.out.print(a.racine); afficherPrefixe(a.gauche); afficherPrefixe(a.droit); } }

9.2. ARBRES GENERAUX

103

public static void afficherInfixe(Arbre a){ if(a != null){ afficherInfixe(a.gauche); System.out.print(a.racine); afficherInfixe(a.droit); } } public static void afficherPostfixe(Arbre a){ if(a != null){ afficherPostfixe(a.gauche); afficherPostixe(a.droit); System.out.print(a.racine); } } } La classe public class TesterArbre{ public static void main(String[] args){ Arbre a = new Arbre(1, new Arbre(0, new Arbre(3, null, null), null), new Arbre(2, null, null)); Arbre.afficherInfixe(a); System.out.println(); Arbre.afficherPrefixe(a); System.out.println(); Arbre.afficherPostfixe(a); System.out.println(); } } nous achera : 1032 3012 3021

9.2

Arbres gnraux e e

Dans certaines applications, il y a plus que deux ls associs ` un nud. e a

9.2.1

Dnitions e

Un arbre gnral est dni comme tant ou bien une structure contenant une e e e e racine, ainsi quun ensemble darbres (S1 , S2 , . . . , Sn ) attachs ` la racine r. On peut le e a noter symboliquement : A = (r, S1 , S2 , . . . , Sn ).

104

CHAPITRE 9. ARBRES

9.2.2

Reprsentation en machine e

On peut par exemple grer un tableau de ls en lieu et place de gauche et droit, ou e encore une liste de ls. On peut galement utiliser la reprsentation ls gauche fr`re droit qui de facto e e e utilise un arbre binaire. Par exemple, 12 10 5 6 8 11

sera cod en machine sous la forme de larbre binaire : e 12


c

10 E 11
c

5 E6 E8 Nous laissons en exercice lcriture des fonctions qui permettent de coder les arbres e n-aires de cette faon, en particulier les parcours. c

9.3
9.3.1

Exemples dutilisation
Expressions arithmtiques e

On consid`re ici des expressions arithmtiques faisant intervenir des variables a..z, e e des entiers, des oprateurs binaires +, , , /, comme par exemple lexpression x + e y/(2z + 1) + t. Toute expression de ce type peut tre reprsente par un arbre binaire e e e (de faon non unique), cf. gure 9.2, ou un arbre n-aire (comme dans Maple, cf. gure c 9.3. Objets de base, premi`re oprations e e Le dbut de la classe est e public class Expression{ char type; int n; Expression filsg, filsd; public Expression(char type, int n, Expression fg, Expression fd){ this.type = type; this.n = n;

9.3. EXEMPLES DUTILISATION + + x y 2 z / + 1 t

105

Fig. 9.2 Arbre binaire pour lexpression x + y/(2z + 1) + t. this.filsg = fg; this.filsd = fd; } public static void afficherPrefixe(Expression e){ if(e != null){ System.out.print("("); if(e.type == I) System.out.print(e.n + " "); else System.out.print(e.type + " "); afficherPrefixe(e.filsg); afficherPrefixe(e.filsd); System.out.print(")"); } } } Passons aux quatre oprations : e public static Expression additionner(Expression e1, Expression e2){ return new(+, 0, e1, e2); } public static Expression soustraire(Expression e1, Expression e2){ return new(-, 0, e1, e2); }

106 + x y 2 z / + 1 t

CHAPITRE 9. ARBRES

Fig. 9.3 Arbre n-naire pour lexpression x + y/(2z + 1) + t.

public static Expression multiplier(Expression e1, Expression e2){ return new(*, 0, e1, e2); } public static Expression diviser(Expression e1, Expression e2){ return new(/, 0, e1, e2); } Un exemple dutilisation sera : public class TestExpr{ public static void main(String[] args){ Expression e, e1; e1 = new Expression(I, 2, null, null); e = new Expression(z, 0, null, null); e = Expression.multiplier(e1, e); e1 = new Expression(I, 1, null, null); e = Expression.additionner(e, e1); e1 = new Expression(y, 0, null, null); e = Expression.diviser(e1, e); e1 = new Expression(x, 0, null, null); e = Expression.additionner(e1, e); e1 = new Expression(t, 0, null, null); e = Expression.additionner(e, e1); Expression.afficherPrefixe(e); } } ce qui nous donne

9.3. EXEMPLES DUTILISATION (+ (+ (x )(/ (y )(+ (* (2 )(z ))(1 ))))(t )) Substitution dexpressions

107

Lintrt des arbres appara clairement d`s quon veut substituer une variable par ee t e une expression quelconque. En clair, il sut de brancher larbre de substitution partout o` la variable appara u t. public static void substituer(Expression e, char v, Expression f){ if(e != null){ if(e.type == v){ e.type = f.type; e.n = f.n; e.filsg = f.filsg; e.filsd = f.filsd; } substituer(e.filsg, v, f); substituer(e.filsd, v, f); } } Lexemple e1 = new Expression(x, 0, null, null); e1 = Expression.multiplier(e1, e1); Expression.substituer(e, z, e1); nous donne (+ (+ (x )(/ (y )(+ (* (2 )(* (x )(x )))(1 ))))(t )) Remplacer une variable par une valeur sappelle instantiation. Cest une variante de la substitution gnrale : e e public static void instancier(Expression e, char v, int n){ if(e != null){ if(e.type == v){ e.type = I; e.n = n; } instancier(e.filsg, v, n); instancier(e.filsd, v, n); } } Lexcution de e Expression.instancier(e, x, 5); Expression.instancier(e, y, 2); Expression.instancier(e, t, 3); Expression.afficherPrefixe(e);

108 donne

CHAPITRE 9. ARBRES

(+ (+ (5 )(/ (2 )(+ (* (2 )(* (5 )(5 )))(1 ))))(3 )) Il ne nous reste plus qu` crire le code dvaluation dune expression numrique : ae e e public static int evaluer(Expression e){ if(e == null) return 0; switch(e.type){ case I: return e.n; case +: return evaluer(e.filsg) + evaluer(e.filsd); case *: return evaluer(e.filsg) * evaluer(e.filsd); case -: return evaluer(e.filsg) - evaluer(e.filsd); case /: return evaluer(e.filsg) / evaluer(e.filsd); default: System.out.println("Erreur"); } return 0; }

9.3.2

Arbres binaires de recherche

Nous allons traiter un exemple important, celui de larbre binaire de recherche, soit A = (r, G, D) vriant la proprit que tout lment du sous-arbre gauche G est e ee ee plus petit que r, lui-mme plus petit que tout lment du sous-arbre droit D. Un e ee parcourt inxe dun tel arbre fournit la liste des valeurs des nuds des arbres dans lordre croissant. Si larbre est quilibr, le temps dinsertion ou de recherche sera en e e O(log2 n), cest-`-dire rapide. a Nous allons modier notre classe de base pour grer de tels arbres. e public class ABR{ private int racine; private ABR gauche, droit; public ABR(int r, ABR g, ABR d){ this.racine = r; this.gauche = g; this.droit = d; } public static ABR inserer(ABR a, int x){ if(a == null) // on cre un nouvel ABR e return new ABR(x, null, null);

9.3. EXEMPLES DUTILISATION else if(x <= a.racine){ // on ins`re dans le sous-arbre gauche e a.gauche = inserer(a.gauche, x); return a; } else // x > a.racine; // on ins`re dans le sous-arbre droit e a.droit = inserer(a.droit, x); return a; } }

109

La seule nouveaut est la fonction dinsertion, qui est plus complexe. Celle-ci cherche e a ` insrer en respectant la notion dABR. Le cas de base de la rcursion est la cration e e e dune feuille. Quand larbre a une racine, on eectue linsertion du ct o` x doit se oe u trouver pour respecter la proprit. Cette fonction scrit beaucoup plus facilement de ee e faon rcursive que de mani`re itrative. c e e e ` A titre dexemple, nous allons crer un ABR ` partir du tableau e a int[] t = {26, 51, 45, 57, 95, 87, 1, 67, 96, 91}; qui va nous permettre de simuler une insertion dynamique de ses lments, lun apr`s ee e lautre. Le premier nud est 26

On ins`re alors 51 dans le sous-arbre droit de 26 : e 26 51

Pour insrer le 45, on part dans le sous-arbre droit de 26, puis dans le sous-arbre e gauche de 51 : 26 51 45

On proc`de ainsi de proche en proche pour aboutir nalement ` la gure 9.4. e a

110 26 1 45 51 57

CHAPITRE 9. ARBRES

95 87 67 91 96

Fig. 9.4 Exemple darbre binaire de recherche.

9.3.3

Les tas

Nous allons tudier ici une structure de donnes particuli`re qui garantit que le e e e temps de recherche du plus grand lment dun tableau de n lments se fasse en temps ee ee O(log2 n). On dit quun tableau t[0..TMAX] poss`de la proprit de tas si pour tout i > e ee 0, t[i] (un parent) est plus grand que ses deux enfants gauche t[2*i] et droit t[2*i+1]. Nous supposons ici que les tas sont des tas dentiers, mais il serait facile de modier cela. Le tableau t = {0, 9, 8, 2, 6, 7, 1, 0, 3, 5, 4} (rappelons que t[0] ne nous sert ` rien ici) a la proprit de tas, ce que lon vrie ` laide du dessin suivant : a ee e a 9 8 6 3 5 4 7 1 2 0

Fig. 9.5 Exemple de tas. Bien que nous puissions nous contenter dutiliser un tableau ordinaire, il est plus intressant dutiliser une classe spciale, que nous appelerons Tas, et qui nous permettra e e de ranger les lments de faon dynamique en grant un indice n, qui dsignera le ee c e e nombre dlments prsents dans le tas : ee e public class Tas{ private int[] t; // la partie utile est t[1..tmax]

9.3. EXEMPLES DUTILISATION e private int n; // indice du dernier elment public static Tas creer(int tmax){ Tas tas = new Tas(); tas.t = new int[tmax+1]; tas.n = 0; return tas; } public boolean estVide(){ return tas.n == 0; } }

111

La premi`re fonction que lon peut utiliser est celle qui teste si un tas a bien la proprit e ee quon attend : public static boolean estTas(Tas tas){ for(int i = tas.n; i > 1; i--) if(tas.t[i] > tas.t[i/2]) return false; return true; } Proposition 2 Soit n 1 et t un tas. On dnit la hauteur du tas (ou de larbre) e comme lentier h tel que 2h n < 2h+1 . Alors (i) Larbre a h + 1 niveaux, llment t[1] se trouvant au niveau 0. ee (ii) Chaque niveau, 0 < h, est stock dans t[2 ..2 +1 [ et comporte ainsi 2 e lments. Le dernier niveau ( = h) contient les lments t[2h ..n]. ee ee (iii) Le plus grand lment se trouve en t[1]. ee Exercice. Ecrire une fonction qui ` lentier i a n associe son niveau dans larbre.

On se sert dun tas pour implanter facilement une le de priorit, qui permet de e grer des clients qui arrivent, mais avec des priorits qui sont direntes, contrairement e e e ` au cas de la poste. A tout moment, on sait quon doit servir le client t[1]. Il reste a e ` dcrire comment on rorganise le tas de sorte qu` linstant suivant, le client de e a plus haute priorit se retrouve en t[1]. On utilise de telles structures pour grer les e e impressions en Unix, ou encore dans lordonnanceur du syst`me. e Dans la pratique, le tas se comporte comme un lieu de stockage dynamique o` u entrent et sortent des lments. Pour simuler ces mouvements, on peut partir dun tas ee dj` form t[1..n] et insrer un nouvel lment x. Sil reste de la place, on le met ea e e ee temporairement dans la case dindice n + 1. Il faut vrier que la proprit est encore e ee satisfaite, ` savoir que le p`re de t[n+1] est bien suprieur ` son ls. Si ce nest pas le a e e a cas, on les permute tous les deux. On na pas dautre test ` faire, car au cas o` t[n+1] a u aurait eu un fr`re, on savait dj` quil tait infrieur ` son p`re. Ayant permut p`re et e ea e e a e e e ls, il se peut que la proprit de tas ne soit toujours pas vrie, ce qui fait que lon ee e e doit remonter vers lanctre du tas ventuellement. e e Illustrons tout cela sur un exemple, celui de la cration dun tas ` partir du tableau : e a

112

CHAPITRE 9. ARBRES int[] a = {6, 4, 1, 3, 9, 2, 0, 5, 7, 8};

Le premier tas est facile : 6 Llment 4 vient naturellement se mettre en position comme ls gauche de 6 : ee 6 4 et apr`s insertion de 1 et 3, on obtient : e 6 4 3 Ces lments sont stocks dans le tableau ee e i 1 2 3 4 t[i] 6 4 1 3 Pour sen rappeler, on balaie larbre de haut en bas et de gauche ` droite. a On doit maintenant insrer 9, ce qui dans un premier temps nous donne e 6 4 3 9 1 1

On voit que 9 est plus grand que son p`re 4, donc on les permute : e 6 9 3 4 1

Ce faisant, on voit que 9 est encore plus grand que son p`re, donc on le permute, et e cette fois, la proprit de tas est bien satisfaite : ee

9.3. EXEMPLES DUTILISATION 9 6 3 4 1

113

Apr`s insertion de tous les lments de t, on retrouve le dessin de la gure 9.5. e ee Le programme Java dinsertion est le suivant : public static boolean inserer(Tas tas, int x){ if(tas.n >= tas.t.length) // il ny a plus de place return false; // il y a encore au moins une place tas.n += 1; tas.t[tas.n] = x; monter(tas, tas.n); return true; } et utilise la fonction de remonte : e // on vrifie que la proprit de tas est e e e // vrifie a partir de tas.t[k] e e ` public static void monter(Tas tas, int k){ int v = tas.t[k]; while((k > 1) && (tas.t[k/2] <= v)){ ` // on est a un niveau > 0 et // le p`re est <= fils e // le p`re prend la place du fils e tas.t[k] = tas.t[k/2]; k /= 2; } // on a trouv la place de v e tas.t[k] = v; } Pour transformer un tableau en tas, on utilise alors : public static Tas deTableau(int[] a){ Tas tas = creer(a.length); for(int i = 0; i < a.length; i++) inserer(tas, a[i]); return tas; } Pour parachever notre travail, il nous faut expliquer comment servir un client. Cela revient ` retirer le contenu de la case t[1]. Par quoi la remplacer ? Le plus simple est a

114

CHAPITRE 9. ARBRES

de mettre dans cette case t[n] et de vrier que le tableau prsente encore la proprit e e ee de tas. On doit donc descendre dans larbre. Reprenons lexemple prcdent. On doit servir le premier client de numro 9, ce qui e e e conduit ` mettre au sommet le nombre 4 : a

8 6 3 5 4 7 1

2 0

On doit maintenant faire redescendre 4 : 4 8 6 3 5 7 1 2 0

ce qui conduit ` lchanger avec son ls gauche : a e 8 4 6 3 5 7 1 2 0

puis on lchange avec 7 pour obtenir nalement : e 8 7 6 3 5 4 1 2 0

9.3. EXEMPLES DUTILISATION La fonction de service est : public static int tacheSuivante(Tas tas){ int tache = tas.t[1]; tas.t[1] = tas.t[tas.n]; tas.n -= 1; descendre(tas, 1); return tache; } qui appelle : public static void descendre(Tas tas, int k){ int v = tas.t[k], j; while(k <= tas.n/2){ // k a au moins 1 fils gauche j = 2*k; if(j < tas.n) // k a un fils droit if(tas.t[j] < tas.t[j+1]) j++; // ici, tas.t[j] est le plus grand des fils if(v >= tas.t[j]) break; else{ // on echange p`re et fils e tas.t[k] = tas.t[j]; k = j; } } // on a trouv la place de v e tas.t[k] = v; }

115

Notons quil faut grer avec soin le probl`me de lventuel ls droit manquant. De mme, e e e e on nchange pas vraiment les cases, mais on met ` jour les cases p`res ncessaires. e a e e Proposition 3 La complexit des procdures monter et descendre est O(h) ou ene e core O(log2 n). Dmonstration : on parcourt au plus tous les niveaux de larbre ` chaque fois, ce qui e a fait au plus O(h) mouvements. 2 Pour terminer cette section, nous donnons comme dernier exemple dapplication un nouveau tri rapide, appel tri par tas (en anglais, heapsort). Lide est la suivante : e e quand on veut trier le tableau t, on peut le mettre sous la forme dun tas, ` laide a de la procdure deTableau dj` donne. Celle-ci aura un cot O(n log2 n), puisquon e ea e u doit insrer n lments avec un cot O(log2 n). Cela tant fait, on permute le plus e ee u e

116

CHAPITRE 9. ARBRES

grand lment t[1] avec t[n], puis on rorganise le tas t[1..n-1], avec un cot ee e u O(log2 (n 1)). Finalement, le cot de lalgorithme sera O(nh) = O(n log2 n). Ce tri est u assez sduisant, car son cot moyen est gal ` son cot le pire : il ny a pas de tableaux e u e a u diciles ` trier. La procdure Java correspondante est : a e public static void triParTas(int[] a){ Tas tas = deTableau(a); for(int k = tas.n; k > 1; k--){ // a[k..n[ est dj` tri, e a e // on trie a[0..k-1] // t[1] contient max t[1..k] = max a[0..k-1] a[k-1] = tas.t[1]; tas.t[1] = tas.t[k]; tas.n -= 1; descendre(tas, 1); } a[0] = tas.t[1]; } Nous verrons dautres tris au chapitre 11. Cette utilisation dun tableau comme reprsentant un arbre complet est couramment e utilise en calcul formel, par exemple dans les arbres de produit. Nous renvoyons ` e a [GG99] pour cela.

Chapitre 10

Graphes
Les graphes sont un moyen commode de reprsenter des liens entre des donnes. e e Nous allons donner ici un aperu de ces objets. Les algorithmes labors seront vus c e e dans la suite des cours.

10.1

Dnitions e

Un graphe G = (S, A) est donn par un ensemble S de sommets et un ensemble A e darcs, A S S. On visualise en gnral un graphe par un dessin dans le plan. Les sommets sont e e reprsents par des points, les arcs par des `ches. Le graphe G1 de la gure 10.1 a e e e pour ensemble de sommets S = {a, b, c, d, e, f, g, h, i, j, k, l, m}, et ensemble dartes e A = {(a, b), (b, c), (c, b), (c, d), (d, a), (a, e), (e, f ), (f, b), (f, g), (b, g), (g, h), (h, c), (h, i), (g, i), (i, c), (i, d), (d, j), (j, e), (k, f ), (k, g), (k, i), (k, j), (m, l)}. b f a e j d k i l m g h c

Fig. 10.1 Le graphe G1 . Il est important de noter quil nexiste pas de dessin canonique dun graphe. Par exemple, les deux dessins de la gure 10.2 correspondent au mme graphe. e Un arc = (a, b) est orient de a vers b ; a pour origine a et destination b ; larc e est incident ` a ainsi qu` b. On dira galement que a est un prdcesseur de b, et b a a e e e un successeur de a ; a et b seront galement dits adjacents. Larc (x, x) est une boucle. e 117

118 b a d c b c

CHAPITRE 10. GRAPHES

Fig. 10.2 Deux dessins dun mme graphe. e

Un graphe est simple sil ne comporte pas de boucles. Le graphe de la gure 10.1 est simple. Le graphe G2 de la gure 10.3 ne lest pas. b a d Fig. 10.3 Le graphe G2 . On peut enrichir un graphe en considrant des quantits associes ` chaque arc, ou e e e a a ` chaque sommet. Penser par exemple ` la carte de circulation dune ville, avec ses sens a interdits et les longueurs des rues. On parlera darcs valus, cest-`-dire quon utilisera e a une fonction : A N qui donnera le poids ou la valeur associe ` un arc. Cest le cas e a du graphe G3 de la gure 10.4 dans lequel on a ajout des poids sur chaque arc. e 1 a 5 4 d 3 b 2 c c

Fig. 10.4 Le graphe valu G3 . e

Graphes non orients e Les graphes les plus gnraux que nous considrerons sont a priori orients, ce qui e e e e veut dire quun arc indique un sens de lecture ou de dessin. Autrement dit, si larc (a, b) existe, cela nimplique pas lexistence de larc (b, a) dans le graphe. Il peut arriver que pour tout arc (a, b), son inverse (b, a) existe galement. On dit que le graphe G e est non orient. Dans ce cas, on utilise le terme arte au lieu darc et on note parfois e e

10.2. REPRESENTATION EN MACHINE

119

{a, b} larte reliant a ` b1 . On dessine alors des traits au lieu de `ches, comme dans e a e lexemple du graphe G4 de la gure 10.5. b a d Fig. 10.5 Le graphe G4 . c

10.2

Reprsentation en machine e

Les dessins que nous avons faits dans la section prcdente sont commodes pour e e faire des exemples et des raisonnements ` la main, mais ils ne sont pas utilisables en a machine. Dessiner dans le plan un graphe donn est en soi une tche algorithmique et e a pratique relativement ardue. Donc nous devons trouver une autre reprsentation plus e commode. Le probl`me se rduit ` reprsenter S et A S S en machine. Sans perte de e e a e gnralit, on peut se ramener au cas o` S = {0, 1, . . . , n 1} si n = |S|. Dans la e e e u pratique, il sera toujours possible de coder les lments du graphe par un tel ensemble ee dentiers, quitte ` utiliser du hachage par exemple (voir les polys des cours prcdents a e e ou [CLR90]). Passons au probl`me de la reprsentation de A. Avec notre dnition de e e e graphe, il est clair que le cardinal de A est born par n2 . Si m = |A| est proche de e n2 , on parlera de graphe dense. Si m = o(n2 ), on parlera de graphe creux. Il est tr`s e frquent en pratique davoir ` traiter des graphes creux (lments nis, carte routi`re, e a ee e graphe dInternet, etc.). Les algorithmes que nous tudierons auront une complexit e e qui pourra bien souvent tre dcrite en terme de n et m, ce qui est toujours intressant e e e quand m est loin de sa valeur maximale thorique O(n2 ). e

10.2.1

Reprsentation par une matrice e

Cest la faon la plus simple de reprsenter lensemble A. On se donne donc une c e matrice M de taille n n. Une telle matrice est appele matrice dadjacence du graphe. e Il existe essentiellement deux choix pour le type de la matrice. Le premier choix consiste ` prendre une matrice de boolens tels que Mi,j est vrai si a e e et seulement si (i, j) A. Pour le graphe G2 de la gure 10.3, on numrote les sommets a, b, c, d selon la correspondance a b c d 0 1 2 3
1 Nous considrerons des paires au sens propre, cest-`-dire quun graphe non orient ne poss`de pas e a e e de boucles.

120

CHAPITRE 10. GRAPHES

puis on construit la matrice boolenne (o` nous avons mis T pour vrai et omis les e u valeurs fausses) : i, j 0 1 2 3 0 T 1 T 2 T T 3 T Le deuxi`me choix conduit a utiliser un tableau dentiers (ou de ottants) quand e ` les arcs de G sont valus. On pose alors Mi,j = ((i, j)) et on utilise une valeur par e dfaut quand larc (i, j) nexiste pas (par exemple une valeur plus grande que toutes e les valeurs de ). Pour le graphe G3 de la gure 10.4, on trouve : i, j 0 1 2 3 0 1 2 1 2 5 3 4 3

Dans le cas o` G est non orient, la matrice M est une matrice symtrique. u e e

10.2.2

Reprsentation par un tableau de listes e

Le stockage ` laide de matrices prend une place proportionnelle ` n2 . Quand m = a a |A| est petit par rapport ` n2 , cela reprsente une perte de place parfois importante a e (ainsi quune perte de temps). Do` lide dutiliser une reprsentation creuse, par u e e exemple sous la forme dun tableau de listes L. La liste Li pour i S, contiendra la liste des successeurs de i dans le graphe, cest-`-dire a Li = (j S, (i, j) A). En utilisant une reprsentation graphique des listes, lexemple du graphe G2 pourra e conduire ` : a L[0] L[1] L[2] L[3] = = = = (1) (2) (2, 3) (0)

Il est important de noter, encore une fois, quune telle reprsentation nest nullement e canonique, car on peut dcider de ranger les voisins dans nimporte quel ordre. De e mani`re plus gnrale, on peut tout ` fait reprsenter notre graphe sous forme creuse e e e a e comme un tableau densembles, les ensembles pouvant avoir une reprsentation et des e proprits varies. ee e La place mmoire ncessite par une telle reprsentation est de |S| ensembles. Dans e e e e le cas de listes cha ees, chaque ensemble demande une place O(|L[i]|) (par exemple n le contenu dune cellule et dun pointeur sur la suivante). En tout, on aura un stockage en O(n + m), donc linaire. En fonction des probl`mes, on pourra trouver des e e reprsentations parfois plus adaptes. e e

10.3. RECHERCHE DES COMPOSANTES CONNEXES

121

10.3

Recherche des composantes connexes

La dcomposition dun graphe non orient en composantes connexes permet de rae e mener un probl`me gnral ` un probl`me composante par composante. Deux sommets e e e a e sont dans la mme composante connexe si et seulement sil existe un chemin menant e de lun ` lautre. a 8 9 10 11

Fig. 10.6 Le graphe exemple. Considrons le graphe de la gure 10.6. Lide de recherche des composantes est e e simple : on part dun sommet et on explore tous les sommets possibles ` partir de ce a premier sommet. Pour le graphe exemple, on part de 0, qui a un voisin 8. Les voisins de 8 sont {0, 1, 2, 3, 4, 5, 9}. On conna dj` 0, donc ce nest pas la peine de le visiter ` t e a a nouveau. Les sommets 1, 2, 3, 4 nont pas de voisins autre que 8, donc ils napportent rien ` la composante. Le sommet 5 a pour voisins 6 et 9 ; 9 na pas de voisins non a visits ; 6 a pour voisin 7, ce qui termine lexamen de la composante. e Commenons par le programme de test, qui va nous guider dans le choix des autres c classes. Ce programme doit crer un graphe ` partir dun tableau dartes, puis acher e a e le graphe, puis acher ses composantes connexes : public class TestGraphe{ public static void main(String[] args){ int[][] aretes = {{10, 11}, {0, 8}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 9}, {8, 5}, {9, 5}, {5, 6}, {6, 7}}; int n = 12; Graphe G = new Graphe(n); for(int i = 0; i < aretes.length; i++){ G.ajouterArc(aretes[i][0], aretes[i][1]); G.ajouterArc(aretes[i][1], aretes[i][0]); } System.out.println("Voici le graphe\n"+G); System.out.println("Voici les composantes connexes"); G.afficherComposantesConnexes(); } }

122

CHAPITRE 10. GRAPHES

Ceci nous sugg`re que la classe Graphe na pas besoin dtre tr`s grosse, et quon e e e na pas besoin de conna tre son implantation pour sen servir. Nous allons choisir ici une reprsentation par tableau de listes, une liste tant semblable ` celles dj` vues au e e a ea chapitre 8. public class Liste { public int i; public Liste suivant; public Liste(Liste l, int ii){ this.i = ii; this.suivant = l; } public static Liste enlever(Liste l, int ii){ if(l == null) return null; if(l.i == ii) return l.suivant; l.suivant = enlever(l.suivant, ii); return l; } public String toString(){ if(this == null) return ""; else return this.i + " -> " + this.suivant; } } Les fonctions sont classiques, et il est ` noter que la fonction qui enl`ve un entier a e est couteuse, car proportionnelle ` la longueur de la liste. Le dbut de la classe Graphe a e est alors : public class Graphe { private Liste[] voisins; public Graphe(int n){ this.voisins = new Liste[n]; } public void ajouterArc(int s, int t){ this.voisins[s] = new Liste(this.voisins[s], t); } public String toString(){ String str = ""; for(int i = 0; i < this.voisins.length; i++)

10.3. RECHERCHE DES COMPOSANTES CONNEXES if(this.voisins[i] != null){ str += "S" + i + " = "; str += this.voisins[i].toString() + "\n"; } return str; } }

123

Il nous reste ` programmer la recherche de composantes connexes. Pour ce faire, a nous allons introduire une classe Ensemble qui permet les oprations suivantes : ajout e (add), retrait (remove), retrait du premier lment (removeFirst), test de vacuit ee e (isEmpty), contient (has). On proc`de composante par composante, en prenant dans e la liste des sommets du graphe, un sommet non encore utilis. Une composante sera e e galement de type Ensemble. Litration principale porte sur la Liste avisiter e qui g`re de faon dynamique les nouveaux amis qui apparaissent lors de lexploration. e c public void afficherComposantesConnexes(){ int n = this.voisins.length; Ensemble sommets = new Ensemble(n); int ncc = 0; // on remplit la liste des sommets for(int i = 0; i < n; i++) sommets.add(i); while(! sommets.isEmpty()){ Ensemble cc = new Ensemble(n); // la composante int s = sommets.removeFirst(); Liste avisiter = new Liste(null, s); while(avisiter != null){ ` // tant quil reste des sommets a explorer int t = avisiter.i; avisiter = avisiter.suivant; if(! cc.has(t)){ // t est nouveau dans la composante cc.add(t); // on rajoute ses voisins non // encore visits e Liste l = this.voisins[t]; while(l != null){ if(sommets.has(l.i)){ e // l.i na pas encore et visit e avisiter = new Liste(avisiter, l.i); sommets.remove(l.i); } l = l.suivant; } } } ncc++;

124

CHAPITRE 10. GRAPHES System.out.println("Composante "+ncc+": "+cc); } }

Il nous reste ` coder la classe Ensemble de faon que les oprations soient les plus a c e rapides possibles (typiquement en O(1)). Cela sugg`re dutiliser un tableau de boolens e e qui indique si un sommet est dj` utilis ou non. Mais cela ne sut pas, car on doit ea e pouvoir extraire un sommet non encore utilis. Do` lutilisation conjointe dune liste, e u mme avec un temps de gestion qui est parfois long (` cause de remove). Cela nous e a donne : public class Ensemble { private boolean[] utilise; private Liste li; public Ensemble(int n){ boolean[] b = new boolean[n]; for(int i = 0; i < n; i++) b[i] = false; this.utilise = b; this.li = null; } public boolean isEmpty(){ return this.li == null; } public boolean has(int i){ return this.utilise[i]; } public void add(int i){ if(! this.utilise[i]){ this.utilise[i] = true; this.li = new Liste(this.li, i); } } public void remove(int i){ this.utilise[i] = false; // cette etape est couteuse this.li = Liste.enlever(this.li, i); } public int removeFirst(){ int f = this.li.i; this.li = this.li.suivant;

10.4. CONCLUSION this.utilise[f] = true; return f; } public String toString(){ String str = "{"; Liste l = this.li; while(l != null){ str += l.i; l = l.suivant; if(l != null) str += ", "; } str += "}"; return str; } }

125

Exercice. Excuter les programmes ci-dessous ` la main sur lexemple donn ` la gure e a ea 10.6. Pour disposer dune gestion pour laquelle remove est plus rapide, il faut utiliser dautres structures de donnes moins intuitives, qui permettent ditrer sur sommets e e rapidement. Nous en donnons un exemple ici (dautres existent). Exercice (*). On introduit la structure de donnes suivante (inspire des travaux de e e e Mairson [Mai77]), qui permet de faire des oprations sur les entiers entre 1 et n en grant une liste du type e 0 <-> 1 <-> 2 <-> ... <-> n <-> n+1 avec 0 et n + 1 des sentinelles. On dnit deux tableaux rlink et llink, tels que e rlink[i] = i + 1 et llink[i] = i 1 au dpart du programme. Quand on veut supprimer e llment i de la structure, on est dans la situation ee i1 <-> i <-> i2 et la suppression de i va entra ner i1 <-> i2 Montrer que cette structure de donnes peut tre utilise pour faire rapidement les e e e oprations ncessaires au parcours des sommets dans afficherComposantesConnexes e e (variable sommets). Ecrire la classe Ensemble2 correspondante (avec les mmes fonce tions que pour Ensemble sans la fonction add inutile) et en dduire la fonction e afficherComposantesConnexes2.

10.4

Conclusion

Nous venons de voir comment stocker des informations prsentant des liens entre e elles. Ce nest quune partie de lhistoire. Dans les bases de donnes, on stocke des e

126

CHAPITRE 10. GRAPHES

informations pouvant avoir des liens compliqus entre elles. Pensez ` une carte des e a villes de France et des routes entre elles, par exemple. Ceci fera lobjet dautres cours du dpartement dans les annes qui suivent. e e

Chapitre 11

Ranger linformation. . . pour la retrouver


Linformatique permet de traiter des quantits gigantesques dinformation et dj`, e ea on dispose dune capacit de stockage susante pour archiver tous les livres crits. e e Reste ` ranger cette information de faon ecace pour pouvoir y accder facilement. a c e On a vu comment construire des blocs de donnes, dabord en utilisant des tableaux, e puis des objets. Cest le premier pas dans le stockage. Nous allons voir dans ce chapitre quelques-unes des techniques utilisables pour aller plus loin. Dautres mani`res de faire e seront prsentes dans le cours INF 421. e e

11.1

Recherche en table

Pour illustrer notre propos, nous considrerons deux exemples principaux : la correce tion dorthographe (un mot est-il dans le dictionnaire ?) et celui de lannuaire (rcuprer e e une information concernant un abonn). e

11.1.1

Recherche linaire e

La mani`re la plus simple de ranger une grande quantit dinformation est de la e e mettre dans un tableau, quon aura ` parcourir ` chaque fois que lon cherche une a a information. Considrons le petit dictionnaire contenu dans la variable dico du programme e ci-dessous : public class Dico{ public static boolean estDans(String[] dico, String mot){ boolean estdans = false; for(int i = 0; i < dico.length; i++) if(mot.compareTo(dico[i]) == 0) estdans = true; return estdans; } 127

128

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

public static void main(String[] args){ String[] dico = {"maison", "bonjour", "moto", "voiture", "artichaut", "Palaiseau"}; if(estDans(dico, args[0])) System.out.println("Le mot est prsent"); e else System.out.println("Le mot nest pas prsent"); e } } Pour savoir si un mot est dans ce petit dictionnaire, on le passe sur la ligne de commande par unix% java Dico bonjour On parcourt tout le tableau et on teste si le mot donn, ici pris dans la variable e args[0] se trouve dans le tableau ou non. Le nombre de comparaisons de cha nes est ici gal au nombre dlments de la table, soit n, do` le nom de recherche linaire. e ee u e Si le mot est dans le dictionnaire, il est inutile de continuer ` comparer avec les a autres cha nes, aussi peut-on arrter la recherche ` laide de linstruction break, qui e a permet de sortir de la boucle for. Cela revient ` crire : ae for(int i = 0; i < dico.length; i++) if(mot.compareTo(dico[i]) == 0){ estdans = true; break; } Si le mot nest pas prsent, le nombre doprations restera le mme, soit O(n). e e e

11.1.2

Recherche dichotomique

Dans le cas o` on dispose dun ordre sur les donnes, on peut faire mieux, en u e rorganisant linformation suivant cet ordre, cest-`-dire en triant, sujet qui formera la e a section suivante. Supposant avoir tri le dictionnaire (par exemple avec les mthodes e e de la section 11.2), on peut maintenant y chercher un mot par dichotomie, en adaptant le programme dj` donn au chapitre 7, et que lon trouvera ` la gure 11.1. Rappelons ea e a que linstruction x.compareTo(y) sur deux cha nes x et y retourne 0 en cas dgalit, e e un nombre ngatif si x est avant y dans lordre alphabtique et un nombre positif sinon. e e Comme dj` dmontr, le cot de la recherche dans le cas le pire passe maintenant ` ea e e u a O(log n). Le passage de O(n) ` O(log n) peut para anodin. Il lest dailleurs sur un dictiona tre naire aussi petit. Avec un vrai dictionnaire, tout change. Par exemple, le dictionnaire de P. Zimmermann1 contient 260688 mots de la langue franaise. Une recherche dun c mot ne cote que 18 comparaisons au pire dans ce dictionnaire. u
1

http://www.loria.fr/zimmerma/

11.2. TRIER

129

// recherche de mot dans dico[g..d[ public static boolean dichoRec(String[] dico, String mot, int g, int d){ int m, cmp; if(g >= d) // lintervalle est vide return false; m = (g+d)/2; cmp = mot.compareTo(dico[m]); if(cmp == 0) return true; else if(cmp < 0) return dichoRec(dico, mot, g, m); else return dichoRec(dico, mot, m+1, d); } public static boolean estDansDico(String[] dico, String mot){ return dichoRec(dico, mot, 0, dico.length); } Fig. 11.1 Recherche dichotomique.

11.1.3

Utilisation dindex

On peut reprer dans le dictionnaire les zones o` on change de lettre initiale ; on e u peut donc construire un index, cod dans le tableau ind tel que tous les mots come menant par une lettre donne sont entre ind[i] et ind[i+1]-1. Dans lexemple c e du dictionnaire de P. Zimmermann, on trouve par exemple que le mot a est le premier mot du dictionnaire, les mots commenant par b se prsentent ` partir de la position c e a 19962 et ainsi de suite. Quand on cherche un mot dans le dictionnaire, on peut faire une dichotomie sur la premi`re lettre, puis une dichotomie ordinaire entre ind[i] et ind[i+1]-1. e Nous laissons la programmation dindex en exercice.

11.2

Trier

Nous avons montr lintrt de trier linformation pour pouvoir retrouver rapidee ee ment ce que lon cherche. Nous allons donner dans cette section quelques algorithmes de tri des donnes. Nous ne serons pas exhaustifs sur le sujet, voir par exemple [Knu73] e pour plus dinformations. Deux grandes classes dalgorithmes existent pour trier un tableau de taille n. Ceux dont le temps de calcul est O(n2 ) et ceux de temps O(n log n). Nous prsenterons e quelques exemples de chaque. On montrera en INF 421 que O(n log n) est la meilleure complexit possible pour la classe des algorithmes de tri procdant par comparaison. e e Pour simplier la prsentation, nous trierons un tableau de n entiers t par ordre e

130

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

croissant. Si nous devions trier un tableau dlments non entiers, il nous surait de ee procder par indirection. Si TO est un tableau dobjets, on lui associerait un tableau e auxiliaire t de mme taille, et on comparerait t[i] et t[j] en comparant en fait e TO[t[i]] et TO[t[j]].

11.2.1

Tris lmentaires ee

Nous prsentons ici deux tris possibles, le tri slection et le tri par insertion. Nous e e renvoyons ` la littrature pour dautres algorithmes, comme le tri ` bulles par exemple. a e a Le tri slection e Le premier tri que nous allons prsenter est le tri par slection. Ce tri va oprer en e e e place, ce qui veut dire que le contenu du tableau t va tre remplac par le contenu tri. e e e ` Le tri consiste ` chercher le plus petit lment de t[0..n[, soit t[m]. A la n du a ee calcul, cette valeur devra occuper la case 0 de t. Do` lide de permuter la valeur de u e t[0] et de t[m] et il ne reste plus ensuite qu` trier le tableau t[1..n[. On proc`de a e ensuite de la mme faon. e c Lesquisse du programme est la suivante : public static void triSelection(int[] t){ int n = t.length, m, tmp; for(int i = 0; i < n; i++){ // invariant: t[0..i[ contient les i plus petits e // elments du tableau // de dpart e m = indice du minimum de t[i..n[ // on echange t[i] et t[m] tmp = t[i]; t[i] = t[m]; t[m] = tmp; } } On peut remarquer quil sut darrter la boucle ` i = n 2 au lieu de n 1, e a puisque le tableau t[n-1..n[ sera automatiquement tri. e Notons le rle du commentaire de la boucle for qui permet dindiquer une sorte o de proprit de rcurrence toujours satisfaite au moment o` le programme repasse par ee e u cet endroit pour chaque valeur de lindice de boucle. Reste ` crire le morceau qui cherche lindice du minimum de t[i..n[, qui nest ae quune adaptation dun algorithme de recherche du minimum global dun tableau. Finalement, on obtient la fonction suivante : public static void triSelection(int[] t){ int n = t.length, m, tmp; for(int i = 0; i < n-1; i++){ // invariant: t[0..i[ contient les i plus petits e // elments du tableau de dpart e // recherche de lindice du minimum de t[i..n[ m = i;

11.2. TRIER for(int j = i+1; j < n; j++) if(t[j] < t[m]) m = j; // on echange t[i] et t[m] tmp = t[i]; t[i] = t[m]; t[m] = tmp; } } quon utilise par exemple dans : public static void main(String[] args){ int[] t = {3, 5, 7, 3, 4, 6}; triSelection(t); }

131

Analysons maintenant le nombre de comparaisons faites dans lalgorithme. Pour chaque valeur de i [0, n 2], on eectue n 1 i comparaisons ` linstruction a if(t[j] < t[m]), soit au total : (n 1) + (n 2) + + 1 = n(n 1)/2 comparaisons. Lalgorithme fait donc O(n2 ) comparaisons. De mme, on peut compter e le nombre dchanges. Il y en a 3 par itration, soit 3(n 1) = O(n). e e Le tri par insertion Ce tri est celui du joueur de cartes qui veut trier son jeu (cest une ide farfelue e en gnral, mais pourquoi pas). On prend en main sa premi`re carte (t[0]), puis on e e e consid`re la deuxi`me (t[1]) et on la met devant ou derri`re la premi`re, en fonction e e e e de sa valeur. Apr`s avoir class ainsi les i 1 premi`res cartes, on cherche la place de e e e la i-i`me, on dcale alors les cartes pour insrer la nouvelle carte. e e e Regardons sur lexemple prcdent, la premi`re valeur se place sans dicult : e e e e 3 On doit maintenant insrer le 5, ce qui donne : e 3 5 puisque 5 > 3. De mme pour le 7. Arrive le 3. On doit donc dcaler les valeurs 5 et 7 e e 3 puis insrer le nouveau 3 : e 3 3 5 7 Et nalement, on obtient : 3 3 4 5 6 7 Ecrivons maintenant le programme correspondant. La premi`re version est la suivante : e 5 7

132

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

public static void triInsertion(int[] t){ int n = t.length, j, tmp; for(int i = 1; i < n; i++){ // t[0..i-1] est dj` tri e a e j = i; // recherche la place de t[i] dans t[0..i-1] while((j > 0) && (t[j-1] > t[i])) j--; // si j = 0, alors t[i] <= t[0] // si j > 0, alors t[j] > t[i] >= t[j-1] // dans tous les cas, on pousse t[j..i-1] // vers la droite tmp = t[i]; for(int k = i; k > j; k--) t[k] = t[k-1]; t[j] = tmp; } } La boucle while doit tre crite avec soin. On fait dcro e e e tre lindice j de faon c a ` trouver la place de t[i]. Si t[i] est plus petit que tous les lments rencontrs ee e ` jusqualors, alors le test sur j 1 serait fatal, j devant prendre la valeur 0. A la n de la boucle, les assertions crites sont correctes et il ne reste plus qu` dplacer les lments e a e ee du tableau vers la droite. Ainsi les lments prcdemment rangs dans t[j..i-1] ee e e e vont se retrouver dans t[j+1..i] librant ainsi la place pour la valeur de t[i]. Il e faut bien programmer en faisant dcro e tre k, en recopiant les valeurs dans lordre. Si lon na pas pris la prcaution de garder la bonne valeur de t[i] sous le coude (on dit e quon la crase), alors le rsultat sera faux. e e e Dans cette premi`re fonction, on a cherch dabord la place de t[i], puis on a tout e e dcal apr`s-coup. On peut condenser ces deux phases comme ceci : e e e public static void triInsertion(int[] t){ int n = t.length, j, tmp; for(int i = 1; i < n; i++){ // t[0..i-1] est dj` tri e a e tmp = t[i]; j = i; // recherche la place de tmp dans t[0..i-1] while((j > 0) && (t[j-1] > tmp)){ t[j] = t[j-1]; j = j-1; } // ici, j = 0 ou bien tmp >= t[j-1] t[j] = tmp; } } On peut se convaincre aisment que ce tri dpend assez fortement de lordre initial e e

11.2. TRIER

133

du tableau t. Ainsi, si t est dj` tri, ou presque tri, alors on trouve tout de suite que ea e e t[i] est ` sa place, et le nombre de comparaisons sera donc faible. On montre quen a moyenne, lalgorithme ncessite un nombre de comparaisons moyen gal ` n(n+3)/41, e e a et un cas le pire en (n 1)(n + 2)/2. Cest donc encore un algorithme en O(n2 ), mais avec un meilleur cas moyen. Exercice. Pour quelle permutation le maximum de comparaisons est-il atteint ? Montrer que le nombre moyen de comparaisons de lalgorithme a bien la valeur annonce e ci-dessus.

11.2.2

Un tri rapide : le tri par fusion

Il existe plusieurs algorithmes dont la complexit atteint O(n log n) oprations, avec e e des constantes et des proprits direntes. Nous avons choisi ici de prsenter uniqueee e e ment le tri par fusion. Ce tri est assez simple ` imaginer et il est un exemple classique dalgorithme de a type diviser pour rsoudre. Pour trier un tableau, on le coupe en deux, on trie chacune e des deux moitis, puis on interclasse les deux bleaux. On peut dj` crire simplement e eae la fonction implantant cet algorithme : public static int[] triFusion(int[] t){ if(t.length == 1) return t; int m = t.length / 2; int[] tg = sousTableau(t, 0, m); int[] td = sousTableau(t, m, t.length); // on trie les deux moitis e tg = triFusion(tg); td = triFusion(td); // on fusionne return fusionner(tg, td); } en y ajoutant la fonction qui fabrique un sous-tableau ` partir dun tableau : a // on cre un tableau contenant t[g..d[ e public static int[] sousTableau(int[] t, int g, int d){ int[] s = new int[d-g]; for(int i = g; i < d; i++) s[i-g] = t[i]; return s; } On commence par le cas de base, cest-`-dire un tableau de longueur 1, donc dj` a ea tri. Sinon, on trie les deux tableaux t[0..m[ et t[m..n[ puis on doit recoller les deux e morceaux. Dans lapproche suivie ici, on retourne un tableau contenant les lments du ee tableau de dpart, mais dans le bon ordre. Cette approche est couteuse en allocation e mmoire, mais sut pour la prsentation. Nous laissons en exercice le codage de cet e e algorithme par eets de bord.

134

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

Il nous reste ` expliquer comment on fusionne deux tableaux tris dans lordre. a e Reprenons lexemple du tableau : int[] t = {3, 5, 7, 3, 4, 6}; Dans ce cas, la moiti gauche trie du tableau est tg = {3, 5, 7}, la moiti droite e e e est td = {3, 4, 6}. Pour reconstruire le tableau fusionn, not f, on commence e e par comparer les deux valeurs initiales de tg et td. Ici elles sont gales, on dcide de e e mettre en tte de f le premier lment de tg. On peut imaginer deux pointeurs, lun e ee qui pointe sur la case courante de tg, lautre sur la case courante de td. Au dpart, e on a donc :

3 5 7

3 4 6

f= ` A la deuxi`me tape, on a dplac les deux pointeurs, ce qui donne : e e e e

3 5 7

3 4 6

f= 3 Pour programmer cette fusion, on va utiliser deux indices g et d qui vont parcourir les deux tableaux tg et td. On doit galement vrier que lon ne sort pas des tableaux. e e Cela conduit au code suivant : public static int[] fusionner(int[] tg, int[] td){ int[] f = new int[tg.length + td.length]; int g = 0, d = 0; // indices de parcourt de tg et td for(int k = 0; k < f.length; k++){ ` // f[k] est la case a remplir if(g >= tg.length) // g est invalide f[k] = td[d++]; else if(d >= td.length) // d est invalide f[k] = tg[g++]; else // g et d sont valides

11.3. HACHAGE if(tg[g] <= td[d]) f[k] = tg[g++]; else // tg[g] > td[d] f[k] = td[d++]; } return f; }

135

Le code est rendu compact par utilisation systmatique des oprateurs de post-ine e crmentation. Le nombre de comparaisons dans la fusion de deux tableaux de taille n e est O(n). Appelons T (n) le nombre de comparaisons de lalgorithme complet. On a : T (n) = qui se rsout en crivant : e e T (n/2) T (n) = + 2. n n/2 Si n = 2k , alors T (2k ) = 2k2k = O(n log n) et le rsultat reste vrai pour n qui nest e pas une puissance de 2. Cest le cot, quelle que soit le tableau t. u Que reste-t-il ` dire ? Tout dabord, la place mmoire ncessaire est 2n, car on ne sait a e e pas fusionner en place deux tableaux. Il existe dautres tris rapides, comme heapsort et quicksort, qui travaillent en place, et ont une complexit moyenne en O(n log n), avec e des constantes souvent meilleures. Dautre part, il existe une version non rcursive de lalgorithme de tri par fusion qui e consiste ` trier dabord des paires dlments, puis des quadruplets, etc. Nous laissons a ee cela ` titre dexercice. a 2T (n/2) + 2n
appels rcursifs e recopies

11.3

Hachage

Revenons maintenant au stockage de linformation, dans un esprit proche de celui de la recherche en table. On peut galement voir cela comme une approche dynamique du e rangement, les informations ` ranger arrivant lune apr`s lautre. Nous allons prsenter a e e ici le hachage dit ouvert. Dans le cas du dictionnaire, on pourrait aussi remplacer un tableau unique par 26 tableaux, un par lettre de dbut de mot, ou encore un tableau pour les mots commenant e c par aa, etc. On aurait ainsi 262 = 676 tableaux, idalement se partageant tout le e dictionnaire, chaque tableau contenant 260688/676 385 mots. Une recherche de mot couterait le cot de localisation du tableau auxiliaire, puis une dichotomie dans un u tableau de taille 385. Supposons quon ait ` stocker N lments. On peut essayer de fabriquer M tables a ee ayant ` peu pr`s N/M lments, ` condition de savoir localiser facilement la table dans a e ee a laquelle chercher. Arriv l`, pourquoi ne pas passer ` la limite ? Cela revient ` fabriquer e a a a N tables ayant 1 lment, et il ne reste plus qu` localiser la table. Remarquons en ee a passant quune table de tables ` 1 lment serait avantageusement remplace par un a ee e tableau tout court.

136

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

Pour localiser llment dans sa table, il faut donc savoir comment calculer une ee fonction de llment qui donne un entier qui sera lindice dans le tableau. Expliquons ee comment ce miracle est possible. Si c est un caract`re, on peut le reprsenter par e e son caract`re unicode, quon obtient simplement par (int)c. Si s est une cha de e ne caract`res de longueur n, on calcule une fonction de tous ces caract`res. En Java, on e e peut tricher et utiliser le fait que si x est un objet, alors x.hashCode() retourne un entier. Une fois quon dispose dun nombre h(z) pour un objet z, on peut sen servir comme indice de stockage dans un tableau t. On peut xer la taille de t, soit T , si lon dispose dune borne sur le nombre dlments ` stocker. Nous verrons plus loin comment xer ee a cette borne. Considrons un exemple simple, celui o` on veut ranger un ensemble Z dentiers e u strictement positifs dans une table de hachage, reprsente ici par le tableau t[0..T[ e e initialis ` 0. Comment ins`re-t-on z dans le tableau ? Idalement, on le place ` lindice ea e e a h(z) de t. Essayons avec lensemble Z = {11, 59, 32, 44, 31, 26, 19}. On va prendre une table de taille 10 et h(z) = z mod 10. i t[i] 0 1 2 3 4 5 6 7 8 9

Le premier lment ` mettre est 11, quon met dans la case h(11) = 1 : ee a i t[i] 0 1 11 2 3 4 5 6 7 8 9

On continue avec 59, 32, 44 : i t[i] 0 1 2 11 32 3 4 44 5 6 7 8 9 59

Avec 31 arrive le probl`me : la case h(31) = 1 est dj` occupe. Qu` cela ne tienne, e ea e a on cherche la premi`re case vide ` droite, ici la case 3 : e a i t[i] 0 1 2 3 4 11 32 31 44 5 6 7 8 9 59

On met alors 26 dans la case 6 : i t[i] 0 1 2 3 4 11 32 31 44 5 6 26 7 8 9 59

Pour 19, on doit faire face ` un nouveau probl`me : la case 9 est occupe, et on doit a e e mettre 19 ailleurs, on la met dans la case 0, en considrant que le tableau t est gr de e ee faon circulaire. c i 0 1 2 3 4 t[i] 19 11 32 31 44 5 6 26 7 8 9 59

Les fonctions implantant cet algorithme sont :

11.3. HACHAGE

137

public class HachageEntier{ final static int T = 10; static int[] t = new int[T]; public static void initialiser(){ for(int i = 0; i < T; i++) t[i] = 0; } public static int hash(int z){ return (z % T); } public static void inserer(int z){ int hz = hash(z); while(t[hz] != 0) hz = (hz+1) % T; t[hz] = z; } public static void main(String[] args){ int[] u = {11, 59, 32, 44, 31, 26, 19}; initialiser(); for(int i = 0; i < u.length; i++) inserer(u[i]); } } Si lon doit chercher un lment dans la table, on proc`de comme pour linsertion, ee e mais en sarrtant quand on trouve la valeur cherche ou bien un 0. e e public static boolean estDans(int z){ int hz = hash(z); while((t[hz] != 0) && (t[hz] != z)) hz = (hz+1) % T; return (t[hz] == z); } Exercice. Comment programmer la suppression dun lment dans une table de haee chage ? Donnons maintenant un cas un plus raliste, celui dun annuaire, compos dabonns e e e ayant un nom et un numro de tlphone : e ee public class Abonne{ String nom;

138

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER int tel; public static Abonne creer(String n, int t){ Abonne ab = new Abonne(); ab.nom = n; ab.tel = t; return ab; }

} Le hachage se fait comme prcdemment : e e public class HachageAnnuaire{ final static int T = 101; static Abonne[] t = new Abonne[T]; public static int hash(String nom){ return Math.abs(nom.hashCode()) % T; } public static void inserer(String nom, int tel){ int h = hash(nom); while(t[h] != null) h = (h+1) % T; t[h] = Abonne.creer(nom, tel); } public static int rechercher(String nom){ int h = hash(nom); while((t[h] != null) && (! nom.equals(t[h].nom))) h = (h+1) % T; if(t[h] == null) return -1; else return t[h].tel; } public static void initialiser(){ for(int i = 0; i < t.length; i++) t[i] = null; inserer("dg", 4001); inserer("dgae", 4002); inserer("de", 4475); inserer("cdtpromo", 5971); inserer("kes", 4822); inserer("bobar", 4824);

11.3. HACHAGE inserer("scola", 4154); inserer("dix", 3467); } public static void main(String[] args){ initialiser(); System.out.println(rechercher("bobar")); System.out.println(rechercher("dg")); } } On peut montrer le rsultat suivant : e

139

Thor`me 2 On appelle N le nombre dlments dj` prsents dans la table et on e e ee ea e pose = N/T . Alors le nombre doprations ` faire est : e a 1/2 + 1/(2(1 )) pour une recherche avec succ`s, e 1/2 + 1/(2(1 )2 ) pour une recherche avec chec. e Par exemple, si = 2/3, on fait 2 ou 5 oprations. Cela permet de stocker un e ensemble de M lments ` laide dun tableau de 3/2M entiers et le test dappartenance ee a se fait en O(1) oprations. e Il existe dautres techniques de hachage. Par exemple celle qui consiste ` grer les a e collisions en utilisant des listes cha ees. n Terminons avec quelques applications du hachage. Dans le logiciel de calcul formel Maple, il faut calculer une adresse dans la mmoire pour nimporte quel type dobjet. e La comparaison est tr`s simple par calcul dadresse, mme sur de gros objets. Dans les e e navigateurs, le hachage permet de reprer les URL parcourues rcemment (donc mises e e en gris). De la mme faon, les moteurs de recherche comme Google, doivent stocker e e c plusieurs milliards de cha nes de caract`res (correspondant ` > 500 106 pages), ce qui e a nest possible quavec du hachage.

140

CHAPITRE 11. RANGER LINFORMATION. . . POUR LA RETROUVER

Troisi`me partie e

Introduction au gnie logiciel e

141

Chapitre 12

Comment crire un programme e


Ce chapitre a pour but de dgager de grandes constantes dans lcriture de petits e e ou de gros programmes, en commenant par les petits. Il vaut mieux prendre de bonnes c habitudes tout de suite. Quels sont les buts ` atteindre : on cherche toujours la concision, a la modularit interne et externe, la rutilisation ventuelle. e e e Ajoutons quun programme volue dans le temps, quil nest pas g, et on doit e e donc prvoir quil va voluer, que ce soit sous la main du programmeur originel, ou de e e ces successeurs qui vont devoir en modier quelques lignes.

12.1

Pourquoi du gnie logiciel ? e

Le code source de Windows XP reprsente 50 millions de lignes de code, Linux e environ 30 millions. Comment peut-on grer autant de lignes de code, et autant de e programmeurs supposs ? e Dans un article des Communications of the ACM (septembre 2006), des donnes e rassembles par le Quantitative Software Management sont prsentes. Ltude a port e e e e e sur 564 projets rcents, raliss dans 31 entreprises dans 16 branches dans 16 pays. Il e e e en ressort quun projet moyen requiert moins de 7 personnes pour une dure infrieure e e a ` 8 mois, pour un cot moyen infrieur ` 58 homme-mois, avec comme langage encore u e a majoritaire COBOL, en passe dtre dtrn par Java, reprsentant moins de 9, 200 e e o e e lignes de code. Depuis lav`nement de linformatique, de nombreux chercheurs et praticiens sintere rogent sur les aspects dorganisation des gros programmes en grosses ( ?) quipes. Une e des bibles de rfrence est toujours [Bro95]. ee

12.2
12.2.1

Principes gnraux e e
La cha de production logicielle ne

Le schma de la gure 12.1 permet de comprendre les direntes phases de la e e cration dun logiciel consquent. e e Comme on peut le constater, un logiciel ne se rsume pas ` la programmation. e a Malgr tout, la phase de programmation reste lendroit o` on a le plus de prise sur le e u produit. 143

144

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

Spcication du produit e Architecture du programme Planication du travail Architecture dtaille e e Programmation Dbogage e Validation/Tests Maintenance Fig. 12.1 La cha de production logicielle. ne La spcication et la documentation e La phase de spcication est importante et conditionne le reste. Les probl`mes que e e lon doit se poser sont gnralement (la liste nest pas exhaustive) : e e Sur quelle machine (avec quel syst`me dexploitation) le programme doit-il toure ner ? Y a-t-il des intractions avec dautres programmes existant ? e Que doit faire le programme ? Qui doit lutiliser ? Quelles sont les oprations spciques ? e e Quel doit tre le temps de rponse ? e e ` A quelles erreurs le programme doit-il pouvoir rsister ? e La premi`re question est assez bien rgle par Java, qui est portable, cest-`-dire tourne e e e a sur toutes les machines. La documentation dun programme (petit ou gros) est fondamentale. Il faut commencer ` lcrire d`s le dbut, avec mise ` jour ` chaque fois quon crit une fonction. a e e e a a e Larchitecture du programme Une architecture excellente peut tre gte par une programmation mdiocre ; une e ae e excellente programmation ne peut pas rattraper compl`tement une architecture dsase e treuse. La division en sous-syst`mes permet de se mettre daccord sur linterface (ici pris e au sens dentre des donnes, formatage des sorties) en liaison avec le moteur du proe e grammes (fabriquant les donnes de sortie). Cette phase recense galement les bases de e e donnes, les probl`mes de communications, laspect graphique, etc. e e Cest dans cette phase que la modularit sexprime le mieux. On cherche toujours e la simplicit, mais aussi une forme de protection contre les changements (surtout dans e linterface). Cela permet de raliser une bonne division du travail et de minimiser les e interactions. Le chapitre 14 reviendra sur les structures de donnes et la ralisation de modules e e rutilisables. e

12.2. PRINCIPES GENERAUX

145

12.2.2

Architecture dtaille e e

Etudiez larchitecture soigneusement et vriez quelle marche avant de continuer. e La mthode gnralement employe est celle de lanalyse descendante et du ranage. e e e e Cette phase doit rester la plus abstraite possible, en particulier elle doit tre la plus e indpendante possible du langage de programmation choisi. De mme, les dtails de e e e programmation sont renvoys au plus tard possible. e Prenons lexemple simple du comptage du nombre de mots dans un chier. Les actions ncessaires sont : e ouvrir le chier ; aller au dbut du chier ; e tant que la n du chier nest pas atteinte lire un mot ; incrmenter le nombre de mots ; e fermer le chier ; acher le nombre de mots. On peut encore raner : comment lire un mot, etc. Rajoutons quelques r`gles : e ne pas descendre de niveau tant que vous ntes pas convaincu(e)s que le niveau e actuel est satisfaisant ; si un probl`me appara cest sans doute quil trouve sa source au niveau imme t, e diatement suprieur. Remonter et rgler le probl`me. e e e

12.2.3

Aspects organisationnels

Planication du travail Dans son bestseller, The mythical Man-Month, F. P. Brooks (qui a conu le syst`me c e dexploitation de lIBM 360, au dbut des annes 1970), donne quelques r`gles empie e e riques pour un bon projet. La rpartition du temps devrait tre celle-ci : e e 1/3 de spcication ; e 1/6 de programmation ; 1/4 de test (alpha) ; 1/4 dintgration et test (beta). e Il faut galement garder en tte la fameuse de la gure 12.2, qui dcrit ltat davane e e e cement vers le but nal. Des outils Au l du temps, des outils de programmation confortables et ecaces ont vu le jour, ce sont des Integrated Development Environments (IDE), comme eclipse et netbeans. Ces outils permettent de simplier le travail sur un logiciel, en intgrant diteur (intuitif, e e avec aide en ligne notamment), compilateur, tests et documentation automatiques. Cela permet entre autres de respecter les bonnes habitudes de programmation sans eort, de sintgrer ` des produits plus complexes. Leur utilisation est recommande dans e a e un projet moyen ou gros, et mme dans les petits, ils aident ` la comprhension et ` e a e a linitiation du langage (documentation en ligne, etc.). Ces IDE sont souvent disponibles sur toutes les machines et tous les syst`mes. e

146

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME Fonctionnalit e 100% 90%

50%

Temps

Fig. 12.2 Fonctionnalit en fonction du temps. e Outre les IDE, il est relativement facile dutiliser des outils de travail collaboratif, qui permettent de dcentraliser la programmation et dassurer les copies de sauvegardes. e Lun des plus connus et des plus faciles ` utiliser est SVN. Notons quils sont galement a e souvent integrs aussi dans les IDE, ce qui facilite encore plus la vie du programmeur. e Protons-en pour insister sur le fait que ces outils nont pas que des nalits ine formatiques pures et dures. Ce poly est par exemple mis dans SVN, ce qui facilite la synchronisation, o` que lauteur se trouve, et mme si celui-ci na ` se coordonner u e a quavec lui-mme. . . e Programmer La programmation est dautant plus simple quelle dcoule logiquement de larchie tecture dtaille. Stant mis daccord sur un certain nombre de tches, le programme e e e a principal est facile ` crire : il se contente dappeler les direntes actions prvues. a e e e On peut donc crire cette fonction en appelant des fonctions qui ne font rien pour le e ` moment. On parle de programmation par stubs (ou bouchons), voir gure 12.3. A tout instant, une maquette du programme tourne, et il ne reste plus qu` programmer les a bouchons les uns apr`s les autres. Certains IDE permettent de facilement programmer e par bouchons, en crivant directement les prototypes des fonctions, librant ainsi le e e programmeur de tches fastidieuses. a d main c b a

Fig. 12.3 Programmation par bouchons.

12.2. PRINCIPES GENERAUX

147

public class MaClasse{ static int a(int i){ return 1; } static void b(int n){ } static void c(int k){ } static void d(){ } public static void main(String[] args){ int n = a(1); b(n); c(n-1); e d(); // dmo } } Cela illustre galement un principe fondamental : le test et le dbug doivent accome e pagner lcriture du programme ` chaque instant. Les bugs doivent tre corrigs le plus e a e e vite possible, car ils peuvent rvler tr`s tt des probl`mes de conception irratrappables e e e o e par la suite. R`gle dor : il est absurde dcrire 1000 lignes de code et de les dboguer dun seul e e e coup. Dboguer e La programmation fait intervenir trois acteurs : le programme, le programmeur et le bogue. Dboguer est un art qui demande patience, ingniosit, exprience, un temps non e e e e born et. . . du sommeil ! e Il est bon de se rpter les lois du dbogage d`s que tout va mal : e e e e tout logiciel complexe contient des bogues ; le bogue est probablement caus par la derni`re chose que vous venez de modier ; e e si le bogue nest pas l` o` vous pensez, cest quil est ailleurs ; a u un bogue algorithmique est beaucoup plus dicile ` trouver quun bogue de a programmation pure. on ne dbogue pas un programme qui marche ! e Au-del` de ces boutades, dboguer rel`ve quand mme dune dmarche scientique : a e e e e il faut isoler le bogue et tre capable de le reproduire. Dboguer est donc tr`s dicile e e e dans les programmes non dterministes (attention aux gnrateurs alatoires mieux e e e e vaut les dbrancher au dpart ; paralllisme, calculs distribus, etc.). e e e e Une faon de procder classique est dacher ou de faire des tests dans le proc e gramme, tests qui ne seront activs que dans certains cas, par exemple si une variable e de classe debug est mise ` true. Des tests de condition (assertions) ` lintrieur des a a e programmes peuvent tre utiliss avec prot. e e

148

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

Il existe aussi des outils qui permettent de dtecter certains bugs (proleurs, etc.). e Notons encore une fois que les IDE contiennent un dbogueur intgr, qui permet e e e dexcuter pas ` pas un programme pour localiser les probl`mes. e a e Valider et tester Dapr`s G. J. Myers[Mye04] : tester, cest excuter le programme dans lintention e e dy trouver des anomalies ou des dfauts. e Le test dun programme est une activit prenante, et ncessaire, du moins quand e e on veut raliser un programme correct, et non pas un produit ` vendre au client, ` qui e a a on fera payer les corrections et les mises ` jour. a R`gle dor : il faut penser ` crire son programme de faon quil soit facile ` tester. e ae c a On sera amen ` crire des programmes fabriquant des jeux de test automatiqueeae ment (en Java ou langage de script). On doit crire des tests avant dcrire le programme e e principal, ce qui facilitera lcriture de celui-ci, et permettra de contrler et valider la e o spcication du programme. Ecrire un programme de test permet de le rutiliser ` e e a chaque modication du programme principal. Les tests de fonctionnalit du programme sont impratifs, font partie du projet e e et sont de la responsabilit immdiate du programmeur. Plus gnralement, on parle e e e e dalpha test pour dsigner les tests faits par lquipe de dveloppement ; le code est e e e alors gel, seuls les bogues corrigs. En phase de beta test, les tests sont raliss par des e e e e testeurs slectionns et extrieurs ` lquipe de dveloppement. e e e a e e Ecrire des tests nest pas toujours facile. Ils doivent couvrir tous les cas normaux ou anormaux (bo de verre, bo noire, etc.). Souvent, tester toutes les branches dun te te programme est tout simplement impossible. On proc`de galement ` des tests de non rgression, pour contrer la cl`bre maxime : e e a e ee la modication dun logiciel : un pas en avant, deux pas en arri`re. Ce sont des tests e que lon pratique d`s la russite de la compilation, pour vrier que des tests (rapides) e e e passent encore. Ils contiennent souvent les instances ayant conduit ` des bugs dans le a pass. Les tests lourds sont souvent regroups ` un autre moment, par exemple la nuit e e a (ce sont les fameux night-build). Les tests de validation permettent de vrier que lon a bien fait le logiciel et on e valide que lon a fait le bon logiciel. Ce sont ceux que lon passe en dernier quand tout le reste est pr`s. e Le test structurel statique (bo de verre) Il sagit l` danalyser le code source te a sans faire tourner le programme. Parmi les questions poses, on trouve : e Revues de code : individuelles ou en groupe ; permet de sassurer du respect de certains standards de codage. y a-t-il susamment de commentaires ? les identieurs ont-ils des noms pertinents ? le code est-il structur ? e y a-t-il trop de littraux (variables) ? e la taille des fonctions est-elle acceptable ? la forme des dcisions est-elle assez simple ? e Jeux de tests : pour toutes les branches du programme (si possible).

12.2. PRINCIPES GENERAUX

149

Preuve formelle : cf. deuxi`me partie du cours + cours anne 3 (notamment e e analyse statique, preuve de programme). Heureusement, des outils existent pour faire tout cela, et reprsentent des sujets de e recherche tr`s actifs en France. e Le test fonctionnel (bo noire) On sattache ici au comportement fonctionnel te dun programme, sans regarder le contenu du programme. On pratique ainsi souvent des tests alatoires, pratiques et faciles ` programmer, mais qui ne trouvent pas souvent e a les bogues dures. Bien sr, dans certains cas, on peut tester le comportement du programme sur toutes u les donnes possibles. Pour de plus gros programmes, on pratique souvent lanalyse e partitionnelle, qui revient ` tablir des classes dquivalence de comportement pour les ae e tests et comparer sur une donne reprsentative de chaque classe que le comportement e e est bien celui attendu, incluant le test aux limites. Donnons quelques exemples : Si la donne x [a, b] : e une classe de valeurs pour x < a (resp. x > b) ; n valeurs valides, dont a, b. Si x est un ensemble avec |x| X : cas invalides : x = , |x| > X ; n valeurs valides. Si x = {x0 , x1 , . . . , xr } (avec r petit) : une classe valide pour chaque xi ; des classes invalides, comprenant une classe correcte sauf pour un des xi , pour tous les i. Si x est une obligation ou contrainte (forme, syntaxe, sens) : une classe contrainte respecte, une non respecte. e e Illustrons ceci par la vrication dune fonction qui calcule F (x) = e 1/x. Les classes utilises pourront tre : e e rels ngatifs ; e e x = 0; rels strictement positifs. e Analyser les performances (benchmarks) Mesurer la vitesse de son programme est galement une bonne ide. Mme si tous les programmes du monde ne peuvent se e e e terminer instantment, essayer de comprendre o` on passe son temps est primordial, e u et un programme rapide est plus vendeur. On peut crire un programme de test qui ache les param`tres pertinents. On peut e e tester 2 fonctions et produire deux courbes de temps, quil reste ` acher et commenter a (xgraphic ou gnuplot en Unix). Si lalgorithme thorique est en O(n2 ), on teste avec n, 2n, 3n et on regarde si le e temps varie par un facteur 4, 9, . . . Si le temps est infrieur, cest quil y a une erreur ; e sil est suprieur, cest quon passe du temps ` faire autre chose et il faut comprendre e a pourquoi. Si le comportement dpend trop de la valeur initiale de n, il y a mati`re ` e e a probl`me. e Il ne reste plus qu` commenter, dduire, etc. Cest le ct exprimental de linfora e oe e matique.

150

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

12.2.4

En guise de conclusion provisoire. . .

Un programme ressemble ` un pont : a Plus le projet est grand, plus il faut soigner larchitecture et le planning. Les probl`mes humains ne peuvent tre ngligs. e e e e Dcouvrir les erreurs tr`s vite est essentiel (ou la dcouverte tardive est catastroe e e phique). Les erreurs peuvent tre dsastreuses (Ariane 5 1 milliard de dollars). e e Utiliser des prfabriqus permet de gagner du temps. e e Un programme nest pas un pont : Le logiciel est purement abstrait ; il est invisible, car il nest vu que par son action sur un matriel physique. e Le logiciel est crit pour tre chang, amlior. e e e e e Le logiciel est en partie rutilisable. e Le logiciel peut tre test ` tout moment de sa cration et de sa vie. e ea e

12.3
12.3.1

Un exemple dtaill e e
Le probl`me e

On cherche ` calculer le jour de la semaine correspondant ` une date donne. a a e Face ` nimporte quel probl`me, il faut tablir une sorte de cahier des charges, quon a e e appelle spcication du programme. Plus la spcication sera prcise, plus le programme e e e nal sera conforme aux attentes. Ici, on entre la date en chires sous la forme agrable e jj mm aaaa et on veut en rponse le nom du jour crit en toutes lettres. e e Avons-nous tout dit ? Non. En particulier, comment les donnes sont-elles fournies e au programme ? Par exemple, le programme doit prendre en entre au terminal trois e entiers j, m, a spars par des espaces, va calculer J et acher le rsultat sous une e e e forme agrable comprhensible par lhumain qui regarde. Que doit faire le programme e e en cas derreur sur les donnes en entre ? Nous indiquons une erreur ` lutilisateur, e e a mais nous ne voulons pas que le programme plante. Finalement, quel format voulonsnous pour la rponse ? Est-ce que nous voulons que la rponse soit ncessairement en e e e franais, ou bien pouvons-nous choisir la langue de la rponse ? Nous allons commencer c e avec le franais, et acher c Le j/m/a est un xxx. Nous allons aussi imaginer que nous pourrons faire voluer le programme pour donner e la rponse dans dautres langues (cf. section 13.3). e

12.3.2

Architecture du programme

Quelle architecture pour ce programme ? Nous devons garder en tte que le proe gramme doit pouvoir tre test dans toutes ses parties. Il doit galement avoir une e e e interface conviviale, ` la fois pour entrer les donnes, mais galement pour acher le a e e rsultat du calcul. Si lon veut un programme le plus gnrique possible, il faut que e e e chacune de ses parties soit la plus indpendante possible des autres. Une analyse dese cendante possible est la suivante : lutilisateur entre les donnes ; e

12.3. UN EXEMPLE DETAILLE

151

le programme vrie la validit des donnes dentre et avertit lutilisateur si tel e e e e nest pas le cas ; le programme calcule le jour de la semaine correspondant aux param`tres ; e le programme ache ce jour de faon conviviale. c Ce dcoupage est simple, et il laisse pour plus tard le mod`le dentre des donnes et e e e e le mod`le de sortie. Ceux-ci pourront tre changs sans que le cur du calcul nait besoin e e e dtre modi, cest lintrt de la modularit. De mme, nous pourrions remplacer la e e ee e e primitive de calcul par une autre base sur un algorithme dirent, sans avoir ` changer e e a les autres morceaux. Tr`s gnralement, les entres seront des cha e e e e nes de caract`res, quil faudra vrier. e e Cette vrication prend deux tapes : dispose-t-on de trois entiers ? Si oui, reprsentente e e ils une date valide ? Ces derniers calculs sont plus faciles ` faire sur des entiers. Ainsi, a on peut raner larchitecture : lutilisateur entre les donnes sous forme de trois cha e nes de caract`res ; e le programme vrie que les trois cha e nes de caract`res reprsentent des entiers ; e e si cest le cas, on rcup`re les trois entiers (trois int suront) et on vrie quils e e e correspondent bien ` une date ; a le programme calcule le jour de la semaine correspondant aux param`tres ; e le programme ache ce jour de faon conviviale. c

12.3.3

Programmation

Appliquons le principe de la programmation par stubs. Nous devons progresser incrmentalement, avec ` chaque fois des progr`s mesurables dans lutilisation du proe a e gramme. Le premier squelette quon peut crire et qui permet de dmarrer peut tre le e e e suivant : public class Jour{ static boolean donneesCorrectes(int j, int m, int a){ return true; // stub } public static String jourDeLaSemaine(int j, int m, int a){ return "vendredi"; // stub } public static String calculerJour(String sj, String sm, String sa){ int j, m, a; j = Integer.parseInt(sj); m = Integer.parseInt(sm); a = Integer.parseInt(sa); if(donneesCorrectes(j, m, a)) return jourDeLaSemaine(j, m, a); else return null;

152 }

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

public static void afficherJour(String sj, String sm, String sa, String s){ System.out.print("Le "+sj+"/"+sm+"/"+sa); System.out.println(" est un "+s+"."); } public static void main(String[] args){ String s, sj, sm, sa; sj = "18"; sm = "3"; sa = "2011"; s = calculerJour(sj, sm, sa); if(s != null) afficherJour(sj, sm, sa, s); else System.out.println("Donnes incorrectes"); e } } Ce programme ne fait pas grand chose, mais il faut bien commencer par le commencement. Il compile, il sexcute, et ache ce quon veut pour au moins un exemple e connu. Nous avons galement dcid quen cas de probl`me, la fonction calculerJour e e e e retourne null. Noter la convention adopte pour direncier (dans la tte du proe e e ne nes de grammeur) lentier j et la cha sj. Dautre part, nous avons converti les cha caract`res en entiers ` laide de la fonction Integer.parseInt (nous y reviendrons e a plus loin). Arms de ce squelette, nous pouvons commencer ` crire un programme de test, e a e que lon fera voluer en mme temps que le programme : e e public class TesterJour{ static boolean testerJour(String t){ String resultat, s; String[] tab; tab = t.split(" "); resultat = tab[3]; s = Jour.calculerJour(tab[0], tab[1], tab[2]); if(s == null) return resultat.equals("erreur"); else return s.equals(resultat); } public static void recette(){ String[] tests = {"18 3 2011 vendredi",

12.3. UN EXEMPLE DETAILLE "1 1 1 erreur"}; for(int i = 0; i < tests.length; i++){ boolean ok = testerJour(tests[i]); System.out.print("Test "+i+" : "); System.out.println(ok); } } public static void main(String[] args){ recette(); } }

153

Le programme principal de cette fonction utilise un tableau de cha nes qui contiennent une entre et la sortie attendue. La fonction testerJour appelle la fonction e idoine de la classe jour et compare le rsultat obtenu ` celui quelle attend et ache le e a rsultat du test de comparaison. Une fois cela fait, il sera facile de rajouter des cha e nes de test au fur et ` mesure de lcriture du programme Jour. Noter lutilisation de a e linstruction bien pratique tab = t.split(" "); qui dcompose la cha t en cha e ne nes de caract`res spares par un blanc et retourne un e e e tableau form de ces cha e nes. Nous avons galement anticip sur un rsultat de calcul e e e qui donnerait null. Le cur du programme est le calcul du jour de la semaine bas sur le thor`me 3 e e e donn ` la n du chapitre. Seul nous intresse ici la spcication de cette fonction. Il ea e e nutilise que des donnes numriques et retourne une donne numrique. La fonction e e e e correspondante est facile ` crire : ae // ENTREE: 1 <= j <= 31, 1 <= m <= 12, 1584 < a // SORTIE: entier J tel que 0 <= J <= 6, avec 0 pour // dimanche, 1 pour lundi, etc. ` // ACTION: J est le jour de la semaine correspondant a // la date donne sous la forme j/m/a e public static int jourZeller(int j, int m, int a){ int mz, az, e, s, J; // calcul des mois/annes Zeller e mz = m-2; az = a; if(mz <= 0){ mz += 12; az--; } // az = 100*s+e, 0 <= e < 100 s = az / 100; e = az % 100;

154

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME // la formule du rvrend Zeller e e J = j + (int)Math.floor(2.6*mz-0.2); J += e + (e/4) + (s/4) - 2*s; // attention aux nombres ngatifs e if(J >= 0) J %= 7; else{ J = (-J) % 7; if(J > 0) J = 7-J; } return J;

} Les commentaires indiquent des proprits supposes satisfaites en entre et en ee e e sortie. Cette fonction sut-elle ` nos besoins ? Il para logique dintroduire une fonction a t un peu gnrale qui va cacher lutilisation de la mthode de Zeller, qui ne parle pas e e e ncessairement au lecteur. Par exemple : e final static String[] JOUR = {"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"}; public static String jourDeLaSemaine(int j, int m, int a){ int jz = jourZeller(j, m, a); return JOUR[jz]; } Ici, nous avons fait le choix de dnir des constantes globales (champs statiques) e de la classe, que lon peut exporter et rutiliser dans dautres contextes. Nous aurions e aussi pu garder la correspondance numrique/texte des jours de la semaine ` lintrieur e a e de la fonction jourDeLaSemaine. Nous pouvons en parall`le ajouter dautres tests e simples dans TesterJour.java : String[] tests = {"18 3 2011 vendredi", "1 1 1 erreur", "19 3 2011 samedi", "10 5 2011 mardi" }; Il nous faut maintenant remplir les fonctions qui ne faisaient rien, parce que par exemple nous navions pas besoin de contrler nos entres. Dans le monde rel, o` un o e e u utilisateur nest pas ncessairement le programmeur lui-mme, il faut prvoir beaucoup e e e de cas derreurs, ne serait-ce que pour ne pas perturber lordinateur (ou le syst`me). e Ainsi, que doit tester la fonction donneesCorrectes ? Que les valeurs de j, m, e et a sont correctes. Pour le mois et lanne, cest facile, mais pour le jour, cest plus compliqu car on doit faire intervenir le fait que lanne peut-tre bissextile. On va e e e galement utiliser un tableau pour stocker le nombre de jours de chaque mois, ainsi e quune fonction qui retourne le nombre de jours dans un mois. Cela nous donne

12.3. UN EXEMPLE DETAILLE

155

final static int[] JOURS_DANS_MOIS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static boolean estBissextile(int a){ if((a % 4) != 0) return false; if((a % 100) != 0) return true; return ((a % 400) == 0); } static int nbJoursDansMois(int m, int a){ if((m != 2) || !estBissextile(a)) return JOURS_DANS_MOIS[m-1]; else return 29; } static boolean donneesCorrectes(int j, int m, int a){ if(a <= 1584) return false; if((m < 1) || (m > 12)) return false; if((j < 1) || (j > nbJoursDansMois(m, a))) return false; return true; } ` A ce point, nous devons tester les nouvelles fonctions ajoutes. Nous allons donner e les fonctions de test pour lanne bissextile, laissant les autres en exercices. Ici, quatre e cas sont susants pour couvrir tous les branchements du code. La fonction dappel du test scrit simplement : e static void tester_estBissextile(){ String[] tests = {"1600 true", "1604 true", "1700 false", "1911 false"}; System.out.println("Tests de Bissextile"); for(int i = 0; i < tests.length; i++){ boolean ok = tester_estBissextile(tests[i]); System.out.print("Test "+i+" : "); System.out.println(ok); } } La fonction de test elle-mme rcup`re les entres, appelle la fonction teste et e e e e e retourne un boolen qui exprime que le test est russi ou pas : e e

156

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

static boolean tester_estBissextile(String t){ String[] tab = t.split(" "); int a = Integer.parseInt(tab[0]); boolean res = tab[1].equals("true"); boolean estb = Jour.estBissextile(a); return estb == res; } Une bonne habitude ` prendre est de choisir les noms des fonctions de test de faon a c canonique en fonction du nom de la fonction teste. e Nous allons maintenant dcrire comment on peut mettre ` jour notre programme e a de Test, simplement en modiant lg`rement le tableau de test : e e String[] tests = {"18 3 2011 vendredi", "1 1 1 erreur", "19 3 2011 samedi", "10 5 2011 mardi", "32 1 1 erreur", "1 32 1 erreur", "1 1 32 erreur", "-1 1 1 erreur", "28 2 2011 lundi", "29 2 2011 erreur", "29 2 2000 mardi", "29 2 2100 erreur", "29 2 2008 vendredi", "1 1 1500 erreur" }; Nous avons essay dtre assez exhaustifs dans le test des cas derreurs. Nous avons e e dcid quil ny avait quun seul type derreur. Quel que soit le probl`me dans les e e e donnes, nous savons seulement quil y a eu une erreur, mais a ne sut sans doute e c pas ` lutilisateur. Il est conseill en gnral de retourner le maximum dinformation a e e e sur lerreur rencontre, ce qui peut permettre au programme de corriger tout seul, ou e bien de renseigner susament lutilisateur sur le probl`me. e Il existe au moins deux faons de signaler des erreurs : dans notre cas, modier c donneesCorrectes pour quelle retourne une cha de caract`res sut et nous ne e laissons cela comme exercice. Lautre, plus gnrique, consiste ` lever une exception, e e a mais cela nous entra nerait trop loin pour le moment. Cette solution sut-elle ? Et que se passe-t-il quand lutilisateur entre des donnes e qui ne sont pas des nombres ? Ou pas assez de donnes ? Ce dernier cas est facile ` e a traiter par modication de la fonction principale, qui devient plus raliste : e public static void main(String[] args){ String s, sj, sm, sa; if(args.length < 3){ System.out.println("Pas assez de donnes"); e return;

12.3. UN EXEMPLE DETAILLE } sj = args[0]; sm = args[1]; sa = args[2]; s = calculerJour(sj, sm, sa); if(s != null) afficherJour(sj, sm, sa, s); else System.out.println("Donnes incorrectes"); e return; }

157

Le premier probl`me est rsolu de mani`re dirente. Nous avons essentiellement e e e e deux choix : ou bien nous vrions que chaque cha dentre ne contient que des e ne e chires, ou bien nous laissons Java essayer de lire des entiers et nous renvoyer une erreur si tel nest pas le cas. Par exemple, lappel unix% java Jour a b c va provoquer

Exception in thread "main" java.lang.NumberFormatException: For input string at java.lang.NumberFormatException.forInputString(NumberFormatException.java at java.lang.Integer.parseInt(Integer.java:447) at java.lang.Integer.parseInt(Integer.java:497) at Jour.calculerJour(Jour.java:73) at Jour.main(Jour.java:97) e Comment interpr`te-t-on cela ? La fonction Integer.parseInt a lanc une exception e car la cha de caract`res en entre na pu tre interprte par Java comme un nombre ne e e e ee entier. Le programme appelant na pu que renvoyer lexception au niveau au-dessus et la machine virtuelle de Java a alors stopp le programme en redonnant la main au e syst`me. Ce que nous voyons ach est la pile dexcution. e e e Un programme utilisateur ne saurait chouer ainsi (imaginer que a se passe sur e c un satellite inaccessible. . . ou sur votre tlphone). Java ore la possibilit de rattraper ee e cette erreur de mani`re plus conviviale. Cela se fait de la mani`re suivante : e e public static void main(String[] args){ String s = null, sj, sm, sa; if(args.length < 3){ System.out.println("Pas assez de donnes"); e return; } sj = args[0]; sm = args[1]; sa = args[2]; try{ s = calculerJour(sj, sm, sa); } catch(Exception e){ System.err.println("Exception: "

158

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME + e.getMessage()); } if(s != null) afficherJour(sj, sm, sa, s); else System.out.println("Donnes incorrectes"); e return; }

Linstruction try ... catch permet en quelque sorte de protger lexcution de e e la fonction qui nous intresse. La seule faon pour la fonction dchouer est quil y ait une e c e erreur de lecture des donnes, ici quune donne ne soit pas un entier. Dans notre cas, e e lexception sera de type NumberFormatException et parseInt l`ve une exception e de ce type qui est propage par calculerJour. Cet objet contient un message qui e informe sur lerreur et nous dcidons de lacher. Remarquez que la variable s nest e aecte que si aucune erreur nest dclenche. Dans ce cas, elle a la valeur quelle avait e e e lors de son initialisation, cest-`-dire ici null. a Le bon emploi des exceptions est assez complexe. Nous renvoyons aux pages web du cours pour plus dinformation (page crite par J. Cervelle). e

12.3.4

Tests exhaustifs du programme

Dans certains cas, on peut rver de faire des tests exhaustifs sur le programme. Ici, e rien nempche de tester toutes les dates correctes possibles entre 1586 et lan 5000 par e exemple. Le seul probl`me ` rsoudre est dcrire ce programme de test, une fois que e a e e lon sait que le 1er janvier 1586 tait un mercredi. e Un programme de test doit tre le plus indpendant possible du programme ` e e a tester. En particulier, il ne faut pas que le programme de test utilise des fonctions du programme test alors que cest justement ce que lon veut tester. . . e Ici, nous allons seulement utiliser le fait que les fonctions estBissextile et ea ee e a nbJoursDansMois ont dj` t testes (ce sont les plus faciles ` tester) et quelles sont correctes. Ensuite, nous allons crire une fonction qui teste tous les jours dune e anne donne, celle-ci itrant sur les mois et le nombre de jours des mois. e e e // ENTREE: j11 est le jour de la semaine qui commence // lanne a. e // SORTIE: le jour de la semaine qui suit le 31/12 de // lanne a. e public static int testerAnnee(int a, int j11){ int jj = j11; for(int m = 1; m <= 12; m++){ int jmax = Jour.nbJoursDansMois(m, a); for(int j = 1; j <= jmax; j++){ // on construit la chane de test String t = j + " " + m + " " + a; t += " " + JourF.jour[jj]; if(! testerJour(t)) System.out.println("PB: "+ t); // on avance dun jour

12.3. UN EXEMPLE DETAILLE jj++; if(jj == 7) jj = 0; } return jj; } public static void testsExhaustifs(){ int j11 = 3; // mercredi 1er janvier 1586 for(int a = 1586; a < 2500; a++){ System.out.println("Test de lanne "+a); e j11 = testerAnnee(a, j11); } }

159

La fonction testerAnnee prend en entre une anne et le jour de la semaine e e correspondant au 1er janvier de lanne. En sortie, la fonction retourne le jour de la e ` semaine du 1er janvier de lanne qui suit. A lintrieur de la fonction, on boucle sur e e les mois et les jours, ainsi que sur le jour de la semaine.

12.3.5

Est-ce tout ?

Nous avons pass du temps a expliquer comment crire un programme raisonnable e ` e bas sur le calcul du jour de la semaine correspondant ` une date. La solution est e a satisfaisante, mais nous avons g des choses qui pour le moment empchent la mise e e a ` jour et lextension du programme, notamment dans lutilisation des entres/sorties. e Nous continuerons le dveloppement de notre programme au chapitre 13. e

12.3.6

Calendrier et formule de Zeller

Nous allons dabord donner la preuve de la formule due au Rvrend Zeller et qui e e rsout notre probl`me. e e Thor`me 3 Le jour J (un entier entre 0 et 6 avec dimanche cod par 0, etc.) core e e respondant ` la date j/m/a est donn par : a e J = (j + 2.6m 0.2 + e + e/4 + s/4 2s) mod 7 o` u (m , a ) = (m 2, a) si m > 2, (m + 10, a 1) si m 2,

et s (resp. e) est le quotient (resp. reste) de la division euclidienne de a par 100, cest-`-dire a = 100s + e, 0 e < 100. a Commenons dabord par rappeler les proprits du calendrier grgorien, qui a t c ee e ee mis en place en 1582 par le pape Grgoire XIII : lanne est de 365 jours, sauf quand e e

160

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

elle est bissextile, i.e., divisible par 4, sauf les annes sculaires (divisibles par 100), qui e e ne sont bissextiles que si elles sont divisibles par 400. Si j et m sont xs, et comme 365 = 7 52 + 1, la quantit J avance de 1 chaque e e anne, sauf quand la nouvelle anne est bissextile, auquel cas J progresse de 2. Il faut e e donc dterminer le nombre dannes bissextiles infrieures ` a. e e e a Dtermination du nombre dannes bissextiles e e Lemme 1 Le nombre dentiers de [1, N ] qui sont divisibles par k est (N, k) = N/k . Dmonstration : les entiers m de lintervalle [1, N ] divisibles par k sont de la forme e m = kr avec 1 kr N et donc 1/k r N/k. Comme r doit tre entier, on a en e fait 1 r N/k . 2 Proposition 4 Le nombre dannes bissextiles dans ]1600, A] est e B(A) = (A 1600, 4) (A 1600, 100) + (A 1600, 400) = A/4 A/100 + A/400 388.

Dmonstration : on applique la dnition des annes bissextiles : toutes les annes e e e e bissextiles sont divisibles par 4, sauf celles divisibles par 100 ` moins quelles ne soient a multiples de 400. 2 Pour simplier, on crit A = 100s + e avec 0 e < 100, ce qui donne : e B(A) = e/4 s + s/4 + 25s 388. Comme le mois de fvrier a un nombre de jours variable, on dcale lanne : on e e e suppose quelle va de mars ` fvrier. On passe de lanne (m, a) ` lanne-Zeller (m , a ) a e e a e comme indiqu ci-dessus. e Dtermination du jour du 1er mars e Ce jour est le premier jour de lanne Zeller. Posons (x) = x mod 7. Supposons e que le 1er mars 1600 soit n, alors il est (n + 1) en 1601, (n + 2) en 1602, (n + 3) en 1603 et (n + 5) en 1604. De proche en proche, le 1er mars de lanne a est donc : e M(a ) = (n + (a 1600) + B(a )). Maintenant, on dtermine n ` rebours en utilisant le fait que le 1er mars 2011 tait un e a e mardi. On trouve n = 3. Le premier jour des autres mois On peut prcalculer le dcalage entre le jour du 1er mars et le jour du 1er des mois e e suivants :

12.3. UN EXEMPLE DETAILLE 1er 1er 1er 1er 1er 1er 1er 1er 1er 1er 1er avril mai juin juillet aot u septembre octobre novembre dcembre e janvier fvrier e 1er 1er 1er 1er 1er 1er 1er 1er 1er 1er 1er mars+3 avril+2 mai+3 juin+2 juillet+3 aot+3 u septembre+2 octobre+3 novembre+2 dcembre+3 e janvier+3

161

Ainsi, si le 1er mars dune anne est un vendredi, alors le 1er avril est un lundi, et e ainsi de suite. On peut rsumer ce tableau par la formule 2.6m 0.2 2, do` : e u Proposition 5 Le 1er du mois m est : (1 + 2.6m 0.2 + e + e/4 + s/4 2s) et le rsultat nal en dcoule. e e

162

CHAPITRE 12. COMMENT ECRIRE UN PROGRAMME

Chapitre 13

Introduction au gnie logiciel en e Java


13.1 Modularit e

Le concept de modularit est fondamental en informatique, et cest une des seules e faons de pouvoir grer les gros programmes ou syst`mes. Nous ne nous occupons pas c e e ici de comment on dcoupe les programmes en modules, mais plutt quels outils nous e o pouvons utiliser et comment. Le concept de classe en Java permet dj` une certaine forme de modularit. Une ea e classe permet de regrouper ensemble toutes les fonctions, donnes, algorithmes qui pere mettent de rsoudre une tche donne. Bien sr, une classe peut utiliser dautres classes, e a e u tre utilises par dautres, etc. Llaboration dune architecture de programme peut soue e e vent tre traduite en une suite (ou un arbre) de classes, qui elles-mmes pourront tre e e e implantes. Dans ce cours, nous nirons pas plus loin, laissant le concept dhritage ` e e a plus tard dans le cursus. Notons que Java permet de regrouper des classes au sein dun paquetage (package). Lintrt des paquetages est de limiter les conits de noms de fonctions, de classes, en ee crant des espaces de nom et des espace daccessibilit. Les fonctions publiques de ces e e paquetages peuvent tre importes par lintermdiaire de linstruction import. e e e Si nous avons dcoup le programme en modules, il est logique de tester chaque e e module de faon spare, cest ce quon appelle les tests unitaires. Quand on a termin, c e e e on passe aux tests dintgration qui eux testent tout le programme. e

13.2

Les interfaces de Java

Les interfaces de Java sont un mcanisme qui permet de dnir le comportement e e dune classe par lintermdiaire des fonctions qui doivent y tre programmes. Il sagit e e e dune sorte de contrat pass par linterface avec une classe qui limplantera. Un proe gramme pourra utiliser une interface, sans rellement savoir quelle classe ralisera lime e plantation, du moment que celle-ci est conforme ` ce qui est demand. Nous allons voir a e deux exemples classique, ceux des piles et des les. 163

164

CHAPITRE 13. INTRODUCTION AU GENIE LOGICIEL EN JAVA

13.2.1

Piles

Nous avons dj` crois les piles dans le chapitre 4. Les informations sont stockes ea e e dans lordre de leur arrive. Le premier lment arriv se trouve dans le fond. Le dernier e ee e arriv peut sortir, ainsi quil est montr dans le dessin qui suit : e e

Avant de donner des implantations possibles des piles, nous pouvons nous demander de quoi exactement nous avons besoin. Nous avons besoin de crer ou dtruire une pile, e e empiler llment au-dessus de la pile, dpiler llment qui est au-dessus. Il est logique ee e ee de pouvoir tester si une pile est vide ou non. Nous avons en fait dni une pile par le e comportement quelle doit avoir, pas par sa reprsentation. On parle classiquement de e type de donnes abstrait. On voit que Java nous fournit un moyen de traiter ce concept e a ` laide dinterface. Dans le cas prsent, en nous limitant pour linstant ` une pile e a contenant des entiers de type int : public interface Pile{ public boolean estVide(); public void empiler(int x); public int depiler(); } Un programme de test pour une telle interface commencera par : public class TesterPile{ public static void testerPile(Pile p){ for(int n = 0; n < 10; n++) p.empiler(n); while(! p.estVide()) System.out.println(p.depiler()); } et on pourra tester toute classe implantant Pile. Arrtons-nous un instant sur cette fonction. Nous voyons que nous pouvons crire du e e code qui dpend juste des proprits des piles, pas de leur implantation. Cest justement e ee ce dont on a besoin pour travailler ` plusieurs : une fois les interfaces spcies, les uns a e e peuvent travailler ` leur utilisation, les autres ` la ralisation concr`te des choses. a a e e Nous allons donner deux implantations possibles de cette classe, la premi`re ` laide e a dun tableau, la seconde ` laide dune liste. Dans les deux cas, nous cachons la gestion a mmoire ` lintrieur des deux classes. De mme, nous ne rendons pas accessibles la e a e e structure de donnes utilise. e e Nous pouvons dnir la classe PileTab en reprsentant celle-ci par un tableau e e dont la taille sera cre ` la construction et ventuellement agrandie (modulo recopie) ee a e lors dune insertion.

13.2. LES INTERFACES DE JAVA

165

public class PileTab implements Pile{ private int taille, hauteur; private int[] t; public PileTab(){ this.taille = 16; this.t = new int[this.taille]; this.hauteur = -1; } public boolean estVide(){ return this.hauteur == -1; } public void empiler(int x){ this.hauteur += 1; if(this.hauteur > this.taille){ int[] tmp = new int[2 * this.taille]; for(int i = 0; i < this.hauteur; i++) tmp[i] = this.t[i]; this.taille = 2 * this.taille; this.t = tmp; } this.t[this.hauteur] = x; } public int depiler(){ return this.t[this.hauteur--]; } } Par convention, la pile vide sera caractrise par une hauteur gale ` -1. Si la hauteur e e e a est positive, cest lindice o` le dernier lment arriv a t stock. u ee e ee e On peut galement utiliser des listes, lide tant de remplacer un tableau de taille e e e xe par une liste dont la taille varie de faon dynamique. La seule restriction sera c la taille mmoire globale de lordinateur. Dans ce qui suit, on va utiliser notre classe e ListeEntier et modier le type de la pile, qui va devenir simplement : public class PileListe implements Pile{ private ListeEntier l; public PileListe(){ this.l = null; } public boolean estVide(){ return this.l == null;

166 }

CHAPITRE 13. INTRODUCTION AU GENIE LOGICIEL EN JAVA

public void empiler(int x){ this.l = new ListeEntier(x, this.l); } public int depiler(){ int c = this.l.contenu; this.l = this.l.suivant; return c; } } Le programme de test pourra alors contenir la fonction principale suivante public static void main(String[] args){ System.out.println("Avec un tableau"); testerPile(new PileTab()); System.out.println("Avec une liste"); testerPile(new PileListe()); }

13.2.2

Files dattente

Le premier exemple est celui dune le dattente ` la poste. L`, je dois attendre a a au guichet, et au dpart, je suis ` la n de la le, qui avance progressivement vers e a le guichet. Je suis derri`re un autre client, et il est possible quun autre client entre, e auquel cas il se met derri`re moi. e Dun point de vue utilisation, nous navons besoin que de linterface suivante1 : public interface FIFO{ public boolean estVide(); public void ajouter(int x); public int supprimer(); } Nous nallons donner ici que limplantation de la FIFO par une liste, laissant le cas du tableau et du programme de test en exercice. Le principe est de grer une liste dont e on conna la tte (appele ici debut) et la rfrence de la prochaine cellule ` utiliser. t e e ee a Les fonctions qui suivent mettent ` jour ces deux variables. a public class FIFOListe implements FIFO{ private ListeEntier debut, fin; public FIFOListe(){ this.debut = this.fin = null; }
1

Nous avons pris le nom anglais FIFO, car File est un mot-clef rserv de Java. e e

13.2. LES INTERFACES DE JAVA

167

public boolean estVide(){ return this.debut == this.fin; } public void ajouter(int x){ if(this.debut == null){ this.debut = new ListeEntier(x, null); this.fin = this.debut; } else{ this.fin.suivant = new ListeEntier(x, null); this.fin = this.fin.suivant; } } public int supprimer(){ int c = this.debut.contenu; this.debut = this.debut.suivant; return c; } }

13.2.3

Les gnriques e e

Nous avons utilis jusqu` prsent des types dentiers pour simplier lexposition. Il e a e ny a aucun probl`me ` utiliser des piles dobjets si le cas sen prsente. e a e Il peut tre intressant de chercher ` faire un type de pile qui ne dpende pas de e e a e faon explicite du type dobjet stock. En Java, on peut raliser cela avec des types c e e gnriques dont nous allons esquisser lusage. e e Lexemple le plus simple est celui dune liste, qui a priori na pas besoin de conna tre le type de ses lments. Pour des raisons techniques, faire une pile dObject nest pas ee susant, car il faut que la liste puisse travailler sur des objets de mme type, mme si e e c ce type nest pas connu. On peut utiliser la classe LinkedList de Java de la faon suivante : LinkedList<Integer> l = new LinkedList<Integer>(); qui dclare l comme tant une liste dInteger. On peut remplacer Integer par e e nimporte quel type. On peut alors utiliser les fonctions classiques : l.add(new Integer("111111")); Integer n = l.getFirst(); Utiliser les classes gnriques donne acc`s ` une grande partie de la richesse des e e e a ` biblioth`ques de Java, orant ainsi un rel confort au programmeur. A titre dexemple, e e la syntaxe for(Integer a : l)

168

CHAPITRE 13. INTRODUCTION AU GENIE LOGICIEL EN JAVA System.out.println(a);

nous permet ditrer sur tous les composants de la liste de faon agrable. Nous verrons e c e a ` la section 14.5.1 comment ce mcanisme nous simplie la vie. e Notez que la classe LinkedList dnit une mthode get(int index) mais quil e e serait extrmement maladroit de vouloir crire litration sous la forme e e e for(int i = 0; i < l.size(); i++){ Integer a = l.get(i); System.out.println(a); } La classe LinkedList implante linterface List LinkedList<Integer> implements List<Integer>{...} correspond ` la dnition du mod`le collection squentielle indexe. Une autre a e e e e a implantation de List est ArrayList qui correspond ` la notion de tableau dynamique (sa taille est double automatiquement quand il est plein). e La classe LinkedList implante aussi linterface Queue (terme anglais pour le) et on peut crire ainsi : e Queue<Integer> q = new LinkedList<Integer>(); Cela dispense les concepteurs de la librairie Java dcrire une implantation partie culi`re de Queue alors que LinkedList est tr`s bien pour cela. Pour lutilisateur, cela e e apporte plus de lisibilit et de sret de son code car il ne peut appliquer ` q que les e u e a quelques fonctions dnies dans Queue. e

13.3

Retour au calcul du jour de la semaine

Nous poursuivons ici lexemple du chapitre 12, en donnant un exemple de cration e de paquetage. Nous allons dcider de crer un paquetage calendrier qui va regrouper une e e classe traitant le calendrier grgorien (et donc appele Gregorien), puis une classe e e AfficherDate qui ache la rponse dans un terminal. Cest ` ce moment quon peut e a se poser la question de linternationalisation. Ce nest pas ` classe Gregorien de soca cuper de la traduction en langage de sortie. Ceci nous am`ne ` modier le type de la e a fonction calculerJour et ` propager cette nouvelle smantique. a e En Unix, Java nous impose de crer un rpertoire calendrier, qui va contenir e e les deux classes Gregorien (dans un chier Gregorien.java) et AfficherDate (dans AfficherDate.java). Chacun de ces chiers doit commencer par la commande package calendrier; qui les identie comme appartenant ` ce paquetage. Le chier Gregorien.java a contient donc package calendrier; public class Gregorien{

13.3. RETOUR AU CALCUL DU JOUR DE LA SEMAINE

169

public static int[] JOURS_DANS_MOIS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static boolean estBissextile(int a){ if((a % 4) != 0) return false; if((a % 100) != 0) return true; return ((a % 400) == 0); } public static int nbJoursDansMois(int m, int a){ if((m != 2) || !estBissextile(a)) return JOURS_DANS_MOIS[m-1]; else return 29; } public static boolean donneesCorrectes(int j, int m, int a){ if(a <= 1584) return false; if((m < 1) || (m > 12)) return false; if((j < 1) || (j > nbJoursDansMois(m, a))) return false; return true; } // Calcul du jour de la semaine correspondant ` // a la date j / m / a, sous la forme dun entier // J tel que 0 <= J <= 6, avec 0 == dimanche, etc. static int jourZeller(int j, int m, int a){ int mz, az, e, s, J; // calcul des mois/annes Zeller e mz = m-2; az = a; if(mz <= 0){ mz += 12; az--; } // az = 100*s+e, 0 <= e < 100 s = az / 100; e = az % 100; // la formule du rvrend Zeller e e

170

CHAPITRE 13. INTRODUCTION AU GENIE LOGICIEL EN JAVA J = j + (int)Math.floor(2.6*mz-0.2); J += e + (e/4) + (s/4) - 2*s; // attention aux nombres ngatifs e if(J >= 0) J %= 7; else{ J = (-J) % 7; if(J > 0) J = 7-J; } return J; } // SORTIE: un jour entre 0 et 6 ou -1 en cas derreur public static int calculerJour(String sj, String sm, String sa){ int j, m, a; j = Integer.parseInt(sj); m = Integer.parseInt(sm); a = Integer.parseInt(sa); if(donneesCorrectes(j, m, a)) return jourZeller(j, m, a); else return -1; }

} Le nom de la classe est dsormais calendrier.Gregorien. e Le chier AfficherDate.java contient package calendrier; public class AfficherDate{ public final static String[] JOUR = {"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"}; public static void afficherJour(String sj, String sm, String sa, int jZ, String lg){ if(lg.equals("fr")){ String s = JOUR[jZ]; System.out.print("Le "+sj+"/"+sm+"/"+sa); System.out.println(" est un "+s+"."); }

13.3. RETOUR AU CALCUL DU JOUR DE LA SEMAINE } }

171

Nous avons bauch un dbut dinternationalisation du programme en rajoutant un e e e c param`tre dachage (le choix par la langue lg), ainsi que les noms de jours en franais. e Notons que pour aller plus loin, il faut crire des fonctions dachage par langue (la e grammaire nest pas la mme en anglais, etc.). e On les utilise alors comme suit, dans le chier Jour.java, au mme niveau que le e rpertoire calendrier e // importation de toutes les classes du paquetage import calendrier.*; public class Jour{ public static void main(String[] args){ String sj, sm, sa; int jZ = -1; if(args.length < 3){ System.out.println("Pas assez de donnes"); e return; } sj = args[0]; sm = args[1]; sa = args[2]; try{ jZ = Gregorien.calculerJour(sj, sm, sa); // (*) } catch(Exception e){ System.err.println("Exception: " + e.getMessage()); } if(jZ != -1) // (*) AfficherDate.afficherJour(sj, sm, sa, jZ, "fr"); else System.out.println("Donnes incorrectes"); e return; } } Les appels aux classes sont modies dans les lignes marques dune toile (*) cie e e dessus. Et nous avons demand un achage en franais. e c Nous laissons au lecteur le soin de mettre ` jour les fonctions de test de ce proa gramme pour tenir compte du changement de type de certaines fonctions, ainsi que pour linternationalisation. Maintenant, nous avons isol chacune des phases du programme dans des classes e bien dnies, que lon peut rutiliser dans dautres contextes, par exemple celle dune e e application X11, ou bien encore dans une application de tlphone android, une appliee cation de type rseau, etc. e

172

CHAPITRE 13. INTRODUCTION AU GENIE LOGICIEL EN JAVA

Chapitre 14

Modlisation de linformation e
Dans ce chapitre1 , nous allons passer en revue direntes faons de modliser, stoe c e cker et traiter linformation, ` travers de nombreux exemples, en utilisant les objets et a structures dnies dans les parties prcdentes. e e e

14.1
14.1.1

Modlisation et ralisation e e
Motivation

Nous considrons ici que de linformation consiste en des donnes (ou de linformae e tion plus parcellaire) et des relations entre ces donnes. Certains mod`les dinformation e e peuvent aussi prciser des contraintes que les donnes ou les relations doivent vrier. e e e Enn, un mod`le inclut une dnition des oprations possibles et permettant de maine e e tenir les contraintes. Pour un mme mod`le, il existe souvent plusieurs structures de donnes qui pere e e mettent de le raliser. La structure idale nexiste pas toujours, et on doit souvent e e choisir de privilgier lecacit de telle ou telle opration au dtriment dautres. Il e e e e est mme possible de faire cohabiter plusieurs ralisations du mme mod`le. Sparer e e e e e modlisation et ralisation est donc fondamental. e e Dans un langage comme Java, la sparation entre mod`le et ralisation est ` peu e e e a pr`s dcrite par le tandem interface/implmentation . Il manque pourtant le moyen e e e de spcier les contraintes et dexiger quune ralisation les implante. Cela reste souvent e e sous la format dun contrat , exprim dans la documentation et que le programmeur e de la structure de donnes, dune part, et lutilisateur, dautre part, doivent seorcer e de respecter chacun ` son niveau de responsabilit. a e Alors quil est gnralement facile didentier les donnes quun programme devra e e e manipuler, il est gnralement plus dicile de recenser de mani`re exhaustive les relae e e e tions et les contraintes. Comme on la vu au chapitre 12, il est pourtant impratif de faire cela d`s la premi`re tape de la conception car se rendre compte dventuels oublis e e e e ou imprcisions, lors de la programmation ou lors des tests, peut conduire ` dnormes e a e pertes de temps. Il est alors rassurant et avantageux de pouvoir facilement relier son probl`me ` un mod`le type, quand cest possible. Cela permet ensuite, sans trop se e a e poser de questions, de prendre sur ltag`re les bons composants pour le raliser. e e e
1

chapitre crit avec P. Chassignet e

173

174

CHAPITRE 14. MODELISATION DE LINFORMATION

14.1.2

Exemple : les donnes e

Un exemple particulier est la notion de multiplet (lment dun produit cartsien) ee e qui permet dassocier des donnes de types disparates mais connus et en nombre ni. e La traduction en Java est une classe qui est une reprsentation du produit cartsien, e e chaque objet de cette classe pouvant reprsenter un multiplet particulier. Nanmoins la e e modlisation par multiplet nimplique pas ncessairement une correspondance directe e e entre les composants du multiplet et les champs de lobjet. Par exemple, si on consid`re la reprsentation des nombres complexes, on peut e e considrer quun nombre complexe associe quatre donnes qui sont sa partie relle, sa e e e partie imaginaire, son module et son argument. Nanmoins, il existe les r`gles bien e e connues qui relient ces quatre donnes et il serait maladroit de reprsenter un nombre e e complexe comme un objet ayant quatre champs. L` aussi la sparation entre mod`le et ralisation nous permet de concilier les deux a e e e points de vue. On peut ainsi dire quun nombre complexe est dni par une interface e Java qui comporte huit mthodes, une pour obtenir la valeur, une pour la modier et e ce pour les quatre grandeurs. Ensuite, on peut envisager une ralisation base sur la e e reprsentation cartsienne et une autre sur la reprsentation polaire (ie. deux classes e e e qui implantent linterface). Chacune de ces classes ne dnit que deux champs (prie vate), avec quatre mthodes qui y acc`dent directement et quatre autres mthodes qui e e e font le changement de reprsentation requis ` chaque acc`s. Le choix dinstancier un e a e objet plutt de lune ou de lautre de ces classes dpend ensuite de lapplication. Par o e exemple, si lon a majoritairement des multiplications de nombres complexes ` traiter, a on priviligiera la reprsentation polaire. e Sans changer de mod`le, on peut galement raliser une classe pour dnir des e e e e constantes complexes o` les mthodes pour modier ne font rien ou lance une exception. u e Nous allons maintenant considrer des associations de donnes de mme type. Ce e e e type peut tre un type lmentaire, un produit cartsien dj` dni ou un super-type e ee e ea e (comme une interface Java) qui permet de manipuler de mani`re uniforme des types a e priori disparates.

14.2

Conteneurs, collections et ensembles

Nous introduisons ici un type abstrait tr`s gnral qui est celui de conteneur pour e e e lequel sont dnies lajout et la suppression dun lment, le test dappartenance dun e ee lment et litration cest-`-dire un mcanisme pour considrer un ` un tous les ee e a e e a lments du conteneur. On ajoute gnralement un acc`s direct au nombre dlments ee e e e ee contenus, pour ne pas avoir ` calculer cela par une itration, ainsi que la possibilit de a e e vider le conteneur en une seule opration. e La collection est le type de conteneur le plus vague dans lequel il est permis de placer plusieurs occurrences dune mme donne. Lors dune itration de la collection, e e e cette donne sera alors considre plusieurs fois. e ee Lensemble, correspondant ` la dnition classique en mathmatiques, est une cola e e lection avec une contrainte dunicit. Cette contrainte est gnralement assure dans e e e e la mthode dajout qui doit procder ` lquivalent dun test dappartenance avant e e a e dajouter eectivement. Avec des ralisations na e ves, par exemple, un tableau ou une liste cha ee, le test dappartenance et la contrainte dunicit cotent cher car il faut n e u itrer sur tout le conteneur pour vrier labsence dun lment. Des structures darbre e e ee

14.2. CONTENEURS, COLLECTIONS ET ENSEMBLES

175

particuli`res permettent de raliser ce test en temps logarithmique et le hachage permet e e de le faire en temps quasi-constant. A priori, une collection ou un ensemble ne sont pas ordonns, cest-`-dire que lordre e a dnumration de leurs lments nest pas dni. En fait la collection non ordonne e e ee e e nexiste pas. Selon la structure de donnes qui est utilise pour raliser le stockage, il e e e existe toujours un ordre dterministe dnumration, sauf ` ajouter explicitement de e e e a lalatoire lors de litration. Une exception notable est le cas o` la structure souse e u jacente utilise le hachage et lordre dnumration, bien que dterministe, est alors e e e dicilement prdictible. e On va maintenant considrer des conteneurs ordonns. Selon les procds les plus e e e e courants, lordre des lments dans le conteneur est dni soit par lordre ou la position ee e de leur ajout, soit par une relation dordre dnie sur les donnes. e e

14.2.1

Collections squentielles e

Il sagit des collections o` lordre des lments est principalement dni par lordre u ee e de leur ajout. Notons que cela permet de considrer de nouvelles oprations dajout, e e dacc`s ou de suppression qui utilisent le numro dordre (la position) dans le contee e neur pour dsigner o` oprer. On parle alors de collection squentielle indexe. Des e u e e e ralisations particuli`rement simples sont possibles ` partir de tableaux ou de listes e e a cha ees mais il faut faire attention aux fonctions que lon tient ` privilgier car elles n a e nont pas toutes la mme ecacit selon la structure sous-jacente. e e Il existe des cas o` on peut restreindre les oprations permises ` un lment paru e a ee ticulier. Par exemple, on veut que lacc`s et la suppression ne soient possibles que sur e le dernier ou le premier ajout. Ce sont respectivement les piles et les les. Le fait de e les identier comme des mod`les ` part permet de clarier lexpression des algorithmes e a qui les utilisent. Le fait de leur ddier des ralisations particuli`res qui limitent les e e e oprations permises permet dviter les erreurs et parfois doptimiser. e e On retrouve ainsi les piles et les les prsentes au chapitre 13. e e

14.2.2

Collections ordonnes e

Il sagit des collections o` lordre des lments est dni par une relation dordre u ee e total sur ces lments et donc indpendant de lordre dans lequel ils sont ajouts ee e e dans la collection. Une ralisation ecace utilise gnralement des structures darbres e e e quilibrs. e e Si la collection est relativement statique, cest-`-dire quelle est constitue au dpart a e e et quensuite, on se contente de la consulter, une alternative consiste ` la former en a triant les lments dune collection squentielle. ee e De mme quune le peut tre vue comme une collection squentielle particuli`re, e e e e on peut dnir la le de priorit comme une collection ordonne dont on restreint les e e e oprations permises. e Exemple : le de priorit e Dans certains cas, une le dattente ne sut pas ` nos besoins. Les donnes peuvent a e arriver avec une priorit. Par exemple, dans une le dimpression, on peut dcider e e quun utilisateur est prioritaire. Un ordonnanceur de syst`me dexploitation a galement e e

176

CHAPITRE 14. MODELISATION DE LINFORMATION

plusieurs les de priorit ` grer. Cest comme a quun bon chef doit galement grer ea e c e e ses aaires courantes. . . Que demandons-nous ` notre mod`le ? Il sut de deux actions notables, la premi`re a e e de stocker un nouvel lment avec sa priorit, la seconde de pouvoir demander quelle ee e est la tche prioritaire suivante a traiter. Accessoirement, nous pourrions insister pour a ` que cette gestion soit rapide, mais le typage ne sut pas pour cela. Il est clair que si le nombre de tches est x une fois pour toutes, on peut trier le tableau des tches en a e a fonction de leur priorit et nous navons plus rien ` faire. Ce qui nous intresse ici est e a e le cas o` des tches arrivent de faon dynamique, et sont traites de faon dynamique u a c e c (par exemple limpression de chiers avec priorit). Linterface dsire, qui ressemble ` e e e a celle dune le dattente normale est public interface FileDePriorite{ public boolean estVide(); public void ajouter(String x, int p); public int tacheSuivante(); } Nous laissons au lecteur le soin dinventer une classe qui implante cette interface a ` laide dun tableau, en sinspirant de ce qui a t fait pour les piles et les les. Nul ee doute que le rsultat aura une complexit proche de O(n2 ) si n est le nombre de tches e e a traites. Lutilisation dun tas (cf. 9.3.3) nous permet de faire mieux, avec un tableau e de taille n et des complexits en O(n log n). e On peut implanter une le de priorit oprant sur des chiers et des priorits en e e e raisonnant sur des couples (String, int) et il est facile de modier la classe Tas (cf. section 9.3.3) en TasFichier. On pourrait alors crer e public class Impression implements FileDePriorite{ TasFichier tas; public Impression(){ this.tas = new TasFichier(); } public boolean estVide(){ return this.tas.estVide(); } public void ajouter(String f, int priorite){ this.tas.ajouter(f, priorite); } public String tacheSuivante(){ return this.tas.tacheSuivante(); } } Nous laissons au lecteur le soin de terminer.

14.3. ASSOCIATIONS

177

14.3

Associations

Formellement une table dassociation peut tre vue comme une fonction qui, a un ene semble ni de donnes dites les clefs, associe dautres donnes. On peut aussi considrer e e e cela comme un ensemble de couples (clef, donnes) avec une fonction particuli`re qui e e consiste ` retrouver les donnes associes ` une clef. a e e a Il y a diverses ralisations possibles. Celle qui consisterait ` utiliser la reprsentation e a e na par une liste de couples est maladroite puisquelle conduit ` programmer la reve a cherche squentielle du couple ayant la clef considre. La solution la plus ecace est e ee une table de hachage organise suivant les clefs. e Si lon a besoin de maintenir un ordre particulier sur les clefs, par exemple un annuaire tri par ordre alphabtique, le concept de table est prcis comme tant ordonn. e e e e e e Son interprtation au niveau modlisation comme ensemble ordonn de couples (clef, e e e donnes) avec une fonction de recherche particuli`re par la clef doit alors suggrer au e e e programmeur que sa bonne ralisation est larbre de couples (clef, donnes) organis e e e en un arbre binaire de recherche sur les clefs qui permet une recherche relativement rapide.

14.4
14.4.1

Information hirarchique e
Exemple : arbre gnalogique e e

Une personne p a deux parents (une m`re et un p`re), qui ont eux-mmes deux e e e parents. On aimerait pouvoir stocker facilement une telle relation. Une structure de donne qui simpose naturellement est celle darbre. Il est ` noter que la relation p`ree a e ls dans cette application est ` linverse de celle qui est employe par les informaticiens a e pour dcrire la structure de donnes en arbre. Cet exemple particuli`rement frappant e e e de conit de terminologies illustre combien il est important de bien sparer les tapes e e conceptuelles entre la dnition et la ralisation. Illustrons notre propos par un dessin, e e construit grce aux bases de donnes utilises dans le logiciel GeneWeb ralis par a e e e e Daniel de Rauglaudre2 . On remarquera quen informatique, on a tendance ` dessiner a les arbres la racine en haut. Louis XIV niveau 0

Louis XIII

Anne dAutriche

niveau 1

Henri IV

Marie de Mdicis e

Philippe III

Marie-Marguerite dAutriche

niveau 2

Fig. 14.1 Un arbre gnalogique. e e


2

http ://cristal.inria.fr/ddr/GeneWeb/

178

CHAPITRE 14. MODELISATION DE LINFORMATION

Une ralisation possible reprend la structure de tas vue en 9.3.3. Pour mmoire, e e on utilise un tableau a, de telle sorte que a[1] (au niveau 0) soit la personne initiale et on continue de proche en proche, en dcidant que a[i] aura pour p`re a[2*i], e e pour m`re a[2*i+1], et pour enfant (si i > 1) la case a[i/2]. Nanmoins, une telle e e structure de donnes ne permet pas de rpondre facilement ` une question comme qui e e a est le p`re dAnne dAutriche ?. Une modlisation par des tables dassociation, une e e pour chaque relation p`re, m`re, enfant, est alors prfrable. Notons aussi que e e ee ce que lon cherche ` faire avec de linformation hirarchique ressemble beaucoup ` ce a e a que lon va voir en 14.5. Ce nest pas tonnant puisque les arbres peuvent tre vus e e comme des graphes avec des contraintes particuli`res. e

14.4.2

Autres exemples

On trouve galement ce type de reprsentation hirarchique dans la classication e e e des esp`ces ou par exemple un organigramme administratif. e

14.5

Quand les relations sont elles-mmes des donnes e e

Que faire quand les liens sont plus complexes, comme par exemple quand on doit reprsenter la carte dun rseau routier, un circuit lectronique, la liste de ses amis ? e e e On utilise alors souvent des graphes, qui permettent de coder de telles informations. Par exemple, on peut imaginer grer une carte du rseau ferr en stockant les gares de e e e France et les direntes distances entre ces gares. e

14.5.1

Un exemple : un rseau social e

Nous allons prendre comme exemple directeur le cas de rseaux3 damis4 . De fait, e nous allons donner une autre implantation dun graphe avec des applications lg`rement e e direntes. e Spcication e Un ami est un membre du rseau. Un membre du rseau a un nom et une collections e e damis. Nous allons choisir dimplanter la relation je suis lami de. Nous supposerons que si Claude a pour ami Dominique, alors la rciproque est vraie (la relation est e symtrique). e Que demander au rseau ? Dabord de pouvoir rajouter de nouveaux membres. Un e nouvel arrivant peut sincrire comme membre isol, ou bien arriver comme tant ami e e dun membre. On consid`re que ladresse lectronique est susante pour indentier un e e membre de faon unique. c Choix des structures de donnes e Comme nous voulons garder lidentication unique des membres, il nous faut un moyen de tester si un membre existe dj`, et ce test doit tre rapide. Il nous faut donc ea e un ensemble de membres qui supporte les oprations dajout et dappartenance (ainsi e
3 4

Toute ressemblance avec des rseaux sociaux existant ne pourrait tre que fortuite. e e Nous prenons ici ami dans un sens neutre.

14.5. QUAND LES RELATIONS SONT ELLES-MEMES DES DONNEES

179

que la suppression, mais nous laissons cela de ct pour le moment), ce qui tend vers oe une solution ` base de hachage. a Programmation On commence par les plus classiques, qui codent les membres et les listes damis, ea e e a en utilisant la classe LinkedList dj` prsente ` la section 13.2.3. import java.util.*; // ncessaire pour utiliser LinkedList e public class Membre{ String nom; LinkedList<Membre> lamis; public Membre(String n){ this.nom = n; this.lamis = new LinkedList<Ami>(); } public String toString(){ return this.nom; } } Nous en avons prot pour dnir des mthodes pratiques, comme toString qui e e e permet dacher simplement un membre. La gestion dune amiti unidirectionnelle e (ajout dun membre dans la liste damis dun autre membre) est simple : public void ajouterAmitie(Membre b){ this.lamis.add(b); } Passons ` limplantation de la classe ReseauSocial. Nous allons utiliser une table a de hachage pour stocker les membres dj` prsents, ce qui va nous simplier la gestion. ea e import java.util.*; public class ReseauSocial{ private String nom; private HashMap<String, Membre> hm; public ReseauSocial(String n){ this.nom = n; this.hm = new HashMap<String, Membre>(); } // retourne le membre dont n est le nom public Membre deNom(String n){ return this.hm.get(n); }

180

CHAPITRE 14. MODELISATION DE LINFORMATION public boolean estMembre(String nom){ return this.hm.containsKey(nom); } public void creerMembre(String nom){ if(! this.estMembre(nom)) this.hm.put(nom, new Membre(nom)); } public void ajouterAmitie(String nom_a, String nom_b){ Membre a = this.hm.get(nom_a); Membre b = this.hm.get(nom_b); a.ajouterAmitie(b); b.ajouterAmitie(a); }

} Un membre nest cr que sil nexiste pas dj`. On ajoute une amiti de mani`re ee ea e e symtrique (notons que nous pourrions provoquer une exception dans le cas o` a ou b e u ne seraient pas membres, mais nous simplions ici). Pour acher tous les membres prsents dans le rseau, on utilise simplement e e public void afficherMembres(){ for(Membre a : this.hm.values()) System.out.println(a); } De mme, nous pouvons acher toutes les amitis : e e public void afficherAmities(){ for(Membre a : this.hm.values()){ System.out.print("Les amis de "+a+" :"); a.afficherAmities(); System.out.println(); } } a ` condition davoir implant dans la classe Membre. e public void afficherAmities(){ for(Membre a : this.lamis) System.out.print(a + " "); System.out.println(); } Nous avons tout ce quil faut pour tester nos classes et crer un rseau ` nous e e a public class FB311{ public static void main(String[] args){ ReseauSocial RS = new ReseauSocial("FB311");

14.5. QUAND LES RELATIONS SONT ELLES-MEMES DES DONNEES String[] inf311 = {"s@", "d@", "r@", "p@", "2@"}; RS.creerMembre("m@"); RS.creerMembre("g@"); RS.creerMembre("c@"); RS.creerMembre("A@"); RS.creerMembre("Z@"); RS.creerMembre("E@"); RS.creerMembre("F@"); RS.ajouterAmitie("m@", RS.ajouterAmitie("m@", RS.ajouterAmitie("c@", RS.ajouterAmitie("c@", RS.ajouterAmitie("A@", "g@"); "c@"); "g@"); "A@"); "Z@");

181

RS.ajouterAmitie("E@", "F@"); for(int i = 0; i < inf311.length; i++){ RS.creerMembre(inf311[i]); RS.ajouterAmitie("m@", inf311[i]); } System.out.println("Voici tous les membres en stock"); RS.afficherMembres(); RS.afficherAmities(); } } qui va nous fournir Voici tous les E@ s@ F@ r@ 2@ p@ d@ m@ Z@ A@ c@ g@ Les amis de E@ Les amis de s@ Les amis de F@ Les amis de r@ Les amis de 2@ membres en stock

: : : : :

F@ m@ E@ m@ m@

182 Les Les Les Les Les Les Les amis amis amis amis amis amis amis de de de de de de de p@ d@ m@ Z@ A@ c@ g@ : : : : : : : m@ m@ g@ A@ c@ m@ m@

CHAPITRE 14. MODELISATION DE LINFORMATION

c@ s@ d@ r@ p@ 2@ Z@ g@ A@ c@

ce qui correspond au dessin de la gure 14.2, o` chaque trait reliant deux membres u symbolise une amiti. e m g E F

Fig. 14.2 Le graphe exemple. Passons ` quelque chose de plus compliqu. Nous souhaitons maintenant acher les a e amis des amis, au sens o` nous faisons lunion ensembliste. La premi`re fonction qui u e vient ` lesprit est la suivante (dans la classe Membre) : a public void afficherAmisDeMesAmis(){ for(Ami a : this.lamis) a.afficherAmities(); } Les lignes Membre m = RS.deNom("m@"); System.out.println("Voici les amis des amis de "+m); m.afficherAmisDeMesAmis(); System.out.println(); nous donnent Voici les amis des amis de m@ g@ m@ c@ c@ m@ g@ A@ s@ m@ d@ m@ r@ m@ p@ m@ 2@ m@

14.5. QUAND LES RELATIONS SONT ELLES-MEMES DES DONNEES

183

La rponse est tr`s redondante et ` peu pr`s sans intrt (en plus de ne pas donner e e a e ee lensemble voulu). Pour rsoudre ce probl`me, nous allons regarder sur un dessin le e e comportement que nous souhaiterions. Les amis de m@ sont faciles ` trouver, nous les a avons cercls sur le dessin de la gure 14.3 (avec la convention que m@ est son propre e ami). m g

Fig. 14.3 Le graphe exemple avec les amis ` distance 1. a Pour trouver les amis des amis, il sut maintenant dajouter ` cet ensemble tous a les amis non encore visits. Autrement dit, dans un premier temps, on construit la liste e des amis ` distance 1 de m@, puis on parcourt cette liste pour rajouter des membres a non encore vus. Pour tester si un membre a dj` t vu ou non, on utilise une table de eaee hachage. public void afficherAmisDeMesAmis2(){ HashSet<Membre> dejavus = new HashSet<Membre>(); LinkedList<Membre> l = new LinkedList<Membre>(); dejavus.add(this); // on traite le cas des amis for(Membre a : this.lamis){ l.add(a); System.out.print(" " + a); dejavus.add(a); } // cas des amis des amis for(Membre a : l) for(Membre b : a.lamis) if(! dejavus.contains(b)){ System.out.print(" " + b); dejavus.add(b); } } qui donne le rsultat voulu e Voici les amis des amis de m@ g@ c@ s@ d@ r@ p@ 2@ A@

184

CHAPITRE 14. MODELISATION DE LINFORMATION

14.6

Automates

Pour le moment, nous avons considr des donnes ges dans le temps. On peut ee e e aussi modliser facilement des processus plus compliqus, par exemple le fonctionnee e ment dune machine, dun programme, etc.. Les automates permettent de modliser des e fonctionnements simples.

14.6.1

Lexemple de la machine ` caf a e

Regardons une machine ` caf. La partie de son fonctionnement qui nous intresse a e e est celui o` lon doit vrier quun client a bien mis la somme demande, sachant quun u e e caf vaut 30 centimes, et quil a droit ` des pi`ces de 10 ou 20 centimes. e a e Dans ltat initial, la machine attend un client, et est prte ` lui servir un caf. En e e a e fonction de la premi`re pi`ce entre par lutilisateur, ltat de la machine change : si un e e e e utilisateur entre une pi`ce de 20 centimes, cet tat devient jai reu une pi`ce de 20. e e c e Si maintenant la machine reoit une pi`ce de 10, la somme totale est 30, la machine c e peut lancer la prparation du caf, puis revenir ` ltat initial o` elle attend le prochain e e a e u client. Si la machine reoit 10 centimes, elle passe dans ltat jai reu pour linstant c e c 20 centimes. Si elle reoit maintenant 10 centimes, elle passe dans ltat de service c e comme en 10. On modlise ce fonctionnement par le graphe de la gure 14.4 : ltat initial est le e e sommet i ; ltat 1 est cod par le sommet 1, et on rajout sur larc (i, 1) linformation e e e jai reu 20 centimes. On trace un arc de 1 vers le sommet 2, qui symbolise ltat de c e dlivrance du caf avant retour ` ltat i. On compl`te sans dicult les autres tats e e a e e e e et arcs.

i 10 20 10 10 3

20

1 10 2

20

Fig. 14.4 Un automate.

14.6.2

Dnitions e

Un automate ni A est un quintuplet A = (Q, , , I, F ) o` : u est un alphabet donn, e Q est un ensemble ni dtats, e I Q est lensemble des tats initiaux, e F Q est lensemble des tats naux, e est un sous-ensemble de Q Q, ensemble des transitions.

14.6. AUTOMATES

185

Il est commode de reprsenter un automate par un graphe, cest-`-dire un ensemble e a de nuds (sommets) relis par des `ches (arcs). Les tats initiaux (resp. naux) sont e e e symboliss par une `che entrante (resp. sortante). e e On dit que lautomate est dterministe si #I = #F = 1 (on a un seul point dentre, e e un seul point de sortie), et il ny a pas darcs doubles (deux `ches ayant mmes point e e de dpart et darrive). e e Soit (p, a, q) une transition : p est lorigine, a ltiquette, q lextrmit ; on note e e e a p q. Un chemin est une suite de transitions successives : c = (q0 , a1 , q1 )(q1 , a2 , q2 ) (qn1 , an , qn ). On le note
1 n c : q0 q1 qn1 qn .

Un chemin est russi (ou acceptant) si q0 I, qn F . e Exemple, pour lautomate de la gure 14.4, on a = {10, 20}, Q = {i, 1, 2, 3}, F = {i}.

14.6.3

Reprsentations dautomate dterministe e e

Si lautomate est simple, on peut le programmer sous forme de switch : char c = in.read(); int q = q0; switch(q){ case 0: if (c == a) ... else if (c == b) ...; break; case 1: if (c == b) ... else if (c == c) ... ; break; ... case 10: if ... break; } Dans un cas plus gnral, tout codage de graphe convient. Un avantage est que e e lautomate devient une donne du programme, plutt que dtre cod en dur par des e o e e instructions comme switch.

186

CHAPITRE 14. MODELISATION DE LINFORMATION

Quatri`me partie e

Problmatiques classiques en e informatique

187

Chapitre 15

Recherche exhaustive
Ce que lordinateur sait faire de mieux, cest traiter tr`s rapidement une quantit e e gigantesque de donnes. Cela dit, il y a des limites ` tout, et le but de ce chapitre e a est dexpliquer sur quelques cas ce quil est raisonnable dattendre comme temps de rsolution dun probl`me. Cela nous permettra dinsister sur le cot des algorithmes et e e u sur la faon de les modliser. c e

15.1

Rechercher dans du texte

Commenons par un probl`me pour lequel de bonnes solutions existent. Rechercher c e une phrase dans un texte est une tche que lon demande ` nimporte quel programme a a de traitement de texte, ` un navigateur, un moteur de recherche, etc. Cest galement a e une part importante du travail accompli rguli`rement en bio-informatique. e e Vues les quantits de donnes gigantesques que lon doit parcourir, il est crucial de e e faire cela le plus rapidement possible. Dans certains cas, on na mme pas le droit de lire e plusieurs fois les donnes. Le but de cette section est de prsenter quelques algorithmes e e qui accomplissent ce travail. Pour modliser le probl`me, nous supposons que nous travaillons sur un texte T e e (un tableau de caract`res char[], plutt quun objet de type String pour allger e o e un peu les programmes) de longueur n dans lequel nous recherchons un motif M (un autre tableau de caract`res) de longueur m que nous supposerons plus petit que e n. Nous appelerons occurence en position i 0 la proprit que T[i]=M[0], . . . , ee T[i+m-1]=M[m-1]. Recherche na ve Cest lide la plus naturelle : on essaie toutes les positions possibles du motif en e dessous du texte. Comment tester quil existe une occurrence en position i ? Il sut dutiliser un indice j qui va servir ` comparer M[j] ` T[i+j] de proche en proche : a a public static boolean occurrence(char[] T, char[] M, int i){ for(int j = 0; j < M.length; j++) if(T[i+j] != M[j]) return false; return true; } 189

190

CHAPITRE 15. RECHERCHE EXHAUSTIVE

Nous utilisons cette primitive dans la fonction suivante, qui teste toutes les occurences possibles : public static void naif(char[] T, char[] M){ System.out.print("Occurrences en position :"); for(int i = 0; i < T.length-M.length; i++) if(occurrence(T, M, i)) System.out.print(" "+i+","); System.out.println(""); } Si T contient les caract`res de la cha "il fait beau aujourdhui" et M ceux e ne de "au", le programme achera Occurrences en position: 10, 13, Le nombre de comparaisons de caract`res eectues est au plus (n m)m, puisque e e chacun des n m tests en demande m. Si m est ngligeable devant n, on obtient un e nombre de lordre de nm. Le but de la section qui suit est de donner un algorithme faisant moins de comparaisons. Notons que lon peut adapter cet algorithme au cas o` u M est lu ` la vole (et donc pas stock en mmoire). a e e e Algorithme linaire de Karp-Rabin e Supposons que S soit une fonction (non ncessairement injective) qui donne une e valeur numrique ` une cha de caract`res quelconque, que nous appelerons signature : e a ne e nous en donnons deux exemples ci-dessous. Si deux cha nes de caract`res C1 et C2 sont e identiques, alors S(C1 ) = S(C2 ). Rciproquement, si S(C1 ) = S(C2 ), alors C1 ne peut e tre gal ` C2 . Insistons lourdement sur le fait que S(C1 ) = S(C2 ) nimplique pas e e a C1 = C2 . Le principe de lalgorithme de Karp-Rabin utilise cette ide de la faon suivante : e c on remplace le test doccurrence T [i..i + m 1] = M [0..m 1] par S(T [i..i + m 1]) = S(M [0..m 1]). Le membre de droite de ce test est constant, on le prcalcule donc et il e ne reste plus qu` eectuer n m calculs de S et comparer la valeur S(T [i..i + m 1]) ` a a cette constante. En cas dgalit, on souponne une occurrence et on la vrie ` laide e e c e a de la fonction occurrence prsente ci-dessus. Le nombre de calculs ` eectuer est e e a simplement 1 + n m valuations de S. e Voici la fonction qui implante cette ide. Nous prciserons la fonction de signature e e S plus loin (code ici sous la forme dune fonction signature) : e public static void KR(char[] T, char[] M){ int n, m; long hT, hM; n = T.length; m = M.length;

15.1. RECHERCHER DANS DU TEXTE System.out.print("Occurrences en position :"); hM = signature(M, m, 0); for(int i = 0; i < n-m; i++){ hT = signature(T, m, i); if(hT == hM){ if(occurrence(T, M, i)) System.out.print(" "+i+","); else System.out.print(" ["+i+"],"); } } System.out.println(""); }

191

La fonction de signature est critique. Il est dicile de fabriquer une fonction qui soit ` la fois injective et rapide ` calculer. On se contente dapproximations. Soit X un a a texte de longueur m. En Java ou dautres langages proches, il est gnralement facile de e e convertir un caract`re en nombre. Le codage unicode reprsente un caract`re sur 16 bits e e e et le passage du caract`re c ` lentier est simplement (int)c. La premi`re fonction e a e a ` laquelle on peut penser est celle qui se contente de faire la somme des caract`res e reprsents par des entiers : e e public static long signature(char[] X, int m, int i){ long s = 0; for(int j = i; j < i+m; j++) s += (long)X[j]; return s; } Avec cette fonction, le programme achera : Occurrences en position: 10, 13, [18], o` on a indiqu les fausses occurrences par des crochets. On verra plus loin comment u e diminuer ce nombre. Pour acclrer le calcul de la signature, on remarque que lon peut faire cela de ee mani`re incrmentale. Plus prcisment : e e e e S(X[1..m]) = S(X[0..m 1]) X[0] + X[m], ce qui remplace m additions par 1 addition et 1 soustraction ` chaque tape (on a a e confondu X[i] et sa valeur en tant que caract`re). e Une fonction de signature qui prsente moins de collisions sobtient ` partir de ce e a quon appelle une fonction de hachage, dont la thorie sera prsente ` la section 11.3. e e e a On prend p un nombre premier et B un entier. La signature est alors : S(X[0..m 1]) = (X[0]B m1 + + X[m 1]B 0 ) mod p.

192

CHAPITRE 15. RECHERCHE EXHAUSTIVE

On montre que la probabilit de collisions est alors 1/p. Typiquement, B = 216 , p = e 231 1 = 2147483647. Lintrt de cette fonction est quelle permet un calcul incrmental, puisque : ee e S(X[i + 1..i + m]) = BS(X[i..i + m 1]) X[i]B m + X[i + m], qui svalue dautant plus rapidement que lon a prcalcul B m mod p. e e e Le nombre de calculs eectus est O(n + m), ce qui reprsente une amlioration notable e e e par rapport ` la recherche na a ve. Les fonctions correspondantes sont : public static long B = ((long)1) << 16, p = 2147483647; // calcul de S(X[i..i+m-1]) public static long signature2(char[] X, int i, int m){ long s = 0; for(int j = i; j < i+m; j++) s = (s * B + (int)X[j]) % p; return s; } // S(X[i+1..i+m]) = B S(X[i..i+m-1])-X[i] Bm + X[i+m] public static long signatureIncr(char[] X, int m, int i, long s, long Bm){ long ss; ss = ((int)X[i+m]) - ((((int)X[i]) * Bm)) % p; if(ss < 0) ss += p; ss = (ss + B * s) % p; return ss; } public static void KR2(char[] T, char[] M){ int n, m; long Bm, hT, hM; n = T.length; m = M.length; System.out.print("Occurrences en position :"); hM = signature2(M, 0, m); // calcul de Bm = Bm mod p Bm = B; for(int i = 2; i <= m; i++) Bm = (Bm * B) % p; hT = signature2(T, 0, m);

15.1. RECHERCHER DANS DU TEXTE for(int i = 0; i < n-m; i++){ if(i > 0) hT = signatureIncr(T, m, i-1, hT, Bm); if(hT == hM){ if(occurrence(T, M, i)) System.out.print(" "+i+","); else System.out.print(" ["+i+"],"); } } System.out.println(""); } Cette fois, le programme ne produit plus de collisions : Occurrences en position : 10, 13, Remarques complmentaires e

193

Des algorithmes plus rapides existent, comme par exemple ceux de Knuth-MorrisPratt ou Boyer-Moore. Il est possible galement de chercher des cha e nes proches du motif donn, par exemple en cherchant ` minimiser le nombre de lettres direntes e a e entre les deux cha nes. La recherche de cha nes est tellement importante quUnix poss`de une commande e ` titre dexemple : grep qui permet de rechercher un motif dans un chier. A unix% grep int Essai.java ache les lignes du chier Essai.java qui contiennent le motif int. Pour acher les lignes ne contenant pas int, on utilise : unix% grep -v int Essai.java On peut faire des recherches plus compliques, comme par exemple rechercher les e lignes contenant un 0 ou un 1 : unix% grep [01] Essai.java Le dernier exemple est : unix% grep "int .*[0-9]" Essai.java qui est un cas dexpression rguli`re. Elles peuvent tre dcrites en termes dautomates, e e e e qui sont tudis en cours INF421 et INF431. Pour plus dinformations sur la commande e e grep, tapez man grep.

194

CHAPITRE 15. RECHERCHE EXHAUSTIVE

15.2

Le probl`me du sac-`-dos e a

Considrons le probl`me suivant, appel probl`me du sac-`-dos : on cherche ` reme e e e a a plir un sac-`-dos avec un certain nombre dobjets de faon ` le remplir exactement. a c a Comment fait-on ? On peut modliser ce probl`me de la faon suivante : on se donne n entiers strictee e c ment positifs ai et un entier S. Existe-t-il des nombres xi {0, 1} tels que S = x0 a0 + x1 a1 + + xn1 an1 ? Si xi vaut 1, cest que lon doit prendre lobjet ai , et on ne le prend pas si xi = 0. Un algorithme de recherche des solutions doit tre capable dnumrer rapidee e e ment tous les n uplets de valeurs des xi . Nous allons donner quelques algorithmes qui pourront tre facilement modis pour chercher des solutions ` dautres probl`mes e e a e numriques : quations du type f (x0 , x1 , . . . , xn1 ) = 0 avec f quelconque, ou encore e e max f (x0 , x1 , . . . , xn1 ) sur un nombre ni de xi .

15.2.1

Premi`res solutions e

Si n est petit et x, on peut sen tirer en utilisant des boucles for imbriques qui e e permettent dnumrer les valeurs de x0 , x1 , x2 . Voici ce quon peut crire : e e e // Solution brutale public static void sacADos3(int[] a, int S){ int N; for(int x0 = 0; x0 < 2; x0++) for(int x1 = 0; x1 < 2; x1++) for(int x2 = 0; x2 < 2; x2++){ N = x0 * a[0] + x1 * a[1] + x2 * a[2]; if(N == S) System.out.println(""+x0+x1+x2); } } Cette version est gourmande en calculs, puisque N est calcul dans la derni`re e e e e o boucle, alors que la quantit x0 a0 + x1 a1 ne dpend pas de x2 . On crit plutt : e public static void sacADos3b(int[] a, int S){ int N0, N1, N2; for(int x0 = 0; x0 < 2; x0++){ N0 = x0 * a[0]; for(int x1 = 0; x1 < 2; x1++){ N1 = N0 + x1 * a[1]; for(int x2 = 0; x2 < 2; x2++){ N2 = N1 + x2 * a[2]; if(N2 == S)

` ` 15.2. LE PROBLEME DU SAC-A-DOS System.out.println(""+x0+x1+x2); } } } }

195

On peut encore aller plus loin, en ne faisant aucune multiplication, et remarquant que deux valeurs de Ni di`rent de ai . Cela donne : e public static void sacADos3c(int[] a, int S){ for(int x0 = 0, N0 = 0; x0 < 2; x0++, N0 += a[0]) for(int x1 = 0, N1 = N0; x1 < 2; x1++, N1 += a[1]) for(int x2 = 0, N2 = N1; x2 < 2; x2++, N2 += a[2]) if(N2 == S) System.out.println(""+x0+x1+x2); } Arriv ici, on ne peut gu`re faire mieux. Le probl`me majeur qui reste est que le e e e programme nest en aucun cas volutif. Il ne traite que le cas de n = 3. On peut bien e sr le modier pour traiter des cas particuliers xes, mais on doit conna n ` lavance, u tre a au moment de la compilation du programme.

15.2.2

Deuxi`me approche e

Les xi doivent prendre toutes les valeurs de lensemble {0, 1}, soit 2n . Toute solution peut scrire comme une suite de bits x0 x1 . . . xn1 et donc sinterprter comme un entier e e unique de lintervalle In = [0, 2n [, ` savoir a x0 20 + x1 21 + + xn1 2n1 . Parcourir lensemble des xi possibles ou bien cet intervalle est donc la mme chose. e On connait un moyen simple de passer en revue tous les lments de In , cest ee laddition. Il nous sut ainsi de programmer laddition binaire sur un entier reprsent e e comme un tableau de bits pour faire lnumration. On additionne 1 ` un registre, en e e a propageant ` la main la retenue. Pour simplier la lecture des fonctions qui suivent, on a a introduit une fonction qui ache les solutions : // affichage de i sous forme de sommes de bits public static afficher(int i, int[] x){ System.out.print("i="+i+"="); for(int j = 0; j < n; j++) System.out.print(""+x[j]); System.out.println(""); } public static void parcourta(int n){

196

CHAPITRE 15. RECHERCHE EXHAUSTIVE int retenue; int[] x = new int[n]; for(int i = 0; i < (1 << n); i++){ afficher(i, x); // simulation de laddition retenue = 1; for(int j = 0; j < n; j++){ x[j] += retenue; if(x[j] == 2){ x[j] = 0; retenue = 1; } else break; // on a fini } } }

(Linstruction 1<<n calcule 2n .) On peut faire un tout petit peu plus concis en grant e virtuellement la retenue : si on doit ajouter 1 ` xj = 0, la nouvelle valeur de xj est 1, il a ny a pas de retenue ` propager, on sarrte et on sort de la boucle ; si on doit ajouter a e 1 ` xj = 1, sa valeur doit passer ` 0 et engendrer une nouvelle retenue de 1 quelle doit a a passer ` sa voisine. On crit ainsi : a e public static void parcourtb(int n){ int[] x = new int[n]; for(int i = 0; i < (1 << n); i++){ afficher(i, x); // simulation de laddition for(int j = 0; j < n; j++){ if(x[j] == 1) x[j] = 0; else{ x[j] = 1; break; // on a fini } } } } La boucle centrale tant crite, on peut revenir ` notre probl`me initial, et au proe e a e gramme de la gure 15.1. Combien dadditions fait-on dans cette fonction ? Pour chaque valeur de i, on fait

` ` 15.2. LE PROBLEME DU SAC-A-DOS

197

// a[0..n[ : existe-t-il x[] tel que // somme(a[i]*x[i], i=0..n-1) = S ? public static void sacADosn(int[] a, int S){ int n = a.length, N; int[] x = new int[n]; for(int i = 0; i < (1 << n); i++){ // reconstruction de N = somme x[i]*a[i] N = 0; for(int j = 0; j < n; j++) if(x[j] == 1) N += a[j]; if(N == S){ System.out.print("S="+S+"="); for(int j = 0; j < n; j++) if(x[j] == 1) System.out.print("+"+a[j]); System.out.println(""); } // simulation de laddition for(int j = 0; j < n; j++){ if(x[j] == 1) x[j] = 0; else{ x[j] = 1; break; // on a fini } } } } Fig. 15.1 Version nale.

198

CHAPITRE 15. RECHERCHE EXHAUSTIVE

au plus n additions dentiers (en moyenne, on en fait dailleurs n/2). Le corps de la boucle est eectu 2n fois, le nombre dadditions est O(n2n ). e

15.2.3

Code de Gray*

Thorie et implantations e Le code de Gray permet dnumrer tous les entiers de In = [0, 2n 1] de telle sorte e e quon passe dun entier ` lautre en changeant la valeur dun seul bit. Si k est un entier a de cet intervalle, on lcrit k = k0 +k1 2+ +kn1 2n1 et on le note [kn1 , kn2 , . . . , k0 ]. e On va fabriquer une suite Gn = (gn,i )0 i<2n dont lensemble des valeurs est [0, 2n 1], mais dans un ordre tel quon passe de gn,i ` gn,i+1 en changeant un seul chire de a lcriture de gn,i en base 2. e Commenons par rappeler les valeurs de la fonction ou exclusif (appel XOR en c e anglais) et not . La table de vrit de cette fonction logique est e e e 0 1 0 0 1 1 1 0 En Java, la fonction sobtient par et op`re sur des mots : si m est de type int, m e reprsente un entier sign de 32 bits et m n eectue lopration sur tous les bits de e e e m et n ` la fois. Autrement dit, si les critures de m et n en binaire sont : a e m = m31 231 + + m0 = [m31 , m30 , . . . , m0 ], n = n31 231 + + n0 = [n31 , n30 , . . . , n0 ] avec mi , ni dans {0, 1}, on a
31

mn=
i=0

(mi ni )2i .

On dnit maintenant gn : e 1] [0, 2n 1] par gn (0) = 0 et si i > 0, gn (i) = gn (i 1) 2b(i) o` b(i) est le plus grand entier j tel que 2j | i. Cet entier existe u et b(i) < n pour tout i < 2n . Donnons les valeurs des premi`res fonctions : e g1 (0) = [0], g1 (1) = [1], g2 (0) = [00], g2 (1) = [01], g2 (2) = g2 ([10]) = g2 (1) 21 = [01] [10] = [11], g2 (3) = g2 ([11]) = g2 (2) 20 = [11] [01] = [10]. Ecrivons les valeurs de g3 sous forme dun tableau qui souligne la symtrie de celles-ci : e i 0 1 2 3 g(i) 000 = 0 001 = 1 011 = 3 010 = 2 i 7 6 5 4 g(i) 100 = 4 101 = 5 111 = 7 110 = 6

[0, 2n

Cela nous conduit naturellement ` prouver que la fonction gn poss`de un comportement a e miroir :

` ` 15.2. LE PROBLEME DU SAC-A-DOS Proposition 6 Si 2n1 i < 2n , alors gn (i) = 2n1 + gn (2n 1 i). 2n 12n1 , soit 0

199

Dmonstration : Notons que 2n 12n < 2n 1i e 2n1 1 < 2n1 . On a

2n 1i

gn (2n1 ) = gn (2n1 1) 2n1 = 2n1 + gn (2n1 1) = 2n1 + gn (2n 1 2n1 ). Supposons la proprit vraie pour i = 2n1 + r > 2n1 . On crit : ee e gn (i + 1) = gn (i) 2b(r+1) = (2n1 + gn (2n 1 i)) 2b(r+1) = 2n1 + (gn (2n 1 i) 2b(r+1) ). On conclut en remarquant que : gn (2n 1 i) = gn (2n 1 i 1) 2b(2
n 1i)

et b(2n i 1) = b(i + 1) = b(r + 1). 2 On en dduit par exemple que gn (2n 1) = 2n1 + gn (0) = 2n1 . e Proposition 7 Si n mme. e 1, la fonction gn dnit une bijection de [0, 2n 1] dans luie

Dmonstration : nous allons raisonner par rcurrence sur n. Nous venons de voir que e e g1 et g2 satisfont la proprit. Supposons-la donc vraie au rang n 2 et regardons ce ee ncide quil se passe au rang n + 1. Commenons par remarquer que si i < 2n , gn+1 co c avec gn car b(i) < n. Si i = 2n , on a gn+1 (i) = gn (2n 1) 2n , ce qui a pour eet de mettre un 1 en bit n + 1. Si 2n < i < 2n+1 , on a toujours b(i) < n et donc gn+1 (i) conserve le n + 1-i`me e bit ` 1. En utilisant la proprit de miroir du lemme prcdent, on voit que gn+1 est a ee e e galement une bijection de [2n , 2n+1 1] dans lui-mme. 2 e e Quel est lintrt de la fonction gn pour notre probl`me ? Des proprits prcdentes, ee e ee e e on dduit que gn permet de parcourir lintervalle [0, 2n 1] en passant dune valeur dun e entier ` lautre en changeant seulement un bit dans son criture en base 2. On trouvera a e a ` la gure 15.2 une premi`re fonction Java qui ralise le parcours. e e On peut faire un peu mieux, en remplaant les oprations de modulo par des c e oprations logiques, voir la gure 15.3. e Revenons au sac-`-dos. On commence par calculer la valeur de a xi ai pour le nuplet [0, 0, . . . , 0]. Puis on parcourt lensemble des xi ` laide du code de Gray. Si ` a a ltape i, on a calcul e e Ni = xn1 an1 + + x0 a0 , avec g(i) = [xn1 , . . . , x0 ], on passe ` ltape i + 1 en changeant un bit, mettons le a e j-`me, ce qui fait que : e Ni+1 = Ni + aj si gi+1 = gi + 2j , et Ni+1 = Ni aj si gi+1 = gi On direncie les deux valeurs en testant la prsence du j-`me bit e e e apr`s lopration sur gi : e e 2j .

200

CHAPITRE 15. RECHERCHE EXHAUSTIVE

public static void gray(int n){ int gi = 0; affichergi(0, n); for(int i = 1; i < (1 << n); i++){ // on ecrit i = 2j*k, 0 <= j < n, k impair int k = i, j; for(j = 0; j < n; j++){ if((k % 2) == 1) // k est impair, on sarrte e break; k /= 2; } gi = (1 << j); affichergi(gi, n); } } public static void afficherAux(int gi, int j, int n){ if(j >= 0){ afficherAux(gi >> 1, j-1, n); System.out.print((gi & 1)); } } public static void affichergi(int gi, int n){ afficherAux(gi, n-1, n); System.out.println("="+gi); } Fig. 15.2 Achage du code de Gray.

` ` 15.2. LE PROBLEME DU SAC-A-DOS public static void gray2(int n){ int gi = 0; affichergi(0, n); for(int i = 1; i < (1 << n); i++){ // on ecrit i = 2j*k, 0 <= j < n, k impair int k = i, j; for(j = 0; j < n; j++){ if((k & 1) == 1) // k est impair, on sarrte e break; k >>= 1; } gi = (1 << j); affichergi(gi, n); } } Fig. 15.3 Achage du code de Gray (2` version). e

201

Remarques Le code de Gray permet de visiter chacun des sommets dun hypercube. Lhypercube en dimension n est form prcisment des sommets (x0 , x1 , . . . , xn1 ) parcourant tous e e e les n-uplets dlments forms de {0, 1}. Le code de Gray permet de visiter tous les ee e sommets du cube une fois et une seule, en commenant par le point (0, 0, . . . , 0), et en c sarrtant juste au-dessus en (1, 0, . . . , 0). Remarquons que ce parcours ne visite pas e ncessairement toutes les artes de lhypercube. Cest pour cela que nous avons dessin e e e des `ches paisses pour bien indiquer quelles artes sont parcourues. e e e

110 10 11 010 011

111

100

101

00

01

000

001

202

CHAPITRE 15. RECHERCHE EXHAUSTIVE

public static void sacADosGray(int[] a, int S){ int n = a.length, gi = 0, N = 0, deuxj; if(N == S) afficherSolution(a, S, 0); for(int i = 1; i < (1 << n); i++){ // on ecrit i = 2j*k, 0 <= j < n, k impair int k = i, j; for(j = 0; j < n; j++){ if((k & 1) == 1) // k est impair, on sarrte e break; k >>= 1; } deuxj = 1 << j; gi = deuxj; if((gi & deuxj) != 0) N += a[j]; else N -= a[j]; if(N == S) afficherSolution(a, S, gi); } } public static void afficherSolution(int[] a, int S, int gi){ System.out.print("S="+S+"="); for(int i = 0; i < a.length; i++){ if((gi & 1) == 1) System.out.print("+"+a[i]); gi >>= 1; } System.out.println(); } Fig. 15.4 Code de Gray pour le sac-`-dos. a

` ` 15.2. LE PROBLEME DU SAC-A-DOS

203

15.2.4

Retour arri`re (backtrack) e

Lide est de rsoudre le probl`me de proche en proche. Supposons avoir dj` calcul e e e ea e Si = x0 a0 + x1 a1 + . . . + xi1 ai1 . Si Si = S, on a trouv une solution et on ne continue e pas ` rajouter des aj > 0. Sinon, on essaie de rajouter xi = 0 et on teste au cran suivant, a puis on essaie avec xi = 1. On fait ainsi des calculs et si cela choue, on retourne en e arri`re pour tester une autre solution, do` le nom backtrack. e u Limplantation de cette ide est donne ci-dessous : e e // on a dj` calcul Si = sum(a[j]*x[j], j=0..i-1) e a e public static void sacADosrec(int[] a, int S, int[] x, int Si, int i){ nbrec++; if(Si == S) afficherSolution(a, S, x, i); else if(i < a.length){ x[i] = 0; sacADosrec(a, S, x, Si, i+1); x[i] = 1; sacADosrec(a, S, x, Si+a[i], i+1); } } On appelle cette fonction avec : public static void sacADos(int[] a, int S){ int[] x = new int[a.length]; nbrec = 0; sacADosrec(a, S, x, 0, 0); System.out.print("# appels=" + nbrec); System.out.println(" // " + (1 << (a.length + 1))); } et le programme principal est : public static void main(String[] args){ int[] a = {1, 4, 7, 12, 18, 20, 30}; sacADos(a, sacADos(a, sacADos(a, sacADos(a, } On a ajout une variable nbrec qui mmorise le nombre dappels eectus ` la fonction e e e a sacADosrec et quon ache en n de calcul. Lexcution donne : e 11); 12); 55); 14);

204 S=11=+4+7 # appels=225 // 256 S=12=+12 S=12=+1+4+7 # appels=211 // 256 S=55=+7+18+30 S=55=+1+4+20+30 S=55=+1+4+12+18+20 # appels=253 // 256 # appels=255 // 256

CHAPITRE 15. RECHERCHE EXHAUSTIVE

On voit que dans le cas le pire, on fait bien 2n+1 appels ` la fonction (mais seulement a 2n additions). On remarque que si les aj sont tous strictement positifs, et si Si > S ` ltape i, a e alors il nest pas ncessaire de poursuivre. En eet, on ne risque pas datteindre S en e ajoutant encore des valeurs strictement positives. Il sut donc de rajouter un test qui permet dliminer des appels rcursifs inutiles : e e // on a dj` calcul Si = sum(a[j]*x[j], j=0..i-1) e a e public static void sacADosrec(int[] a, int S, int[] x, int Si, int i){ nbrec++; if(Si == S) afficherSolution(a, S, x, i); else if((i < a.length) && (Si < S)){ x[i] = 0; sacADosrec(a, S, x, Si, i+1); x[i] = 1; sacADosrec(a, S, x, Si+a[i], i+1); } } On constate bien sur les exemples une diminution notable des appels, dans les cas o` S est petit par rapport ` i ai : u a S=11=+4+7 # appels=63 // 256 S=12=+12 S=12=+1+4+7 # appels=71 // 256 S=55=+7+18+30 S=55=+1+4+20+30 S=55=+1+4+12+18+20 # appels=245 // 256 # appels=91 // 256 Terminons cette section en remarquant que le probl`me du sac-`-dos est le prototype e a des probl`mes diciles au sens de la thorie de la complexit, et que cest l` lun des e e e a sujets traits en Anne 3. e e

15.3. PERMUTATIONS

205

15.3

Permutations

Une permutation des n lments 1, 2, . . ., n est un n-uplet (a1 , a2 , . . . , an ) tel que ee lensemble des valeurs des ai soit exactement {1, 2, . . . , n}. Par exemple, (1, 3, 2) est une permutation sur 3 lments, mais pas (2, 2, 3). Il y a n! = n (n 1) 2 1 ee permutations de n lments. ee

15.3.1

Fabrication des permutations

Nous allons fabriquer toutes les permutations sur n lments et les stocker dans un ee tableau. On proc`de rcursivement, en fabriquant les permutations dordre n 1 et en e e rajoutant n ` toutes les positions possibles : a

public static int[][] permutations(int n){ if(n == 1){ int[][] t = {{0, 1}}; return t; } else{ // tnm1 va contenir les (n-1)! ` e // permutations a n-1 elments int[][] tnm1 = permutations(n-1); int factnm1 = tnm1.length; int factn = factnm1 * n; // vaut n! int[][] t = new int[factn][n+1]; // recopie de tnm1 dans t for(int i = 0; i < factnm1; i++) for(int j = 1; j <= n; j++){ // on recopie tnm1[][1..j[ for(int k = 1; k < j; k++) t[n*i+(j-1)][k] = tnm1[i][k]; ` // on place n a la position j t[n*i+(j-1)][j] = n; // on recopie tnm1[][j..n-1] for(int k = j; k <= n-1; k++) t[n*i+(j-1)][k+1] = tnm1[i][k]; } return t; }

206

CHAPITRE 15. RECHERCHE EXHAUSTIVE

15.3.2

Enumration des permutations e

Le probl`me de lapproche prcdente est que lon doit stocker les n! permutations, e e e ce qui peut nir par tre un peu gros en mmoire. Dans certains cas, on peut vouloir e e se contenter dnumrer sans stocker. e e On va l` aussi procder par rcurrence : on suppose avoir construit une permutation a e e t[1..i0-1] et on va mettre dans t[i0] les ni0 +1 valeurs non utilises auparavant, e a ` tour de rle. Pour ce faire, on va grer un tableau auxiliaire de boolens utilise, o e e tel que utilise[j] est vrai si le nombre j na pas dj` t choisi. Le programme est eaee alors : // approche en O(n!) public static void permrec2(int[] t, int n, boolean[] utilise, int i0){ if(i0 > n) afficher(t, n); else{ for(int v = 1; v <= n; v++){ if(! utilise[v]){ utilise[v] = true; t[i0] = v; permrec2(t, n, utilise, i0+1); utilise[v] = false; } } } } public static void permrec2(int n){ int[] t = new int[n+1]; boolean[] utilise = new boolean[n+1]; permrec2(t, n, utilise, 1); } Pour n = 3, on fabrique les permutations dans lordre :

1 1 2 2 3 3

2 3 1 3 1 2

3 2 3 1 2 1

15.4. LES N REINES

207

15.4

Les n reines

Nous allons encore voir un algorithme de backtrack pour rsoudre un probl`me e e combinatoire. Dans la suite, nous supposons que nous utilisons un chiquier n n. e

15.4.1

Prlude : les n tours e

Rappelons quelques notions du jeu dchecs. Une tour menace toute pi`ce adverse e e se trouvant dans la mme ligne ou dans la mme colonne. e e

T '

r
c

On voit facilement quon peut mettre n tours sur lchiquier sans que les tours ne e sattaquent. En fait, une solution correspond ` une permutation de 1..n, et on sait dj` a ea faire. Le nombre de faons de placer n tours non attaquantes est donc T (n) = n!. c

15.4.2

Des reines sur un chiquier e

La reine se dplace dans toutes les directions et attaque toutes les pi`ces (adverses) e e se trouvant sur les mme ligne ou colonne ou diagonales quelle. e
s d d d d d d ' c  E d d d d d d d

Une reine tant une tour avec un peu plus de pouvoir, il est clair que le nombre e maximal de reines pouvant tre sur lchiquier sans sattaquer est au plus n. On peut e e montrer que ce nombre est n pour n = 1 ou n 41 . Reste ` calculer le nombre de a solutions possibles, et cest une tche dicile, et non rsolue. a e Donnons les solutions pour n = 4 :
1 Les petits cas peuvent se faire ` la main, une preuve gnrale est plus dlicate et elle est due ` a e e e a Ahrens, en 1921.

208

CHAPITRE 15. RECHERCHE EXHAUSTIVE

q q

q q q
(2413)

q q
(3142)

Expliquons comment rsoudre le probl`me de faon algorithmique. On commence e e c par chercher un codage dune conguration. Une conguration admissible sera code e par la suite des positions dune reine dans chaque colonne. On oriente lchiquier comme e suit : j
E

ic Avec ces notations, on dmontre : e Proposition 8 La reine en position (i1 , j1 ) attaque la reine en position (i2 , j2 ) si et seulement si i1 = i2 ou j1 = j2 ou i1 j1 = i2 j2 ou i1 + j1 = i2 + j2 . Dmonstration : si elles sont sur la mme diagonale nord-ouest/sud-est, i1 j1 = i2 j2 ; e e ou encore sur la mme diagonale sud-ouest/nord-est, i1 + j1 = i2 + j2 . 2 e On va procder comme pour les permutations : on suppose avoir construit une e solution approche dans t[1..i0[ et on cherche ` placer une reine dans la colonne e a i0. Il faut sassurer que la nouvelle reine nattaque personne sur sa ligne (cest le rle o du tableau utilise comme pour les permutations), et personne dans aucune de ses diagonales (fonction pasDeConflit). Le code est le suivant : // t[1..i0[ est dj` rempli e a public static void reinesAux(int[] t, int n, boolean[] utilise, int i0){ if(i0 > n) afficher(t); else{ for(int v = 1; v <= n; v++) if(! utilise[v] && pasDeConflit(t, i0, v)){ utilise[v] = true; t[i0] = v; reinesAux(t, n, utilise, i0+1); utilise[v] = false; } } }

15.5. LES ORDINATEURS JOUENT AUX ECHECS La programmation de la fonction pasDeConflit dcoule de la proposition 8 : e

209

// t[1..i0[ est dj` rempli e a public static boolean pasDeConflit(int[] t, int i0, int j){ int x1, y1, x2 = i0, y2 = j; for(int i = 1; i < i0; i++){ // on rcup`re les positions e e x1 = i; y1 = t[i]; if((x1 == x2) // mme colonne e || (y1 == y2) // mme ligne e || ((x1-y1) == (x2-y2)) || ((x1+y1) == (x2+y2))) return false; } return true; } Notons quil est facile de modier le code pour quil calcule le nombre de solutions. Terminons par un tableau des valeurs connues de R(n) : n R(n) 4 2 5 10 6 4 7 40 8 92 n R(n) 9 352 10 724 11 2680 12 14200 13 73712 n R(n) 14 365596 15 2279184 16 14772512 17 95815104 18 666090624 n R(n) 19 4968057848 20 39029188884 21 314666222712 22 2691008701644 23 24233937684440

Vardi a conjectur que log R(n)/(n log n) > 0 et peut-tre que = 1. Rivin & e e Zabih ont dailleurs mis au point un algorithme de meilleur complexit pour rsoudre e e le probl`me, avec un temps de calcul de O(n2 8n ). e

15.5

Les ordinateurs jouent aux checs e

Nous ne saurions terminer un chapitre sur la recherche exhaustive sans voquer un e cas tr`s mdiatique, celui des ordinateurs jouant aux checs. e e e

15.5.1

Principes des programmes de jeu

Deux approches ont t tentes pour battre les grands ma ee e tres. La premi`re, dans la e ligne de Botvinik, cherche ` programmer lordinateur pour lui faire utiliser la dmarche e a e humaine. La seconde, et la plus fructueuse, cest utiliser lordinateur dans ce quil sait faire le mieux, cest-`-dire examiner de nombreuses donnes en un temps court. a e Comment fonctionne un programme de jeu ? En r`gle gnral, ` partir dune position e e e a donne, on num`re les coups valides et on cre la liste des nouvelles positions. On tente e e e e alors de dterminer quelle est la meilleure nouvelle position possible. On fait cela sur e

210

CHAPITRE 15. RECHERCHE EXHAUSTIVE

plusieurs tours, en parcourant un arbre de possibilits, et on cherche ` garder le meilleur e a chemin obtenu. Dans le meilleur des cas, lordinateur peut examiner tous les coups et il gagne ` a coup sr. Dans le cas des checs, le nombre de possibilits en dbut et milieu de partie u e e e est beaucoup trop grand. Aussi essaie-t-on de programmer la recherche la plus profonde possible.

15.5.2

Retour aux checs e

Codage dune position La premi`re ide qui vient ` lesprit est dutiliser une matrice 8 8 pour reprsenter e e a e un chiquier. On limplante gnralement sous la forme dun entier de type long qui a e e e 64 bits, un bit par case. On g`re alors un ensemble de tels entiers, un par type de pi`ce e e par exemple. On trouve dans la th`se de J. C. Weill un codage astucieux : e les cases sont numrotes de 0 ` 63 ; e e a les pi`ces sont numrotes de 0 ` 11 : pion blanc = 0, cavalier blanc = 1, . . ., pion e e e a noir = 6, . . . , roi noir = 11. On stocke la position dans le vecteur de bits (c1 , c2 , . . . , c768 ) tel que c64i+j+1 = 1 ssi la pi`ce i est sur la case j. e Les positions sont stockes dans une table de hachage la plus grande possible qui e permet de reconna une position dj` vue. tre ea

Fonction dvaluation e Cest un des secrets de tout bon programme dchecs. Lide de base est dvaluer e e e la force dune position par une combinaison linaire mettant en uvre le poids dune e pi`ce (reine = 900, tour= 500, etc.). On complique alors gnralement la fonction en e e e fonction de stratgies (position forte du roi, etc.). e

Biblioth`ques de dbut et n e e Une faon dacclrer la recherche est dutiliser des biblioth`que douvertures pour c ee e les dbuts de partie, ainsi que des biblioth`ques de ns de partie. e e Dans ce dernier cas, on peut tenter, quand il ne reste que peu de pi`ces dnumrer e e e toutes les positions et de classier les perdantes, les gagnantes et les nulles. Lalgorithme est appel analyse rtrograde et a t dcrite par Ken Thompson (lun des crateurs e e ee e e dUnix). ` A titre dexemple, la gure ci-dessous dcrit une position ` partir de laquelle il faut e a 243 coups (contre la meilleure dfense) ` Blanc (qui joue) pour capturer une pi`ce e a e sans danger, avant de gagner (Stiller, 1998).

15.5. LES ORDINATEURS JOUENT AUX ECHECS

211

Z Z ZNZ Z Z Z KS ZnZ Z Z Z Z Z Z Z Z Z Z Z Z Z Z ZnZ Z Z ZkZ Z Z


Deep blue contre Kasparov (1997) Le projet a dmarr en 1989 par une quipe de chercheurs et techniciens : C. J. Tan, e e e Murray Campbell (fonction dvaluation), Feng-hsiung Hsu, A. Joseph Hoane, Jr., Jerry e Brody, Joel Benjamin. Une machine spciale a t fabrique : elle contenait 32 nuds e ee e avec des RS/6000 SP (chip P2SC) ; chaque nud contenait 8 processeurs spcialiss e e pour les checs, avec un syst`me AIX. Le programme tait crit en C pour le IBM SP e e e e Parallel System (MPI). La machine tait capable dengendrer 200, 000, 000 positions e par seconde (ou 60 109 en 3 minutes, le temps allou). Deep blue a gagn 2 parties ` e e a 1 contre Kasparov2 . Deep Fritz contre Kramnik (2002 ; 2006) Cest cette fois un ordinateur plus raisonnable qui aronte un humain : 8 processeurs ` 2.4 GHz et 256 Mo, qui peuvent calculer 3 millions de coups ` la seconde. Le a a programmeur F. Morsch a soign la partie algorithmique. Kramnik ne fait que match e nul (deux victoires chacun, quatre nulles), sans doute puis par la tension du match. e e Lors de la rencontre de 2006, Deep Fritz bat Kramnik 4 ` 2, ce qui semble mettre a un terme au dbat pour le moment, les ordinateurs battant les grands ma e tres. Conclusion Peut-on dduire de ce qui prc`de que les ordinateurs sont plus intelligents que les e e e humains ? Certes non, ils calculent plus rapidement sur certaines donnes, cest tout. e Pour la petite histoire, les joueurs dchec peuvent sadapter ` lordinateur qui joue face e a a ` lui et trouver des positions qui le mettent en dicult. Une mani`re de faire est de e e jouer systmatiquement de faon ` maintenir un grand nombre de possibilits ` chaque e c a e a tape. e Nous renvoyons aux articles correspondant de wikipedia pour plus dinformations.

www.research.ibm.com/deepblue

212

CHAPITRE 15. RECHERCHE EXHAUSTIVE

Chapitre 16

Polynmes et transforme de o e Fourier


Nous allons donner quelques ides sur la ralisation de biblioth`ques de fonctions e e e sappliquant ` un domaine commun, en lillustrant sur un exemple, celui des calculs sur a les polynmes ` coecients entiers. Une bonne rfrence pour les algorithmes dcrits o a ee e dans ce chapitre est [Knu81]. Comment crit-on une biblioth`que ? On commence dabord par choisir les objets e e de base, puis on leur adjoint quelques prdicats, des primitives courantes (fabricae tion, entres sorties, test dgalit, etc.). Puis dans un deuxi`me temps, on construit e e e e des fonctions un peu plus complexes, et on poursuit en assemblant des fonctions dj` ea construites.

16.1

La classe Polynome

Nous dcidons de travailler sur des polynmes ` coeents entiers, que nous supe o a poserons ici tre de type long1 . Un polynme P (X) = pd X d + + p0 a un degr e o e d, qui est un entier positif ou nul si P nest pas identiquement nul et 1 sinon (par convention).

16.1.1

Dnition de la classe e

Cela nous conduit ` dnir la classe, ainsi que le constructeur associ qui fabrique a e e un polynme dont tous les coecients sont nuls : o public class Polynome{ int deg; long[] coeff; public Polynome(int d){ this.deg = d; this.coeff = new long[d+1];
1

stricto sensu, nous travaillons en fait dans lanneau des polynmes ` coecients dnis modulo o a e

264 .

213

214 } }

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

Nous faisons ici la convention que les arguments dappel dune fonction correspondent ` des polynmes dont le degr est exact, et que la fonction retourne un poa o e lynme de degr exact. Autrement dit, si P est un param`tre dappel dune fonction, on o e e suppose que P.deg contient le degr de P , cest-`-dire que P est nul si P.deg ==-1 e a et P.coeff[P.deg] nest pas nul sinon.

16.1.2

Cration, achage e

Quand on construit de nouveaux objets, il convient dtre capable de les crer et e e manipuler aisment. Nous avons dj` crit un constructeur, mais nous pouvons avoir e eae besoin par exemple de copier un polynme : o public static Polynome copier(Polynome P){ Polynome Q = new Polynome(P.deg); for(int i = 0; i <= P.deg; i++) Q.coeff[i] = P.coeff[i]; return Q; } On crit maintenant une fonction toString() qui permet dacher un polynme e o a e ` lcran. On peut se contenter dune fonction toute simple : public String toString(){ String s = ""; for(int i = this.deg; i >= 0; i--){ s = s.concat("+"+this.coeff[i]+"*X"+i); } if(s == "") return "0"; else return s; } Si on veut tenir compte des simplications habituelles (pas dachage des coecients nuls de P sauf si P = 0, 1X 1 est gnralement crit X), il vaut mieux crire la e e e e fonction de la gure 16.1.

16.1.3

Prdicats e

Il est commode de dnir des prdicats sur les objets. On programme ainsi un test e e dgalit ` zro : e ea e public static boolean estNul(Polynome P){ return P.deg == -1; }

16.1. LA CLASSE POLYNOME

215

public String toString(){ String s = ""; long coeff; boolean premier = true; for(int i = this.deg; i >= 0; i--){ coeff = this.coeff[i]; if(coeff != 0){ // on naffiche que les coefficients non nuls if(coeff < 0){ s = s.concat("-"); coeff = -coeff; } else // on naffiche "+" que si ce nest pas // premier coefficient affich e if(!premier) s = s.concat("+"); e // traitement du cas spcial "1" if(coeff == 1){ if(i == 0) s = s.concat("1"); } else{ s = s.concat(coeff+""); if(i > 0) s = s.concat("*"); } // traitement du cas spcial "X" e if(i > 1) s = s.concat("X"+i); else if(i == 1) s = s.concat("X"); ` // a ce stade, un coefficient non nul e // a et affich e premier = false; } } // le polynme nul a le droit dtre affich o e e if(s == "") return "0"; else return s; } Fig. 16.1 Fonction dachage dun polynme. o

216

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER De mme, on ajoute un test dgalit : e e e public static boolean estEgal(Polynome P, Polynome Q){ if(P.deg != Q.deg) return false; for(int i = 0; i <= P.deg; i++) if(P.coeff[i] != Q.coeff[i]) return false; return true; }

16.1.4

Premiers tests

Il est important de tester le plus tt possible la biblioth`que en cours de cration, o e e a ` partir dexemples simples et ma trisables. On commence par exemple par crire un e programme qui cre le polynme P (X) = 2X + 1, lache ` lcran et teste sil est nul : e o a e public class TestPolynome{ public static void main(String[] args){ Polynome P; // cration de 2*X+1 e P = new Polynome(1); P.coeff[1] = 2; P.coeff[0] = 1; System.out.println("P="+P); System.out.println("P == 0 ? " + Polynome.estNul(P)); } } Lexcution de ce programme donne alors : e P=2*X+1 P == 0? false Nous allons avoir besoin dentrer souvent des polynmes et il serait souhaitable o davoir un moyen plus simple que de rentrer tous les coecients les uns apr`s les autres. e On peut dcider de crer un polynme ` partir dune cha de caract`res formatte e e o a ne e e avec soin. Un format commode pour dnir un polynme est une cha de caract`res e o ne e s de la forme "deg s[deg] s[deg-1] ... s[0]" qui correspondra au polynme o P (X) = sdeg X deg + +s0 . Par exemple, la cha "1 1 2" codera le polynme X +2. ne o La fonction convertissant une cha au bon format en polynme est alors : ne o public static Polynome deChaine(String s){ Polynome P; long[] tabi = TC.longDeChaine(s); P = new Polynome((int)tabi[0]);

` 16.2. PREMIERES FONCTIONS for(int i = 1; i < tabi.length; i++) P.coeff[i-1] = tabi[i]; return P; }

217

(la fonction TC.longDeChaine appartient ` la classe TC dcrite en annexe) et elle a e est utilise dans TestPolynome de la faon suivante : e c P = Polynome.deChaine("1 1 2"); // cest X+2 Une fois dnis les objets de base, il faut maintenant passer aux oprations plus e e complexes.

16.2
16.2.1

Premi`res fonctions e
Drivation e
d i i=0 pi X

La drive du polynme 0 est 0, sinon la drive de P (X) = e e o e e


d

est :

P (X) =
i=1

ipi X i1 .

On crit alors la fonction : e public static Polynome deriver(Polynome P){ Polynome dP; if(estNul(P)) return copier(P); dP = new Polynome(P.deg - 1); for(int i = P.deg; i >= 1; i--) dP.coeff[i-1] = i * P.coeff[i]; return dP; }

16.2.2

Evaluation ; schma de Horner e

Passons maintenant ` lvaluation du polynme P (X) = d pi X i en la valeur a e o i=0 x. La premi`re solution qui vient ` lesprit est dappliquer la formule en calculant de e a proche en proche les puissances de x. Cela scrit : e // evaluation de P en x public static long evaluer(Polynome P, long x){ long Px, xpi; if(estNul(P)) return 0; // Px contiendra la valeur de P(x)

218

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER Px = P.coeff[0]; xpi = 1; for(int i = 1; i <= P.deg; i++){ // calcul de xpi = xi xpi *= x; Px += P.coeff[i] * xpi; } return Px;

} Cette fonction fait 2d multiplications et d additions. On peut faire mieux en utilisant le schma de Horner : e P (x) = ( ((pd x + pd1 )x + pd2 )x + )x + p0 . La fonction est alors : public static long Horner(Polynome P, long x){ long Px; if(estNul(P)) return 0; Px = P.coeff[P.deg]; for(int i = P.deg-1; i >= 0; i--){ ` // a cet endroit, Px contient: // p_d*x(d-i-1) + ... + p_{i+1} Px *= x; // Px contient maintenant // p_d*x(d-i) + ... + p_{i+1}*x Px += P.coeff[i]; } return Px; } On ne fait plus dsormais que d multiplications et d additions. Notons au passage e que la stabilit numrique serait meilleure, si x tait un nombre ottant. e e e

16.3

Addition, soustraction
n i i=0 pi X ,

Si P (X) =

Q(X) =

m j j=0 qj X ,

alors
n m i

min(n,m)

P (X) + Q(X) =
k=0

(pk + qk )X +
i=min(n,m)+1

pi X +
j=min(n,m)+1

qj X j .

Le degr de P + Q sera infrieur ou gal ` max(n, m) (attention aux annulations). e e e a Le code pour laddition est alors :

16.3. ADDITION, SOUSTRACTION

219

public static Polynome plus(Polynome P, Polynome Q){ int maxdeg = (P.deg >= Q.deg ? P.deg : Q.deg); int mindeg = (P.deg <= Q.deg ? P.deg : Q.deg); Polynome R = new Polynome(maxdeg); for(int i = 0; i <= mindeg; i++) R.coeff[i] = P.coeff[i] + Q.coeff[i]; for(int i = mindeg+1; i <= P.deg; i++) R.coeff[i] = P.coeff[i]; for(int i = mindeg+1; i <= Q.deg; i++) R.coeff[i] = Q.coeff[i]; trouverDegre(R); return R; } Comme il faut faire attention au degr du rsultat, qui est peut-tre plus petit que e e e prvu, on a d introduire une nouvelle primitive qui se charge de mettre ` jour le degr e u a e de P (on remarquera que si P est nul, le degr sera bien mis ` 1) : e a // vrification du degr e e public static void trouverDegre(Polynome P){ while(P.deg >= 0){ if(P.coeff[P.deg] != 0) break; else P.deg -= 1; } } On proc`de de mme pour la soustraction, en recopiant la fonction prcdente, les e e e e seules modications portant sur les remplacements de + par aux endroits appropris. e Il importe ici de bien tester les fonctions crites. En particulier, il faut vrier que e e la soustraction de deux polynmes identiques donne 0. Le programme de test contient o ainsi une soustraction normale, suivie de deux soustractions avec diminution du degr : e public static void testerSous(){ Polynome P, Q, S; P = Polynome.deChaine("1 1 1"); // X+1 Q = Polynome.deChaine("2 2 2 2"); // X2+X+2 System.out.println("P="+P+" Q="+Q); System.out.println("P-Q="+Polynome.sous(P, Q)); System.out.println("Q-P="+Polynome.sous(Q, P)); Q = Polynome.deChaine("1 1 0"); // X System.out.println("Q="+Q); System.out.println("P-Q="+Polynome.sous(P, Q)); System.out.println("P-P="+Polynome.sous(P, P));

220 }

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

dont lexcution donne : e P=X+1 Q=2*X2+2*X+2 P-Q=-2*X2-X-1 Q-P=2*X2+X+1 Q=1 P-Q=X P-P=0

16.4
16.4.1

Deux algorithmes de multiplication


Multiplication na ve
n i i=0 pi X ,

Soit P (X) =

Q(X) =

m j j=0 qj X , n+m

alors pi q j X k .


i+j=k

P (X)Q(X) =
k=0

Le code correspondant en Java est : public static Polynome mult(Polynome P, Polynome Q){ Polynome R; if(estNul(P)) return copier(P); else if(estNul(Q)) return copier(Q); R = new Polynome(P.deg + Q.deg); for(int i = 0; i <= P.deg; i++) for(int j = 0; j <= Q.deg; j++) R.coeff[i+j] += P.coeff[i] * Q.coeff[j]; return R; }

16.4.2

Lalgorithme de Karatsuba

Nous allons utiliser une approche diviser pour rsoudre de la multiplication de e polynmes. o Comment fait-on pour multiplier deux polynmes de degr 1 ? On crit : o e e P (X) = p0 + p1 X, et on va calculer R(X) = P (X)Q(X) = r0 + r1 X + r2 X 2 , avec r 0 = p0 q 0 , r 1 = p0 q 1 + p1 q 0 , r 2 = p1 q 1 . Q(X) = q0 + q1 X,

16.4. DEUX ALGORITHMES DE MULTIPLICATION

221

Pour calculer le produit R(X), on fait 4 multiplications sur les coecients, que nous appelerons multiplication lmentaire et dont le cot sera lunit de calcul pour les ee u e comparaisons ` venir. Nous ngligerons les cots daddition et de soustraction. a e u Si maintenant P est de degr n 1 et Q de degr n 1 (ils ont donc n termes), on e e peut crire : e P (X) = P0 (X) + X m P1 (X), Q(X) = Q0 (X) + X m Q1 (X),

o` m = n/2 , avec P0 et Q0 de degr m 1 et P1 , Q1 de degr n 1 m. On a alors : u e e R(X) = P (X)Q(X) = R0 (X) + X m R1 (X) + X 2m R2 (X), R0 = P0 Q0 , R1 = P0 Q1 + P1 Q0 , R2 = P1 Q1 .

Notons M(d) le nombre de multiplications lmentaires ncessaires pour calculer le ee e produit de deux polynmes de degr d 1. On vient de voir que : o e M(21 ) = 4M(20 ). Si n = 2t , on a m = 2t1 et : M(2t ) = 4M(2t1 ) = O(22t ) = O(n2 ). Lide de Karatsuba est de remplacer 4 multiplications lmentaires par 3, en utie ee lisant une approche dite valuation/interpolation. On sait quun polynme de degr n e o e est compl`tement caractris soit par la donne de ses n + 1 coecients, soit par ses e e e e valeurs en n + 1 points distincts (en utilisant par exemple les formules dinterpolation de Lagrange). Lide de Karatsuba est dvaluer le produit P Q en trois points 0, 1 et e e . On crit : e R0 = P0 Q0 , R2 = P1 Q1 , R1 = (P0 + P1 )(Q0 + Q1 ) R0 R1 ce qui permet de ramener le calcul des Ri ` une multiplication de deux polynmes a o de degr m 1, et deux multiplications en degr n 1 m plus 2 additions et 2 e e soustractions. Dans le cas o` n = 2t , on obtient : u K(2t ) = 3K(2t1 ) = O(3t ) = O(nlog2 3 ) = O(n1.585 ). La fonction K vrie plus gnralement lquation fonctionnelle : e e e e K(n) = 2K n 2 +K n 2

et son comportement est dlicat ` prdire (on montre quelle a un comportement frace a e tal). Premi`re implantation e Nous allons implanter les oprations ncessaires aux calculs prcdents. On a besoin e e e e dune fonction qui rcup`re P0 et P1 ` partir de P . On crit donc une fonction : e e a e

222

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

// cre le polynme e o // P[dbut]+P[dbut+1]*X+...+P[fin]*X(fin-dbut) e e e public static Polynome extraire(Polynome P, int dbut, int fin){ e Polynome E = new Polynome(fin-dbut); e for(int i = dbut; i <= fin; i++) e E.coeff[i-dbut] = P.coeff[i]; e trouverDegre(E); return E; } Quel va tre le prototype de la fonction de calcul, ainsi que les hypoth`ses faites en e e entre ? Nous dcidons ici dutiliser : e e // ENTREE: deg(P) = deg(Q) <= n-1, // P.coeff et Q.coeff sont de taille >= n; // SORTIE: R tq R = P*Q et deg(R) <= 2*(n-1). public static Polynome Karatsuba(Polynome P, Polynome Q, int n){ Nous xons donc arbitrairement le degr de P et Q ` n 1. Une autre fonction est e a suppose tre en charge de la normalisation des oprations, par exemple en crant des e e e e objets de la bonne taille. On remarque galement, avec les notations prcdentes, que P0 et Q0 sont de degr e e e e m 1, qui est toujours plus grand que le degr de P1 et Q1 , ` savoir n m 1. Il faudra e a donc faire attention au calcul de la somme P0 + P1 (resp. Q0 + Q1 ) ainsi quau calcul de R1 . La fonction compl`te est donne dans la table 16.2. e e Expliquons la remarque 1. On dcide pour linstant darrter la rcursion quand on e e e doit multiplier deux polynmes de degr 0 (donc n = 1). o e La remarque 2 est justie par notre invariant de fonction : les degrs de SP et SQ e e (ou plus exactement la taille de leurs tableaux de coecients), qui vont tre passs ` e e a Karatsuba doivent tre m 1. Il nous faut donc modier lappel plus(P0, P1); e en celui plusKara(P0, P1, m-1); qui retourne la somme de P0 et P1 dans un polynme dont le nombre de coecients est toujours m, quel que soit le dgr de la o e e somme (penser que lon peut tout ` fait avoir P0 = 0 et P1 de degr m 2). a e // ENTREE: deg(P), deg(Q) <= d. // SORTIE: P+Q dans un polynme R tel que R.coeff a taille o // d+1. public static Polynome plusKara(Polynome P, Polynome Q, int d){ int mindeg = (P.deg <= Q.deg ? P.deg : Q.deg); Polynome R = new Polynome(d); //PrintK("plusKara("+d+", "+mindeg+"): "+P+" "+Q); for(int i = 0; i <= mindeg; i++)

16.4. DEUX ALGORITHMES DE MULTIPLICATION

223

public static Polynome Karatsuba(Polynome P, Polynome Q, int n){ Polynome P0, P1, Q0, Q1, SP, SQ, R0, R1, R2, R; int m; if(n <= 1) // (cf. remarque 1) return mult(P, Q); m = n/2; if((n % 2) == 1) m++; // on multiplie P = P0 + Xm * P1 avec Q = Q0 + Xm * Q1 // deg(P0), deg(Q0) <= m-1 // deg(P1), deg(Q1) <= n-1-m <= m-1 P0 = extraire(P, 0, m-1); P1 = extraire(P, m, n-1); Q0 = extraire(Q, 0, m-1); Q1 = extraire(Q, m, n-1); // R0 = P0*Q0 de degr 2*(m-1) e R0 = Karatsuba(P0, Q0, m); // R2 = P2*Q2 de degr 2*(n-1-m) e R2 = Karatsuba(P1, Q1, n-m); // R1 = (P0+P1)*(Q0+Q1)-R0-R2 // deg(P0+P1), deg(Q0+Q1) <= max(m-1, n-1-m) = m-1 SP = plusKara(P0, P1, m-1); // (cf. remarque 2) SQ = plusKara(Q0, Q1, m-1); R1 = Karatsuba(SP, SQ, m); R1 = sous(R1, R0); R1 = sous(R1, R2); // on reconstruit le rsultat e // R = R0 + Xm * R1 + X(2*m) * R2 R = new Polynome(2*(n-1)); for(int i = 0; i <= R0.deg; i++) R.coeff[i] = R0.coeff[i]; for(int i = 0; i <= R2.deg; i++) R.coeff[2*m + i] = R2.coeff[i]; for(int i = 0; i <= R1.deg; i++) R.coeff[m + i] += R1.coeff[i]; trouverDegre(R); return R; } Fig. 16.2 Algorithme de Karatsuba.

224

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER R.coeff[i] = P.coeff[i] + Q.coeff[i]; for(int i = mindeg+1; i <= P.deg; i++) R.coeff[i] = P.coeff[i]; for(int i = mindeg+1; i <= Q.deg; i++) R.coeff[i] = Q.coeff[i]; return R;

} Comment teste-t-on un tel programme ? Tout dabord, nous avons de la chance, car nous pouvons comparer Karatsuba ` mul. Un programme test prend en entre des a e couples de polynmes (P, Q) de degr n et va comparer les rsultats des deux fonctions. o e e Pour ne pas avoir ` rentrer des polynmes ` la main, on construit une fonction qui a o a fabrique des polynmes (unitaires) alatoires ` laide dun gnrateur cr pour la o e a e e ee classe : public static Random rd = new Random(); public static Polynome aleatoire(int deg){ Polynome P = new Polynome(deg); P.coeff[deg] = 1; for(int i = 0; i < deg; i++) P.coeff[i] = rd.nextLong(); return P; } La mthode rd.nextLong() retourne un entier alatoire de type long fabriqu e e e par le gnrateur rd. e e Le programme test, dans lequel nous avons galement rajout une mesure du temps e e de calcul est alors : // testons Karatsuba sur n polynmes de degr deg o e public static void testerKaratsuba(int deg, int n){ Polynome P, Q, N, K; long tN, tK, totN = 0, totK = 0; for(int i = 0; i < n; i++){ P = Polynome.aleatoire(deg); Q = Polynome.aleatoire(deg); TC.demarrerChrono(); N = Polynome.mult(P, Q); tN = TC.tempsChrono(); TC.demarrerChrono(); K = Polynome.Karatsuba(P, Q, deg+1); tK = TC.tempsChrono(); if(! Polynome.estEgal(K, N)){

16.4. DEUX ALGORITHMES DE MULTIPLICATION System.out.println("Erreur"); System.out.println("P*Q(norm)=" + N); System.out.println("P*Q(Kara)=" + K); for(int i = 0; i <= N.deg; i++){ if(K.coeff[i] != N.coeff[i]) System.out.print(" "+i); } System.out.println(""); System.exit(-1); } else{ totN += tN; totK += tK; } } System.out.println(deg+" N/K = "+totN+" "+totK); }

225

Que se passe-t-il en pratique ? Voici des temps obtenus avec le programme prcdent, e e pour 100 deg 1000 par pas de 100, avec 10 couples de polynmes ` chaque fois : o a Test de Karatsuba 100 N/K = 2 48 200 N/K = 6 244 300 N/K = 14 618 400 N/K = 24 969 500 N/K = 37 1028 600 N/K = 54 2061 700 N/K = 74 2261 800 N/K = 96 2762 900 N/K = 240 2986 1000 N/K = 152 3229 Cela semble frustrant, Karatsuba ne battant jamais (et de tr`s loin) lalgorithme na e f sur la plage considre. On constate cependant que la croissance des deux fonctions est ee a ` peu pr`s la bonne, en comparant par exemple le temps pris pour d et 2d (le temps e pour le calcul na est multipli par 4, le temps pour Karatsuba par 3). f e Comment faire mieux ? Lastuce classique ici est de dcider de repasser ` lalgoe a rithme de multiplication classique quand le degr est petit. Par exemple ici, on remplace e la ligne repre par la remarque 1 en : ee if(n <= 16) ce qui donne : Test de 100 N/K 200 N/K 300 N/K Karatsuba = 1 4 = 6 6 = 14 13

226

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

400 N/K = 24 17 500 N/K = 38 23 600 N/K = 164 40 700 N/K = 74 69 800 N/K = 207 48 900 N/K = 233 76 1000 N/K = 262 64 Le rglage de cette constante est critique et dpend de la machine sur laquelle on e e op`re. e Remarques sur une implantation optimale
La fonction que nous avons implante ci-dessus est gourmande en mmoire, car elle alloue e e sans cesse des polynmes auxiliaires. Diminuer ce nombre dallocations (il y en a O(n1.585 ) o galement...) est une tche majeure permettant de diminuer le temps de calcul. Une faon de e a c faire est de travailler sur des polynmes dnis par des extraits compris entre des indices de o e dbut et de n. Par exemple, le prototype de la fonction pourrait devenir : e public static Polynome Karatsuba(Polynome P, int dP, int fP, Polynome Q, int dQ, int fQ, int n){ qui permettrait de calculer le produit de P = Pf P X f P dP + + PdP et Q = Pf Q X f QdQ + + QdQ . Cela nous permettrait dappeler directement la fonction sur P0 et P1 (resp. Q0 et Q1 ) et viterait davoir ` extraire les coecients. e a Dans le mme ordre dide, laddition et la soustraction pourraient tre faites en place, e e e cest-`-dire quon implanterait plutt P := P Q. a o

16.5

Multiplication ` laide de la transforme de Fourier* a e

Quel est le temps minimal requis pour faire le produit de deux polynmes de degr o e n ? On vient de voir quil existe une mthode en O(n1.585 ). Peut-on faire mieux ? Lape proche de Karatsuba consiste ` couper les arguments en deux. On peut imaginer de a couper en 3, voire plus. On peut dmontrer quasymptotiquement, cela conduit ` une e a mthode dont le nombre de multiplications lmentaires est O(n1+ ) avec > 0 aussi e ee petit quon le souhaite. Il existe encore une autre mani`re de voir les choses. Lalgorithme de Karatsuba est e le prototype des mthodes de multiplication par valuation/interpolation. On a calcul e e e R(0), R(1) et R(+) et de ces valeurs, on a pu dduire la valeur de R(X). Lapproche e de Cooley et Tukey consiste ` interpoler le produit R sur des racines de lunit bien a e choisies.

16.5.1

Transforme de Fourier e

Dnition 1 Soit C et N un entier. La transforme de Fourier est une application e e F : CN CN (a0 , a1 , . . . , aN 1 ) (0 , a1 , . . . , aN 1 ) a

` 16.5. MULTIPLICATION A LAIDE DE LA TRANSFORMEE DE FOURIER* 227 o` u ai =


j=0

N 1

ij aj

pour 0

N 1.

Proposition 9 Si est une racine primitive N -i`me de lunit, (i.e., N = 1 et i = 1 e e pour 1 i < N ), alors F est une bijection et
1 F =

1 F 1 . N

Dmonstration : Posons e i = On calcule N i =


k

1 N

N 1

ik ak .
k=0

ik
j

kj aj =
j

aj
k

k(ji) =
j

aj Si,j .

Si i = j, on a Si,j = N et si j = i, on a
N 1

Si,j =
k=0

( ji )k =

1 ( ji )N = 0.2 1 ji

16.5.2
Soient

Application ` la multiplication de polynmes a o


n1 n1

P (X) =
i=0

pi X i ,

Q(X) =
i=0

qi X i

deux polynmes dont nous voulons calculer le produit : o


2n1

R(X) =
i=0

ri X i

(avec r2n1 = 0). On utilise une transforme de Fourier de taille N = 2n avec les e vecteurs : p = (p0 , p1 , . . . , pn1 , 0, 0, . . . , 0),
n termes

q = (q0 , q1 , . . . , qn1 , 0, 0, . . . , 0).


n termes

Soit une racine primitive 2n-i`me de lunit. La transforme de p e e e p F (p) = (0 , p1 , . . . , p2n1 ) nest autre que : (P ( 0 ), P ( 1 ), . . . , P ( 2n1 )).

228

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

De mme pour q, de sorte que le produit terme ` terme des deux vecteurs : e a F (p) F (q) = (0 q0 , p1 q1 , . . . , p2n1 q2n1 ) p donne en fait les valeurs de R(X) = P (X)Q(X) en les racines de lunit, cest-`-dire e a F (R) ! Par suite, on retrouve les coecients de P en appliquant la transforme inverse. e Un algorithme en pseudo-code pour calculer R est alors : N = 2n, = exp(2i/N ) ; calculer F (p), F (q) ; calculer (0 , r1 , . . . , r2n1 ) = F (p) F (q) ; r rcuprer les ri par e e (r0 , r1 , . . . , r2n1 ) = (1/N )F1 (0 , r1 , . . . , r2n1 ). r

16.5.3

Transforme rapide e

Transforme multiplicative e Si lon sy prend na vement, le calcul des xi dnis par e


N 1

xk =
m=0

xm mk , 0

N 1

prend Supposons que lon puisse crire N sous la forme dun produit de deux entiers plus e grands que 1, soit N = N1 N2 . On peut crire : e m = N 1 m2 + m1 , avec 0 m1 , k1 < N1 et 0 xk =
m1 =0 N1 1

N2

multiplications2 .

k = N2 k1 + k2
N2 1

m2 , k2 < N2 . Cela conduit ` rcrire : a e N2 m1 k1 m1 k2


m2 =0

xN1 m2 +m1 N1 m2 k2 .

On peut montrer sans grande dicult que 1 = N2 est racine primitive N1 -i`me e e de lunit, 2 = N1 est racine primitive N2 -i`me. On se ram`ne alors ` calculer : e e e a
N1 1 N2 1 m 1 1 k1 m1 k2 m1 =0 m2 =0 m xN1 m2 +m1 2 2 k2 .

xk =

La deuxi`me somme est une transforme de longueur N2 applique aux nombres e e e (xN1 m2 +m1 )0
m2 <N2 .

Le calcul se fait donc comme celui de N1 transformes de longueur N2 , suivi de multie plications par des facteurs m1 k2 , suivies elles-mmes de N2 transformes de longueur e e N1 . Le nombre de multiplications lmentaires est alors : ee
2 2 N1 (N2 ) + N1 N2 + N2 (N1 ) = N1 N2 (N1 + N2 + 1)

ce qui est en gnral plus petit que (N1 N2 )2 . e e


2 On remarque que mk = (mk) mod N et le prcalcul des i pour 0 e lmentaires. ee

i < N cote N multiplications u

` 16.5. MULTIPLICATION A LAIDE DE LA TRANSFORMEE DE FOURIER* 229 Le cas magique N = 2t Appliquons le rsultat prcdent au cas o` N1 = 2 et N2 = 2t1 . Les calculs que e e e u nous devons eectuer sont :
N/21 N/21

xk =
m=0 N/21

x2m ( )

2 mk

k m=0 N/21

x2m+1 ( 2 )mk x2m+1 ( 2 )mk


m=0

xk+N/2 =
m=0

x2m ( 2 )mk k

car N/2 = 1. Autrement dit, le calcul se divise en deux morceaux, le calcul des moitis e droite et gauche du signe + et les rsultats sont rutiliss dans la ligne suivante. e e e Pour insister sur la mthode, nous donnons ici le pseudo-code en Java sur des e vecteurs de nombres rels : e public static double[] FFT(double[] x, int N, double omega){ double[] X = new double[N], xx, Y0, Y1; double omega2, omegak; if(N == 2){ X[0] = x[0] + x[1]; X[1] = x[0] - x[1]; return X; } else{ xx = new double[N/2]; omega2 = omega*omega; for(m = 0; m < N/2; m++) xx[m] = x[2*m]; Y0 = FFT(xx, N/2, omega2); for(m = 0; m < N/2; m++) xx[m] = x[2*m+1]; Y1 = FFT(xx, N/2, omega2); omegak = 1.; // pour omegak for(k = 0; k < N/2; k++){ X[k] = Y0[k] + omegak*Y1[k]; X[k+N/2] = Y0[k] - omegak*Y1[k]; omegak = omega * omegak; } return X; } } Le cot de lalgorithme est alors F (N ) = 2F (N/2) + N/2 multiplications lmenu ee taires. On rsout la rcurrence ` laide de lastuce suivante : e e a F (N ) F (N/2) = + 1/2 = F (1) + t/2 = t/2 N N/2

230

CHAPITRE 16. POLYNOMES ET TRANSFORMEE DE FOURIER

1 c e do` F (N ) = N log2 N . Cette variante a ainsi reu le nom de transforme de Fourier u 2 rapide (Fast Fourier Transform ou FFT). ` A titre dexemple, si N = 210 , on fait 5 210 multiplications au lieu de 220 . Remarques complmentaires e Nous avons donn ici une br`ve prsentation de lide de la FFT. Cest une ide e e e e e tr`s importante ` utiliser dans tous les algorithmes bass sur les convolutions, comme e a e par exemple le traitement dimages, le traitement du signal, etc. Il y a des milliards dastuces dimplantation, qui sappliquent par exemple aux probl`mes de prcision. Cest une opration tellement critique dans certains cas que e e e du hardware spcique existe pour traiter des FFT de taille xe. On peut galement e e chercher ` trouver le meilleur dcoupage possible quand N nest pas une puissance de a e 2. Le lecteur intress est renvoy au livre de Nussbaumer [Nus82]. e e e Signalons pour nir que le mme type dalgorithme (Karatsuba, FFT) est utilis e e dans les calculs sur les grands entiers, comme cela est fait par exemple dans la biblioth`que multiprcision GMP (cf. http://www.swox.com/gmp/). e e

Cinqui`me partie e

Annexes

231

Annexe A

Complments e
A.1 Exceptions

Les exceptions sont des objets de la classe Exception. Il existe aussi une classe Error moins utilise pour les erreurs syst`me. Toutes les deux sont des sous-classes de e e la classe Throwable, dont tous les objets peuvent tre appliqus ` loprateur throw, e e a e comme suit :
throw e ;

Ainsi on peut crire en se servant de deux constructeurs de la classe Exception : e


throw new Exception(); throw new Exception ("Acc`s interdit dans un tableau"); e

Heureusement, dans les classes des biblioth`ques standard, beaucoup dexceptions sont e dj` pr-dnies, par exemple IndexOutOfBoundsException. On rcup`re une exea e e e e ception par linstruction try . . . catch. Par exemple
try { // un programme compliqu e } catch ( IOException e) { // essayer de rparer cette erreur dentre/sortie e e } catch ( Exception e) { // essayer de rparer cette erreur plus gnrale e e e }

Si on veut faire un traitement uniforme en n de linstruction try, que lon passe ou non par une exception, que le contrle sorte ou non de linstruction par une rupture de o squence comme un return, break, etc, on crit e e
try { // un programme compliqu e } catch ( IOException e) { // essayer de rparer cette erreur dentre/sortie e e

233

234

ANNEXE A. COMPLEMENTS

} catch ( Exception e) { //essayer de rparer cette erreur plus gnrale e e e } finally { // un peu de nettoyage }

Il y a deux types dexceptions. On doit dclarer les exceptions vries derri`re le e e e e mot-cl throws dans la signature des fonctions qui les l`vent. Ce nest pas la peine e e pour les exceptions non vries qui se reconnaissent en appartenant ` une sous-classe e e a de la classe RuntimeException. Ainsi
static int lire () throws IOException, ParseException { int n; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print ("Taille du carr magique, svp?:: e n = Integer.parseInt (in.readLine()); if ((n <= 0) || (n > N) || (n % 2 == 0)) erreur ("Taille impossible."); return n; } ");

A.2

La classe MacLib

Cette classe est luvre de Philippe Chassignet, et elle a survcu ` lvolution de e a e lenseignement dinformatique ` lX. Jusquen 1992, elle permettait de faire acher a sur un cran de Macintosh des dessins produits sur un Vax (via TGiX, autre interface e du mme auteur). Elle a ensuite t adapte en 1994 ` ThinkPascal sur Macintosh, e ee e a puis TurboPascal sous PC-Windows ; puis ` ThinkC et TurboC, X11 ; DelphiPasa cal, Borland C (nouveau Windows) ; CodeWarrior Pascal et C, tout a dans la c priode 19961998. En 1998 elle a commenc une nouvelle adaptation, avec Java, qui a e e constitu une simplication norme du travail, la mme version marchant sur toutes les e e e plateformes ! Elle est dsormais interface avec lAWT (Abstract Windowing Toolkit), e e avec un usage souple de la boucle dvnement. e e

A.2.1

Fonctions lmentaires ee

Les fonctions sont inspires de la libraire QuickDraw du Macintosh. La mthode e e initQuickDraw() dont lutilisation est imprative et doit prcder toute opration e e e e de dessin permet dinitialiser la fentre de dessin. Cette fentre Drawing cre par e e ee dfaut permet de grer un cran de 1024 768 points. Lorigine du syst`me de coe e e e ordonnes est en haut et ` gauche. Laxe des x va classiquement de la gauche vers la e a droite, laxe des y va plus curieusement du haut vers le bas (cest une vieille tradition de linformatique, dure ` remettre en cause). En QuickDraw, x et y sont souvent appels a e h (horizontal) et v (vertical). Il y a une notion de point courant et de crayon avec une

A.2. LA CLASSE MACLIB

235

taille et une couleur courantes. On peut dplacer le crayon, en le levant ou en dessinant e des vecteurs par les fonctions suivantes moveTo (x, y) Dplace le crayon aux coordonnes absolues x, y. e e move (dx, dy) Dplace le crayon en relatif de dx, dy. e lineTo (x, y) Trace une ligne depuis le point courant jusquau point de coordonnes x, y. e line (dx, dy) Trace le vecteur (dx, dy) depuis le point courant. penSize(dx, dy) Change la taille du crayon. La taille par dfaut est (1, 1). Toutes e les oprations de trac peuvent se faire avec une certaine paisseur du crayon. e e e penMode(mode) Change le mode dcriture : patCopy (mode par dfaut qui reme e place ce sur quoi on trace), patXor (mode Xor, i.e. en inversant ce sur quoi on trace).

A.2.2

Rectangles

Certaines oprations sont possibles sur les rectangles. Un rectangle r a un type prdni e e e Rect. Ce type est une classe qui a le format suivant
public class Rect { short left, top, right, bottom; }

Fort heureusement, il ny a pas besoin de conna le format internes des rectangles, tre et on peut faire simplement les oprations graphiques suivantes sur les rectangles e setRect(r, g, h, d, b) xe les coordonnes (gauche, haut, droite, bas) du rece tangle r. Cest quivalent ` faire les oprations r.left := g ;, r.top := h ;, e a e r.right := d ;, r.bottom := b. Le rectangle r doit dj` avoir t construit. ea ee unionRect(r1, r2, r) dnit le rectangle r comme lenveloppe englobante des e rectangles r1 et r2. Le rectangle r doit dj` avoir t construit. ea ee frameRect(r) dessine le cadre du rectangle r avec la largeur, la couleur et le mode du crayon courant. paintRect(r) remplit lintrieur du rectangle r avec la couleur courante. e invertRect(r) inverse la couleur du rectangle r. eraseRect(r) eace le rectangle r. drawChar(c), drawString(s) ache le caract`re c ou la cha s au point coue ne rant dans la fentre graphique. Ces fonctions di`rent de write ou writeln qui e e crivent dans la fentre texte. e e frameOval(r) dessine le cadre de lellipse inscrite dans le rectangle r avec la largeur, la couleur et le mode du crayon courant. paintOval(r) remplit lellipse inscrite dans le rectangle r avec la couleur courante. invertOval(r) inverse lellipse inscrite dans r. eraseOval(r) eace lellipse inscrite dans r.

236

ANNEXE A. COMPLEMENTS

frameArc(r,start,arc) dessine larc de lellipse inscrite dans le rectangle r dmarrant ` langle start et sur la longueur dnie par langle arc. e a e frameArc(r,start,arc) peint le camembert correspondant ` larc prcdent . . . . a e e Il y a aussi des fonctions pour les rectangles avec des coins arrondis. button() est une fonction qui renvoie la valeur vraie si le bouton de la souris est enfonc, faux sinon. e getMouse(p) renvoie dans p le point de coordonnes (p.h, p.v) courantes du cure seur.

A.2.3

La classe Maclib

public class Point { short h, v; Point(int h, int v) { h = (short)h; v = (short)v; } } public class MacLib { static static static static ... } void setPt(Point p, int h, int v) {..} void addPt(Point src, Point dst) {...} void subPt(Point src, Point dst) {...} boolean equalPt(Point p1, Point p2) {...}

Et les fonctions correspondantes (voir page 235)


static void setRect(Rect r, int left, int top, int right, int bottom) static void unionRect(Rect src1, Rect src2, Rect dst) static static static static static static static static static static static static static static static void void void void void void void void frameRect(Rect r) paintRect(Rect r) eraseRect(Rect r) invertRect(Rect r) frameOval(Rect r) paintOval(Rect r) eraseOval(Rect r) invertOval(Rect r)

void frameArc(Rect r, int startAngle, int arcAngle) void paintArc(Rect r, int startAngle, int arcAngle) void eraseArc(Rect r, int startAngle, int arcAngle) void invertArc(Rect r, int startAngle, int arcAngle) boolean button() void getMouse(Point p) void getClick(Point p)

A.2. LA CLASSE MACLIB Toutes ces dnitions sont aussi sur les stations de travail, dans le chier e
/usr/local/lib/MacLib-java/MacLib.java

237

On veillera ` avoir cette classe dans lensemble des classes chargeables (variable a denvironnement CLASSPATH).

A.2.4

Jeu de balle

Le programme suivant fait rebondir une balle dans un rectangle, premi`re tape e e vers un jeu de pong. class Pong{ static final int C = 5, // Le rayon de la balle X0 = 5, X1 = 250, Y0 = 5, Y1 = 180; public static void main(String args[]) { int x, y, dx, dy; Rect r = new Rect(); Rect s = new Rect(); Point p = new Point(); int i; // Initialisation du graphique MacLib.initQuickDraw(); MacLib.setRect(s, 50, 50, X1 + 100, Y1 + 100); MacLib.setDrawingRect(s); MacLib.showDrawing(); MacLib.setRect(s, X0, Y0, X1, Y1); // le rectangle de jeu MacLib.frameRect(s); // on attend un click et on note les coordonnes e // du pointeur MacLib.getClick(p); x = p.h; y = p.v; // la vitesse initiale de la balle dx = 1; dy = 1; while(true){ MacLib.setRect(r, x - C, y - C, x + C, y + C); // on dessine la balle en x,y MacLib.paintOval(r);

238

ANNEXE A. COMPLEMENTS x = x + dx; if(x - C <= X0 + 1 || x + C >= X1 - 1) dx = -dx; y = y + dy; if(y - C <= Y0 + 1 || y + C >= Y1 - 1) dy = -dy; // On temporise for(i = 1; i <= 2500; ++i) ; // On efface la balle MacLib.invertOval(r); }

} }

A.3

La classe TC

Le but de cette classe crite spcialement pour le cours par lauteur du prsent e e e poly (F. Morain) est de fournir quelques fonctions pratiques pour les TP, comme des entres-sorties faciles, ou encore un chronom`tre. Les exemples qui suivent sont presque e e tous donns dans la classe TestTC qui est dans le mme chier que TC.java (qui se e e trouve lui-mme accessible ` partir des pages web du cours). e a

A.3.1

Fonctionnalits, exemples e

Gestion du terminal Le deuxi`me exemple de programmation consiste ` demander un entier ` lutilisae a a teur et ache son carr. Avec la classe TC, on crit : e e public class TCex{ public static void main(String[] args){ int n; System.out.print("Entrer n="); n = TC.lireInt(); System.out.println("n2=" + (n*n)); } } La classe TC contient dautres primitives de ce type, comme TC.lireLong, ou encore lireLigne. Si lon veut lire plusieurs nombres1 , ou bien si on veut les lire sur lentre standard, e par exemple en excutant e
1

Ce passage peut tre saut en premi`re lecture. e e e

A.3. LA CLASSE TC unix% java prgm < fichier il convient dutiliser la syntaxe : do{ System.out.print("Entrer n="); n = TC.lireInt(); if(! TC.eof()) System.out.println("n2=" + (n*n)); } while(! TC.eof());

239

qui teste explicitement si lon est arriv ` la n du chier (ou si on a quitt le proe a e gramme). Lectures de chier Supposons que le chier fic.int contienne les entiers suivants : 1 2 3 4 5 6 6 7 et quon veuille rcuprer un tableau contenant tous ces entiers. On utilise : e e int[] tabi = TC.intDeFichier("fic.int"); for(int i = 0; i < tabi.length; i++) System.out.println(""+tabi[i]); On peut lire des double par : double[] tabd = TC.doubleDeFichier("fic.double"); for(int i = 0; i < tabd.length; i++) System.out.println(""+tabd[i]); La fonction static char[] charDeFichier(String nomfichier) permet de rcuprer le contenu dun chier dans un tableau de caract`res. De mme, e e e e la fonction static String StringDeFichier(String nomfichier) retourne une cha qui contient le chier nomfichier. ne On peut galement rcuprer un chier sous forme de tableau de lignes ` laide de : e e e a static String[] lignesDeFichier(String nomfichier)

240 La fonction

ANNEXE A. COMPLEMENTS

static String[] motsDeFichier(String nomfichier) retourne un tableau de cha nes contenant les mots contenus dans un chier, cest-`-dire a les cha nes de caract`res spares par des blancs, ou bien des caract`res de tabulation, e e e e etc. Sortie dans un chier Il sagit l` dune fonctionnalit spciale. En eet, on souhaite pouvoir faire ` la fois a e e a une sortie dcran normale, ainsi quune sortie simultane dans un chier. Lutilisation e e typique en est la pale machine. Pour bncier de cette fonctionnalit, on utilise : e e e TC.sortieFichier("nom_sortie"); TC.print(3); TC.println(" allo"); ce qui a pour eet dacher ` lcran a e 3 allo ainsi que den faire une copie dans le chier nom_sortie. Les fonctions TC.print et TC.println peuvent tre utilises en lieu et place de System.out.print et e e System.out.println. Si on oublie de faire linitialisation, pas de probl`me, les rsultats sachent ` lcran e e a e comme si de rien ntait. e Conversions ` partir des String a Souvent, on rcup`re une cha de caract`res (par exemple une ligne) et on veut e e ne e la couper en morceaux. La fonction static String[] motsDeChaine(String s) retourne un tableau contenant les mots de la cha ne. Si on est sr que s ne contient que des entiers spars par des blancs (cf. le chapitre u e e sur les polynmes), la fonction o static int[] intDeChaine(String s) retourne un tableau dentiers contenus dans s. Par exemple si s="1 2 3", on rcup`e e rera un tableau contenant les trois entiers 1, 2, 3 dans cet ordre. Si lon a aaire ` des a entiers de type long, on utilisera : static long[] longDeChaine(String s)

A.3. LA CLASSE TC Utilisation du chronom`tre e La syntaxe dutilisation du chronom`tre est la suivante : e long t; TC.demarrerChrono(); N = 1 << 10; t = TC.tempsChrono();

241

La variable t contiendra le temps coul pendant la suite doprations eectues depuis e e e e le lancement du chronom`tre. e

A.3.2

La classe Efichier

Cette classe rassemble des primitives de traitement des chiers en entre. Les exe plications qui suivent peuvent tre omises en premi`re lecture. e e Lide est dencapsuler le traitement des chiers dans une structure nouvelle, appele e e Efichier (pour chier dentres). Cette structure est dnie comme : e e public class Efichier{ BufferedReader buf; String ligne; StringTokenizer tok; boolean eof, eol; } On voit quelle contient un tampon dentre ` la Java, une ligne courante (ligne), e a un tokeniser associ (tok), et g`re elle-mme les ns de ligne (variable eol) et la n e e e du chier (variable eof). On a dni deux constructeurs : e
Efichier(String nomfic){ try{ buf = new BufferedReader(new FileReader(new File(nomfic))); } catch(IOException e){ System.out.println(e); } ligne = null; tok = null; eof = false; eol = false; } Efichier(InputStreamReader isr){ buf = new BufferedReader(isr); ligne = null; tok = null; eof = false; eol = false; }

242

ANNEXE A. COMPLEMENTS

le second permettant dutiliser lentre standard sous la forme : e


static Efichier STDIN = new Efichier(new InputStreamReader(System.in));

La lecture dune nouvelle ligne peut alors se faire par : String lireLigne(){ try{ ligne = buf.readLine(); } catch(EOFException e){ eol = true; eof = true; return null; } catch(IOException e){ erreur(e); } eol = true; if(ligne == null) eof = true; else tok = new StringTokenizer(ligne); return ligne; } o` nous grons tous les cas, et en particulier eof ` la main. On dnit galement u e a e e lireMotSuivant, etc. Les fonctionnalits de gestion du terminal sont encapsules ` leur tour dans la classe e e a TC, ce qui permet dcrire entre autres : e public class TC{ static boolean eof(){ return Efichier.STDIN.eof; } static int lireInt(){ return Efichier.STDIN.lireInt(); } ... }

Bibliographie
[AS85] [Bro95] [CLR90] [GG99] [Kle71] [Knu73] [Knu81] [Mai77] [Mye04] [Nus82] Harold Abelson and Gerald J. Sussman. Structure and Interpretation of Computer Programs. MIT Press, 1985. F. P. Brooks. The Mythical Man-Month : Essays on Software Engineering, Anniversary Edition (2nd Edition). Addison-Wesley Professional, 1995. T. H. Cormen, C. E. Leiserson, and R. L. Rivest. Introduction to Algorithms. MIT Press/McGraw-Hill, 1990. J. von zur Gathen and J. Gerhard. Modern Computer Algebra. Cambridge University Press, 1999. Stephen C. Kleene. Introduction to Metamathematics. North Holland, 1971. 6`me dition (1`re en 1952). e e e Donald E. Knuth. The Art of Computer Programming Sorting and Searching, volume 3. Addison Wesley, 1973. D. E. Knuth. The Art of Computer Programming : Seminumerical Algorithms. Addison-Wesley, 2nd edition, 1981. H. Mairson. Some new upper bounds on the generation of prime numbers. Comm. ACM, 20(9) :664669, September 1977. G. L. Myers. The Art of Software Testing. Wiley, 2nd edition, 2004. H. J. Nussbaumer. Fast Fourier transform and convolution algorithms, volume 2 of Springer Series in Information Sciences. Springer-Verlag, 2 edition, 1982. Hartley Rogers. Theory of recursive functions and eective computability. MIT press, 1987. Edition originale McGraw-Hill, 1967. Bob Sedgewick. Algorithms. Addison-Wesley, 1988. En franais : Algorithmes c en langage C, traduit par Jean-Michel Moreau, InterEditions, 1991.

[Rog87] [Sed88]

243

Index
abonn, 127, 137 e Ackerman, 74 adresse, 40 de n, 90 aectation, 16 Ahrens, 207 aiguillage, 23 algorithme dEuclide, 26 de Boyer-Moore, 193 de Cooley et Tukey, 226 de Karatsuba, 220 de Karp-Rabin, 190 de Knuth-Morris-Pratt, 193 de Newton, 28 de tri, 80 exponentiel, 80 linaire, 80 e polynomial, 80 sous linaire, 79 e alg`bre linaire, 47 e e annuaire, 127, 137 anne e bissextile, 160 sculaire, 160 e arbre binaire, 101 de recherche, 108 complet, 102 de possibilits, 210 e gnral, 103 e e gnalogique, 177 e e hauteur, 102 parcours, 102 arc, 117 arguments dentre, 65 e automate, 193 backtrack, 203, 207 bataille range, 50 e 244 biblioth`que, 213 e bio-informatique, 189 bloc, 14 Botvinik, 209 break, 23, 28 carte ` puce, 41 a cast, 17, 45 catch, 233 cellule, 89 champs, 58 Chassignet, P., 234 cha de caract`res, 63 ne e i-`me caract`re, 64 e e concatnation, 65 e longueur, 64 cha de caract`res, 235 ne e chronom`tre, 241 e classe, 14, 57 ABR, 108 Arbre, 102 Dico, 127 Expression, 104 Point, 57, 61 Polynome, 213 Produit, 62 public, 62 TC, 238 utilisation, 62 variable de, 61 commentaire, 14 compilation, 13 complexit, 79 e composantes connexes, 121 concatnation, 63 e connecteur logique, 22 constante, 62 constructeur explicite, 58, 59

INDEX implicite, 58, 59 continue, 28 conversion explicite, 17, 25 implicite, 17 correction orthographique, 127 cosinus (dveloppement), 75 e crible dEratosthene, 49 Daniel de Rauglaudre, 177 Deep blue, 211 Deep Fritz, 211 degr, 213 e dessins, 234 divide and conquer, 84 diviser pour rsoudre, 84 e division euclidienne, 16 do, 27 dclaration, 16 e dcrmentation, 18 e e dpiler, 68 e criture binaire, 69 e eet de bord, 33, 49 else, 22 en place, 130 entiers alatoires, 45 e quilibrer, 102 e exception, 22, 233 non vrie, 234 e e vrie, 234 e e exponentielle binaire, 85 expression boolenne, 21 e rguli`re, 193 e e factorielle, 67 feuille, 101 Fibonacci, 72 chier lecture, 239 criture, 240 e le dattente, 166 le de priorit, 111 e finally, 234 fonction, 31 dAckerman, 74 dvaluation, 210 e fonctions mutuellement rcursives, 74 e for, 24 Gdel, 76 o GMP, 230 graphe, 117 graphique, 234 Gray code de, 198 grep, 193 Grgoire XIII, 159 e hachage, 135 hypercube, 201

245

if, 22 incrmentation, 18 e indentation, 14 index, 129 IndexOutOfBoundsException, 233 indirection, 130 information hirarchique, 177 e instruction, 14, 21 conditionnelle, 21, 22 de rupture de contrle, 28 o interface, 164 invariant, 130 itration, 21, 24 e Kasparov, 211 Kramnik, 211 Lisp, 89 liste, 165 i-`me lment, 93 e ee achage, 91 ajouter en queue, 93 cha ee, 89 n copie, 94 cration, 90 e insertion, 97 inversion, 97 longueur, 92 partage, 96 suppression, 95 machine virtuelle, 13 MacLib, 234 Maple, 139 modularit, 60 e mthode, 14, 19, 31 e dobjet, 60 de classe, 60

246 main, 65 n reines, 207 n tours, 207 new, 91 Newton, 28, 31 nud, 101 nombres de Fibonacci, 72 premiers, 49 non mutable, 63 notation O, 80 , 80 null, 91 NullPointerException, 41 objet, 57, 66, 127 galit, 59 e e cration, 57 e passage par rfrence, 61 ee recopie, 58 oprateurs de comparaison, 21 e oprations, 18 e ordre lexicographique, 65 parseInt, 28 passage par valeur, 34 permutation, 205 numration, 206 e e alatoire, 50 e pile, 55, 164 dappels, 68 point courant, 234 polynme, 213 o drivation, 217 e multiplication, 220 primitives, 213 private, 63, 64 probl`me e de Syracuse, 27 probl`me dicile, 204 e Programme bonjour, 13 Cercle, 19 Essai, 35 mystere, 37 Newton, 29 PremierCalcul, 15 Syracuse, 27 prdicats, 213 e public, 63 QuickDraw, 234 quotient, 16 racine, 101 recherche dans un texte, 189 de racine, 84 dichotomique, 81, 128 en table, 127 exhaustive, 189 linaire, 127 e reprsentation creuse, 98 e retour arri`re, 203 e return, 28, 33 Rivin, 209 rcurrence, 67 e rcursivit, 67 e e terminale, 69 rfrence, 41, 58, 91 ee passage par, 45 rutilisation, 32 e r`gles dvaluation, 18, 22 e e S-expression, 89 sac-`-dos, 194 a schma de Horner, 217 e signature, 32, 190 sinus (dveloppement), 75 e sommet, 117 spcication, 150 e static, 60 Stiller, 210 String, 63 structure de donnes e dynamique, 89 statique, 89 surcharge, 34, 63 switch, 23 System, 14 syst`me de chiers, 89 e

INDEX

table de hachage, 136 tableau, 39, 96, 127 a ` plusieurs dimensions, 43 comme argument de fonction, 44 construction, 39 dclaration, 39 e

INDEX galit, 42 e e recherche du plus petit lment, 40, ee 81 reprsentation en mmoire, 40 e e taille, 40 tas, 110 terminaison, 28, 76 terminaison des programmes, 69 TGiX, 234 this, 59, 60 throw, 233 throws, 234 toString, 60, 214 tours de Hanoi, 71 transforme de Fourier, 226 e tri, 129 lmentaire, 130 ee fusion, 133 insertion, 131 par tas, 115 slection, 130 e try, 233 type, 15, 21, 57 de donnes abstrait, 164 e Unicode, 16 Vardi, 209 variable, 14 de classe, 34 visibilit, 34 e visibilit, 63 e void, 33 Weill, J. C., 210 while, 26 Zabih, 209 Zeller, 159

247

248

INDEX

Table des gures


1.1 4.1 4.2 4.3 6.1 6.2 6.3 9.1 9.2 9.3 9.4 9.5 10.1 10.2 10.3 10.4 10.5 10.6 Coercions implicites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Crible dEratosthene. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pile des appels. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pile des appels (suite). . . . . . . . . . . . . . . . . . . . . . . . . . . . . Empilement des appels rcursifs. . . . . . . . . . . . . . . . . . . . . . . e Dpilement des appels rcursifs. . . . . . . . . . . . . . . . . . . . . . . e e Les tours de Hanoi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemple darbre. . . . . . . . . . . . . . . . . . . . Arbre binaire pour lexpression x + y/(2z + 1) + t. Arbre n-naire pour lexpression x + y/(2z + 1) + t. Exemple darbre binaire de recherche. . . . . . . . Exemple de tas. . . . . . . . . . . . . . . . . . . . Le graphe G1 . . . . . . . Deux dessins dun mme e Le graphe G2 . . . . . . . Le graphe valu G3 . . . . e Le graphe G4 . . . . . . . Le graphe exemple. . . . . . . . graphe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 51 55 56 68 68 73 101 105 106 110 110 117 118 118 118 119 121

11.1 Recherche dichotomique. . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 12.1 La cha de production logicielle. . . . . . . . . . . . . . . . . . . . . . 144 ne 12.2 Fonctionnalit en fonction du temps. . . . . . . . . . . . . . . . . . . . . 146 e 12.3 Programmation par bouchons. . . . . . . . . . . . . . . . . . . . . . . . . 146 14.1 14.2 14.3 14.4 15.1 15.2 15.3 15.4 Un arbre gnalogique. . . . . . . . . . . . . . . . . e e Le graphe exemple. . . . . . . . . . . . . . . . . . . Le graphe exemple avec les amis ` distance 1. . . . a Un automate. . . . . . . . . . . . . . . . . . . . . . . Version nale. . . . . . . . . . . . . . . Achage du code de Gray. . . . . . . . Achage du code de Gray (2` version). e Code de Gray pour le sac-`-dos. . . . . a 249 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 182 183 184 197 200 201 202

250

TABLE DES FIGURES 16.1 Fonction dachage dun polynme. . . . . . . . . . . . . . . . . . . . . 215 o 16.2 Algorithme de Karatsuba. . . . . . . . . . . . . . . . . . . . . . . . . . . 223

You might also like