Algorithmique Et Programmation en Java
Algorithmique Et Programmation en Java
et programmation
Illustration de couverture :
'<t"
T"-1
0
N
@
.....,
© Dunod, 2000, 2004, 2010, 2014
..c:
Ol
·c 5 rue Laromiguière, 75005 Paris
>
a.
0 [Link]
u
ISBN 978-2-10-071452-0
-0
0
c:
=i
0
"<:t"
,..-1
0
N
@
....... .µ
..c: ..c
O'l Ol
·c ï:::
> >-
Cl. 0.
0 0
u u
VIII Algorithmique et programmation en Java
CHAPITRE 4 •EXPRESSIONS 35
4.1 Évaluation 36
Table des matières 4.1.1 Composition du même opérateur plusieurs fois 36
4.1.2 Composition de plusieurs opérateurs différents 36
4.1.3 Parenthésage des parties d'une expression 37
4.2 Type d'une expression 37
4.3 Conversions de type 38
4.4 Un exemple 38
4.5 Exercices 41
21.3.1 Liste non ordonnée 266 CHAPITRE 24 •ALGORITHMES SUR LES GRAPHES 345
21.3.2 Liste ordonnée 268 24.1 Composantes connexes 345
21.3.3 Recherche dichotomique 270 24.2 Fermeture transitive 347
21.4 Représentation par un arbre ordonné 272 24.3 Plus court chemin 349
21.4.1 Recherche d'un élément 273 24.3.1 Algorithme de Dijkstra 349
Les onze derniers chapitres étudient en détail ]es structures de données abstraites clas
siques (liste, graphe, arbre ... ) et de nombreux d'algorithmes fondamentaux (recherche, tri,
jeux et stratégie ... ) que tout étudiant en informatique doit connaître et maîtriser. D' autre part,
un chapitre est consacré aux interfaces graphiques et à leur programmation avec SWING.
La présentation des concepts de programmation cherche à être indépendante, autant que
faire se peut, d'un langage de programmation particulier. Les algorithmes seront décrits dans
une notation algorithmique épurée. Pour des raisons pédagogiques, il a toutefois bien fallu
faire le choix d'un langage pour programmer les structures de données et les algorithmes
présentés dans cet ouvrage. Ce choix s'est porté sur le langage à objets JAVA [GJS96], non
Avant-propos pas par effet de mode, mais plutôt pour les qualités de ce langage, malgré quelques défauts.
Ses qualités sont en particulier sa relative simplicité pour ]a mise en œuvre des algorithmes,
un large champ d'application et sa grande disponibilité sur des environnements variés. Ce
dernier point est en effet important; le lecteur doit pouvoir disposer facilement d'un compi
lateur et d'un interprète afin de résoudre les exercices proposés à la fin des chapitres. Enfin,
JAVA est de plus en plus utilisé comme langage d' apprentissage de la programmation dans les
universités. Pour les défauts, on peut par exemple regretter l'absence de l'héritage multiple,
et la présence de constructions archaïques héritées du langage C. Ce livre n'est toutefois pas
un ouvrage d'apprentissage du langage JAVA. Même si les éléments du langage nécessaires à
Longtemps attendue, la version 8 de JAVA est sortie en mars 2014, avec de nombreuses
]a mise en œuvre des notions d' algorithmique et de programmation ont été introduits, ce livre
nouveautés. Sans doute, la plus importante est l'ajout des fonctions anonymes (lambda ex
n'enseignera pas au lecteur les finesses et les arcanes de JAVA, pas plus qu'il ne décrira les
pressions) et son incidence tant sur le langage que sur son API.
nombreuses classes de l' APL Le lecteur intéressé pourra se reporter aux très nombreux ou
Pour cette quatrième édition d'Algorithmique et Programmation en Java, l'ensemble de vrages qui décrivent le langage en détail, comme par exemple [GRl 1 , Bro99, SkaOO, EckOO].
l'ouvrage a été révisé pour tenir compte des fonctions anonymes. En particulier, il inclut un Les corrigés de la plupart des exercices, ainsi que des applets qui proposent une vision
nouveau chapitre qui présente le paradigme fonctionnel et la façon dont il est mis en œuvre graphique de certains programmes présentés dans l'ouvrage sont accessibles sur le site web
avec JAVA 8. de l' auteur à l'adresse :
L'informatique est une science mais aussi une technologie et un ensemble d'outils. Ces www . p o l y t e c h . uni ce . fr/�vg / f r / l ivre s / a l go j ava
trois composantes ne doivent pas être confondues, et l' enseignement de l'informatique ne Cet ouvrage doit beaucoup à de nombreuses personnes. Tout d' abord, aux auteurs des
doit pas être réduit au seul apprentissage des logiciels. Ainsi, 1' activité de programmation algorithmes et des techniques de programmation qu'il présente. Il n'est pas possible de les
ne doit pas se confondre avec l'étude d'un langage de programmation particulier. Même citer tous ici, mais les références à leurs principaux textes sont dans la bibliographie. À Olivier
si l'importance de ce dernier ne doit pas être sous-estimée, il demeure un simple outil de Lecarme et Jean-Claude Boussard, mes professeurs à l'université de Nice qui m'ont enseigné
-0 -0
0
c:
mise en œuvre de concepts algorithmiques et de programmation généraux et fondamentaux. 0
c: cette discipline au début des années 1 980. Je tiens tout particulièrement à remercier ce dernier
=i =i
0 L'objectif de cet ouvrage est d'enseigner au lecteur des méthodes et des outils de construction 0
qui fut le tout premier lecteur attentif de cet ouvrage alors qu'il n'était encore qu'une ébauche,
"<:t"
,..-1 de programmes informatiques valides et fiables. "<:t"
,..-1
et qui m ' a encouragé à poursuivre sa rédaction. À Carine Fédèle qui a bien voulu lire et
0 0
N N
L'étude de l' algorithmique et de la programmation est un des piliers fondamentaux sur corriger ce texte à de nombreuses reprises, qu'elle en soit spécialement remercier. Enfin, à
@ @
.......
..c:
lesquels repose l'enseignement de l' informatique. Ce livre s' adresse principalement aux étu .......
..c:
mes collègues et mes étudiants qui m'ont aidé et soutenu dans cette tâche ardue qu'est la
O'l O'l
·c diants des cycles informatiques et élèves ingénieurs informaticiens, mais aussi à tous ceux ·c rédaction d'un livre.
> >
Cl.
0
qui ne se destinent pas à la carrière informatique mais qui seront certainement confrontés Cl.
0 Enfin, je remercie toute l'équipe Dunod, Carole Trochu, Jean-Luc Blanc, et Romain Hen
u u
au développement de programmes informatiques au cours de leur scolarité ou dans leur vie nion, pour leur aide précieuse et leurs conseils avisés qu'ils m'ont apportés pour la publication
professionnelle. des quatre éditions de cet ouvrage.
Les seize premiers chapitres présentent les concepts de base de la programmation im
pérative en s'appuyant sur une méthodologie objet. Le chapitre 1 3 est également dédié à
la programmation fonctionnelle. Ils mettent en particulier l' accent sur la notion de preuve
Sophia Antipolis, avril 2014.
des programmes grâce à la notion d'affirmations (antécédent, conséquent, invariant) dont la
vérification formelle garantit la validité de programmes. Ils introduisent aussi la notion de
complexité des algorithmes pour évaluer leur performance.
2 Chapitre 1 • Introduction
tée, les données. Cette mémoire est formée d'un ensemble de cellules, ou mots, ayant
chacune une adresse unique, et contenant des instructions ou des données. La repré
sentation de l'information est faite grâce à une codification binaire, 0 ou 1 . On appelle
longueur de mot, caractéristique d'un ordinateur, le nombre d'éléments binaires, appe
lés bits, groupés dans une simple cellule. Les longueurs de mots usuelles des ordina
teurs actuels (ou passés) sont, par exemple, 8, 16, 24, 32, 48 ou 64 bits. Cette mémoire
possède une capacité.finie, exprimée en gigaoctets (Go) ; un octet est un ensemble de
8 bits, un kilo-octet (Ko) est égal à 1 024 octets, un mégaoctet est égal à 1 024 Ko,
un gigaoctet (Go) est égal à 1 024 Mo, et enfin un téraoctet (To) est égal à 1 024 Go.
Chapitre 1 Actuellement, les tailles courantes des mémoires centrales des ordinateurs individuels
varient entre 4 Go à 8 Go 1•
- D'une unité centrale de traitement, formée d'une unité de commande (UC) et d'une
Introduction unité arithmétique et logique (UAL). L'unité de commande extrait de la mémoire cen
trale les instructions et les données sur lesquelles portent les instructions ; elle dé
clenche le traitement de ces données dans l'unité arithmétique et logique, et éventuel
lement range le résultat en mémoire centrale. L'unité arithmétique et logique effectue
sur les données qu'elle reçoit les traitements commandés par l'unité de commande.
- De registres. Les registres sont des unités de mémorisation, en petit nombre (certains
ordinateurs n'en ont pas), qui permettent à l'unité centrale de traitement de ranger de
façon temporaire des données pendant les calculs. L'accès à ces registres est très rapide,
beaucoup plus rapide que l' accès à une cellule de la mémoire centrale. Le rapport entre
Les informaticiens, ou les simples usagers de l'outil informatique, utilisent des systèmes
les temps d' accès à un registre et à la mémoire centrale est de l'ordre de 1 00.
informatiques pour concevoir ou exécuter des programmes d' application. Nous considére
- D'unités d'échanges reliées à des périphériques pour échanger de l' information avec
rons qu'un environnement informatique est formé d'une part d'un ordinateur et de ses équi
le monde extérieur. L'unité de commande dirige les unités d'échange lorsqu' elle ren
pements externes, que nous appellerons environnement matériel, et d'autre part d'un système
contre des instructions d'entrée-sortie.
d'exploitation avec ses programmes d'application, que nous appellerons environnement logi
ciel. Les programmes qui forment le logiciel réclament des méthodes pour les construire, des Jusqu'au milieu des années 2000, les constructeurs étaient engagés dans une course à la
langages pour les rédiger et des outils pour les exécuter sur un ordinateur. vitesse avec des microprocesseurs toujours plus rapides. Toutefois, les limites de la physique
Dans ce chapitre, nous introduirons la terminologie et les notions de base des ordinateurs actuelle ont été atteintes et depuis 2006 la tendance nouvelle est de placer plusieurs (le plus
et de la programmation. Nous présenterons les notions d'environnement de développement possible) microprocesseurs sur une même puce (le circuit-intégré). Ce sont par exemple les
et d'exécution d'un programme, nous expliquerons ce qu'est un langage de programmation processeurs 64 bits Core i7 d' INTEL ou AMD FX d' AMD, qui possèdent de 4 à 8 proces
-0
0 et nous introduirons les méthodes de construction des programmes.
-0
0
seurs.
c: c:
=i =i
0 0 Les ordinateurs actuels possèdent aussi plusieurs niveaux de mémoire. Ils introduisent,
"<:t"
,..-1
"<:t"
,..-1 entre le processeur et la mémoire centrale, des mémoires dites caches qui accélèrent l'accès
0 0
aux données. Les mémoires caches peuvent être primaires, c'est-à-dire situées directement
N
1 .1 E NVIRO N N E M E NT MATÉRI E L N
@
.......
@
.......
sur le processeur, ou secondaires, c'est-à-dire situées sur la carte mère. Certains ordinateurs
..c: ..c:
O'l Un automate est un ensemble fini de composants physiques pouvant prendre des états O'l introduisent même un troisième niveau de cache. En général, le rapport entre le temps d'accès
·c ·c
>
Cl. identifiables et reproductibles en nombre fini, auquel est associé un ensemble de changements >
Cl.
entre les deux premiers niveaux de mémoire cache est d'environ 10 (le cache de niveau 1 est
0 0
u d'états non instantanés qu'il est possible de commander et d' enchaîner sans intervention hu u le plus rapide et le plus petit). Le temps d'accès entre la mémoire cache secondaire et la
maine. mémoire centrale est lui aussi d'un rapport d'environ 10.
Un ordinateur est un automate déterministe à composants électroniques. Tous les ordina Les équipements externes, ou périphériques, sont un ensemble de composants permettant
teurs, tout au moins les ordinateurs monoprocesseurs, sont construits, peu ou prou, sur le mo de relier l'ordinateur au monde extérieur, et notamment à ses utilisateurs humains. On peut
dèle proposé en 1 944 par le mathématicien américain d'origine hongroise VON NEUMANN. distinguer :
Un ordinateur est muni :
1. Notez qu'avec 32 bits, J'espace adressage est de 4 Go mais qu'en général les systèmes d'exploitation ne
- D'une mémoire, dite centrale ou principale, qui contient deux sortes d'informations : permettent d'utiliser qu'un espace mémoire de taille inférieure. Les machines 64 bits actuelles, avec un système
d'une part l'information traitante, les instructions, et d'autre part l'information trai- d'exploitation adapté, permettent des tailles de mémoire centrale supérieures à 4 Go.
1. 1 Environnement matériel 3 4 Chapitre 1 • Introduction
unité centrale
Le lecteur intéressé par 1' architecture et 1 ' organisation des ordinateurs pourra lire avec
;- ----------- ------------ -
: profit le livre d' A. TANENBAUM [Tan 1 2] sur le sujet.
1 1
instructions
1
1 .2 E NVIRO N N E M E NT LOGICIEL
1
1
1
mémoire
1
1
1
centrale
données
1
1
L'ordinateur que fabrique le constructeur est une machine incomplète à laquelle il faut
unité arithmétique
ajouter, pour la rendre utilisable, une quantité importante de programmes variés, qui consti
1
et logique -
1
résultats tuent le logiciel.
1
, _________[ ______________:
En général, un ordinateur est livré avec un système d'exploitation. Un système d'exploi
1
1
1
tation est un programme, ou plutôt un ensemble de programmes, qui assurent la gestion des
ressources, matérielles et logicielles, employées par le ou les utilisateurs. Un système d'ex
unité d'échange
ploitation a pour tâche la gestion et la conservation de l' information (gestion des processus
et de la mémoire centrale, système de gestion de fichiers) ; il a pour rôle de créer l' environ
nement nécessaire à l 'exécution d'un travail, et est chargé de répartir les ressources entre
les usagers. Il propose aussi de nombreux protocoles de connexion pour relier l'ordinateur à
périphériques un réseau. Entre l'utilisateur et l' ordinateur, le système d'exploitation propose une interface
textuelle au moyen d'un interprète de commandes et une interface graphique au moyen d'un
FIGURE 1.1 Structure générale d'un ordi nateur.
gestionnaire de fenêtres.
- Les dispositifs qui servent pour la communication avec l'homme (clavier, écran, impri Les systèmes d'exploitation des premiers ordinateurs ne permettaient l'exécution que
mantes, micros, haut-parleurs, scanners, etc.) et qui demandent une transcodification d' une seule tâche à la fois, selon un mode de fonctionnement appelé traitement par lots
appropriée de 1 ' information, par exemple sous forme de caractères alphanumériques. qui assurait l'enchaînement de l'exécution des programmes. À partir des années 1960, les
- Les mémoires secondaires, qui permettent de conserver de l'information, impossible systèmes d'exploitation ont cherché à exploiter au mieux les ressources des ordinateurs en
à garder dans la mémoire centrale de 1 ' ordinateur faute de place, ou que 1 'on désire permettant le temps partagé, pour offrir un accès simultané à plusieurs utilisateurs.
conserver pour une période plus ou moins longue après l 'arrêt de l'ordinateur. Jusqu'au début des années 1 980, les systèmes d'exploitation étaient dits propriétaires.
Les mémoires secondaires sont les disques durs magnétiques ou flash, les CD-ROM, Les constructeurs fournissaient avec leurs machines un système d' exploitation spécifique,
les DVD, les Elu-ray ou les clés USE. Par le passé, les bandes magnétiques ou les et le nombre de systèmes d'exploitation différents était important. Aujourd'hui, ce nombre a
disquettes étaient des supports très utilisés. Actuellement les bandes magnétiques ne considérablement réduit, et seuls quelques-uns sont réellement utilisés dans le monde. Citons,
le sont quasiment plus, la fabrication des disquettes (supports de très petite capacité par exemple, WINDOWS, MACÜS ou LINUX pour les ordinateurs individuels, et UNIX pour
-0 -0
0 et peu fiables) a été arrêtée depuis plusieurs années déjà, et les ordinateurs vendus au 0 les ordinateurs multi-utilisateurs.
c: c:
0
=i jourd'hui sont bien souvent dépourvus de lecteur/graveur de DVD ou Elu-ray. 0
=i
Aujourd'hui, la capacité des mémoires secondaires atteint des valeurs toujours plus Aujourd'hui, avec l'augmentation de la puissance des ordinateurs personnels et l 'avène
"<:t" "<:t"
,..-1 ,..-1
0 importantes. Alors que certains DVD ont une capacité de 17 Go, qu'un disque Elu-ray 0 ment des réseaux mondiaux, les systèmes d'exploitation offrent, en plus des fonctions déjà
N N
atteint 50 Go, et que des clés USE de 64 Go sont courantes, un seul disque dur peut mé citées, une quantité extraordinaire de services et d'outils aux utilisateurs. Ces systèmes d'ex
@ @
.......
..c: moriser jusqu'à plusieurs téraoctets. Des systèmes permettent de regrouper plusieurs
.......
..c:
ploitation modernes mettent à la disposition des utilisateurs tout un ensemble d'applications
O'l O'l
·c
disques, vus comme un disque unique, offrant une capacité de plusieurs centaines de ·c (traitement de texte, tableurs, outils multimédias, navigateurs pour le web, jeux, ... ) qui leur
> >
Cl.
0 téraoctets. Toutefois, 1 ' accès aux informations sur les supports secondaires reste bien
Cl.
0
offrent un environnement de travail pré-construit, confortable et facile d'utilisation.
u u
plus lent que celui aux informations placées en mémoire centrale. Pour les disques Le traitement de l' information est l'exécution par l'ordinateur d'une série finie de com
durs, le rapport est d'environ 10. mandes préparées à l ' avance, le programme, qui vise à calculer et rendre des résultats, gé
À l'avenir, avec le développement du « nuage » (cloud computing), la tendance est plu néralement, en fonction de données entrées au début ou en cours d'exécution par l' intermé
tôt à la limitation des mémoires locales, en faveur de celles, délocalisées et bien plus diaire d'interfaces textuelles ou graphiques. Les commandes qui forment le programme sont
vastes, proposées par les centres de données (datacenters) accessibles par le réseau décrites au moyen d'un langage. Si ces commandes se suivent strictement dans le temps,
Internet. et ne s'exécutent jamais simultanément, l'exécution est dite séquentielle, sinon elle est dite
- Les dispositifs qui permettent l'échange d'informations sur un réseau. Pour relier l' or parallèle.
dinateur au réseau, il existe par exemple des connexions filaires comme celles de type
ethernet, ou des connexions sans fil comme celles de type WiFi.
1.3 Les langages de programmation 5 6 Chapitre 1 • Introduction
Chaque ordinateur possède un langage qui lui est propre, appelé langage machine. Le Le texte qui suit4, écrit en langage d' assemblage d'un Core i5-3320M d'Intel, correspond
langage machine est un ensemble de commandes élémentaires représentées en code binaire à l'appel, de la fonction C pr int f ( "Bonjour\n") qui écrit Bonjour sur la sortie standard (e.g.
qu'il est possible de faire exécuter par l'unité centrale de traitement d'un ordinateur donné. l'écran) depuis la fonction main.
Le seul langage que comprend l'ordinateur est son langage machine.
LCO :
Tout logiciel est écrit à l'aide d'un ou plusieurs langages de programmation. Un langage
. st r i ng "Bon j ou r "
de programmation est un ensemble d'énoncés déterministes, qu' il est possible, pour un être
. t ext
humain, de rédiger selon les règles d'une grammaire donnée, et destinés à représenter les
. gl ob l ma in
objets et les commandes pouvant entrer dans la constitution d'un programme. Ni le langage . t ype mai n , @ funct ion
machine, trop éloigné des modes d'expressions humains, ni les langues naturelles écrites ou ma in :
parlées, trop ambiguës, ne sont des langages de programmation. . LFBO :
. c f i_s t artproc
La production de logiciel est une activité difficile et complexe, et les éditeurs font payer,
pushq % rbp
parfois très cher, leur logiciel dont le code source n'est, en général, pas distribué. Toute
. c f i def c f a o f f s e t 16
fois, tous les logiciels ne sont pas payants. La communauté internationale des informaticiens
. c fi_o f f s e t 6 , -16
produit depuis longtemps du logiciel gratuit (ce qui ne veut pas dire qu' il est de mauvaise movq % r sp , %rbp
qualité, bien au contraire) mis à la disposition de tous. Il existe aux États-Unis 2, une fonda . c f i_def_cfa_regi s t e r 6
tion, la FSF (Free Software Foundation), à l ' initiative de R . STALLMAN, qui a pour but la movl $ . LC O , %edi
promotion de la construction du logiciel libre, ainsi que celle de sa distribution. Libre ne veut call puts
pas dire nécessairement gratuit 3, bien que cela soit souvent le cas, mais indique que le texte movl $0 , %eax
source du logiciel est disponible. D'ailleurs, la FSF propose une licence, GNU GPL, afin de popq % rbp
garantir que les logiciels sous cette licence soient libres d'être redistribués et modifiés par . c f i_def_cfa 7 , 8
tous leurs utilisateurs. ret
. c f i_endp r o c
Le système d'exploitation LINUX est disponible librement depuis de nombreuses années,
et aujourd'hui certains éditeurs suivent ce courant en distribuant, comme Apple par exemple,
Le langage d'assemblage, comme le langage machine, est d'un niveau très élémentaire
gratuitement la dernière version de leur système d'exploitation Mavericks (toutefois sans le
(une suite linéaire de commandes et sans structure) et, comme le montre l'exemple précé
code source).
dent, guère lisible et compréhensible. Son utilisation par un être humain est alors difficile,
fastidieuse et sujette à erreurs.
Ces défauts, entre autres, ont conduit à la conception des langages de programmation dits
1 .3 LES LANGAGES D E PROG RAM MATION de haut niveau. Un langage de programmation de haut niveau offrira au programmeur des
-0 -0 moyens d'expression structurés proches des problèmes à résoudre et qui amélioreront la fia
0
c:
Nous venons de voir que chaque ordinateur possède un langage qui lui est propre : le 0
c: bilité des programmes. Pendant de nombreuses années, les ardents défenseurs de la program
=i =i
0 langage machine, qui est en général totalement incompatible avec celui d'un ordinateur d'un 0
mation en langage d' assemblage avançaient le critère de son efficacité. Les optimiseurs de
"<:t"
,..-1 autre modèle. Ainsi, un programme écrit dans le langage d'un ordinateur donné ne pourra "<:t"
,..-1
code ont balayé cet argument depuis longtemps, et les défauts de ces langages sont tels que
0 0
N être réutilisé sur un autre ordinateur. N
leurs thuriféraires sont de plus en plus rares.
@ @
.......
..c:
Le langage d'assemblage est un codage alphanumérique du langage machine. Il est plus .......
..c: Si on ajoute à un ordinateur un langage de programmation, tout se passe comme si l'on dis
O'l O'l
·c lisible que ce dernier et surtout permet un adressage relatif de la mémoire. Toutefois, comme ·c posait d'un nouvel ordinateur (une machine abstraite), dont le langage est maintenant adapté
> >
Cl.
0
le langage machine, le langage d'assemblage est lui aussi dépendant d'un ordinateur donné Cl.
0 à l'être humain, aux problèmes qu'il veut résoudre et à la façon qu'il a de comprendre et
u u
(voire d'une famille d'ordinateurs) et ne facilite pas le transport des programmes vers des de raisonner. De plus, cet ordinateur fictif pourra recouvrir des ordinateurs différents, si le
machines dont l'architecture est différente. L'exécution d'un programme écrit en langage langage de programmation peut être installé sur chacun d'eux. Ce dernier point est très im
d'assemblage nécessite sa traduction préalable en langage machine par un programme spé portant, puisqu'il signifie qu'un programme écrit dans un langage de haut niveau pourra être
cial, l'assembleur. exploité (théoriquement) sans modification sur des ordinateurs différents.
La définition d'un langage de programmation recouvre trois aspects fondamentaux. Le
2. FSF France a pour but la promotion du logiciel libre en France ([Link] premier, appelé lexical, définit les symboles (ou caractères) qui servent à la rédaction des
3. La confusion provient du fait qu'en anglais le mot« free »possède les deux sens.
4. Produit par le compilateur gcc.
1.3 Les langages de programmation 7 8 Chapitre 1 • Introduction
programmes et les règles de formation des mots du langage. Par exemple, un entier décimal comporte les mêmes phases d'analyse qu'un compilateur. L' interprétation est alors
sera défini comme une suite de chiffres compris entre 0 et 9. Le second, appelé syntaxique, faite sur le langage intermédiaire. C'est la technique d'implantation du langage JAVA
est l'ensemble des règles grammaticales qui organisent les mots en phrases. Par exemple, (voir la figure 1 .3), mais aussi de beaucoup d'autres langages. Un programme source
la phrase « 234 / 54 »,formée de deux entiers et d'un opérateur de division, suit la règle JAVA est d'abord traduit en un programme objet écrit dans un langage intermédiaire,
grammaticale qui décrit une expression. Le dernier aspect, appelé sémantique, étudie la si appelé JAVA pseudo-code (ou byte-code). Le programme objet est ensuite exécuté par
gnification des phrases. Il définit les règles qui donnent un sens aux phrases. Notez qu'une la machine virtuelle JAVA, JVM (Java Virtual Machine).
phrase peut être syntaxiquement valide, mais incorrecte du point de vue de sa sémantique
(e.g. 234/0, une division par zéro est invalide). L'ensemble des règles lexicales, syntaxiques
et sémantiques définit un langage de programmation, et on dira qu' un programme appartient programme
à un langage de programmation donné s'il vérifie cet ensemble de règles. La vérification de la source Java
conformité lexicale, syntaxique et sémantique d'un programme est assurée automatiquement
par des analyseurs qui s' appuient, en général, sur des notations formelles qui décrivent sans
ambiguïté l 'ensemble des règles.
Comment exécuter un programme rédigé dans un langage de programmation de haut ni compilateur
veau sur un ordinateur qui, nous le savons, ne sait traiter que des programmes écrits dans son
langage machine ? Voici deux grandes familles de méthodes qui permettent de résoudre ce
problème :
langage interprète
- La première méthode consiste à traduire le programme, appelé source, écrit dans le - - - - - - résultats
intermédiaire JVM
langage de haut niveau, en un programme sémantiquement équivalent écrit dans le
langage machine de l'ordinateur (voir la figure 1.2). Cette traduction est faite au moyen
d'un logiciel spécialisé appelé compilateur. Un compilateur possède au moins quatre
phases : trois phases d'analyse (lexicale, syntaxique et sémantique), et une phase de données
u
0
FIGURE 1.2 Traduction en langage machine. u
0 de soixante ans, plusieurs centaines de langages de programmation ont été conçus. Certains
n'existent plus, d'autres ont eu un usage limité, et seule une minorité sont vraiment très uti
- Nous avons vu qu'un langage de programmation définü un ordinateur fictif. La seconde lisés 5 . Le but de ce paragraphe est de donner quelques repères importants dans l'histoire des
méthode consiste à simuler le fonctionnement de cet ordinateur fictif sur l'ordinateur ».
langages de programmation « classiques 11 n'est pas question de dresser ici un historique
réel par interprétation des instructions du langage de programmation de haut niveau. exhaustif. Le lecteur pourra se reporter avec intérêt aux ouvrages [Sam69, Wex8 1 , Hor83]
Le logiciel qui effectue cette interprétation s'appelle un interprète. L'interprétation qui retracent les vingt-cinq premières années de cette histoire, à [ACM93] pour les quinze
directe des instructions du langage est en général difficilement réalisable. Une pre années qui suivirent, et à [M+ 89] qui présente un panorama complet des langages à objets.
mière phase de traduction du langage de haut niveau vers un langage intermédiaire
de plus bas niveau est d'abord effectuée. Remarquez que cette phase de traduction 5. Voir les classements donnés par l ang- index . sourceforge . net ou www . t i obe . corn.
1.3 Les langages de programmation 9 10 Chapitre 1 • Introduction
FORTRAN (Formula Translator) [Int57, ANS78] fut le premier traducteur en langage ma BACKUS-NAUR 6, et le premier à soumettre la définition à l'ensemble de la communauté
chine d'une notation algébrique pour écrire des formules mathématiques. Il fut conçu à I B M pour en permettre la révision avant de figer quoi que ce soit. De nombreux concepts, que l'on
à partir de 1 954 par J. B A C K U S en collaboration avec d' autres chercheurs. Jusqu' à cette date, retrouvera dans la plupart des langages de programmation qui suivront, ont été définis pour la
les programmes étaient écrits en langage machine ou d'assemblage, et l'importance de FOR première fois dans ALGOL 60 (la structure de bloc, le concept de déclaration, le passage des
TRAN a été de faire la démonstration, face au scepticisme de certains, de l'efficacité de la paramètres, les procédures récursives, les tableaux dynamiques, les énoncés conditionnels
traduction automatique d'une notation évoluée pour la rédaction de programmes de calcul et itératifs, le modèle de pile d'exécution, etc.). Pour toutes ces raisons, et malgré quelques
numérique scientifique. À l'origine, FORTRAN n'est pas un langage et ses auteurs n'en ima lacunes mises en évidence par D. KNUTH [Knu67], ALGOL 60 est le langage qui fit le plus
ginaient pas la conception. En revanche, ils ont inventé des techniques d'optimisation de code progresser l'informatique.
particulièrement efficaces. Dans ces années 1 960, des tentatives de définition de langages universels, c'est-à-dire
pouvant s' appliquer à tous les domaines, ont vu le jour. Les langages PL/I (Programming
LISP (List Processor) a été développé à partir de la fin de l' année 1 958 par J. MCCARTHY
Language One) [ANS76] et ALGOL 68 reprennent toutes les « bonnes » caractéristiques de
au MIT (Massachusetts Institute of Technology) pour le traitement de données symboliques
leurs aînés conçus dans les années 1 950. PL/I cherche à combiner en un seul langage CO
(i.e. non numériques) dans le domaine de l' intelligence artificielle. Il fut utilisé pour résoudre
BOL, LISP, FORTRAN (entre autres langages), alors qu' ALGOL 68 est le successeur officiel
des problèmes d' intégration et de différenciation symboliques, de preuve de théorèmes, ou
d' ALGOL 60. Ces langages, de par la trop grande complexité de leur définition, et par consé
encore de géométrie et a servi au développement de modèles théoriques de l 'informatique.
quence de leur utilisation, n'ont pas connu le succès attendu.
La notation utilisée, appelée S-expression, est plus proche d'un langage d' assemblage d'une
machine abstraite spécialisée dans la manipulation de liste (le type de donnée fondamental ; Lui aussi fortement inspiré par ALGOL 60, PA SCAL [NAJN75, AFN82] est conçu par
un programme LISP est lui-même une liste) que d'un véritable langage de programmation. N. WTRTH en 1 969. D'une grande simplicité conceptuelle, ce langage algorithmique a servi
Cette notation préfixée de type fonctionnel utilise les expressions conditionnelles et les fonc (et peut-être encore aujourd'hui) pendant de nombreuses années à l'enseignement de la pro
tions récursives basées sur la notation À (lambda) de A . CHURCH. Une notation, appelée grammation dans les universités.
M-expression, s' inspirant de FORTRAN et à traduire en S-expression, avait été conçue à la fin Le langage C [KR88, ANS89] a été développé en 1 972 par D . RITCHIE pour la récriture
des années 1 950 par J. McCARTHY, mais n'a jamais été implémentée. Hormis LISP 2, un du système d'exploitation UNIX. Conçu à l'origine comme langage d'écriture de système,
langage inspiré de ALGOL 60 (voir paragraphes suivants) développé et implémenté au milieu ce langage est utilisé pour la programmation de toutes sortes d' applications. Malgré de nom
des années 1 960, les nombreuses versions et variantes ultérieures de L I S P seront basées sur la breux défauts, C est encore très utilisé aujourd'hui, sans doute pour des raisons d'efficacité
notation S-expression. L IS P et ses successeurs font partie des langages dits fonctionnels (cf du code produit et une certaine portabilité des programmes. Ce langage a été normalisé en
le chapitre 1 3). Il est à noter aussi que LISP est le premier à mettre en œuvre un système de 1 989 par l' ANSI 7 , puis en 1 999 et 20 1 1 par l'ISO 8 (normes C99 et C l l ).
récupération automatique de mémoire (garbage-collector). Les années 1 970 correspondent à l'approche « génie logiciel ». Devant le coût et la com
plexité toujours croissants des logiciels, il devient essentiel de développer de nouveaux lan
La gestion est un autre domaine important de l'informatique. Au cours des années 1 950
gages puissants, ainsi qu'une méthodologie pour guider la construction, maîtriser la com
furent développés plusieurs langages de programmation spécialisés dans ce domaine. À partir
plexité, et assurer la fiabilité des programmes. ALPHARD [W+ 76] et CLU [L+ 77], deux
de 1 959, un groupe de travail comprenant des universitaires, mais surtout des industriels
-0 -0 langages expérimentaux, MODULA-2 [Wir85], ou encore ADA [ANS83] sont des exemples
0
c:
américains, sous l'égide du Département de la Défense des États-Unis (DOD), réfléchit à 0
c:
=i =i parmi d'autres de langages imposant une méthodologie dans la conception des programmes.
0 la conception d'un langage commun pour les applications de gestion. Le langage COBOL 0
Une des originalités du langage ADA est certainement son mode de définition. Il est le produit
"<:t"
,..-1 (Common Business Oriented Language) [Cob60] est le fruit de cette réflexion. Il a posé les "<:t"
,..-1
0 0 d'un appel d'offres international lancé en 1 974 par le DOD pour unifier la programmation de
N premières bases de la structuration des données. N
@ @ ses systèmes embarqués. Suivirent de nombreuses années d'étude de conception pour débou
....... .......
..c:
On peut dire que les années 1 950 correspondent à l'approche expérimentale de l'étude des ..c: cher sur une norme (ANSI, 1 983), posée comme préalable à l'exploitation du langage .
O'l O'l
·c ·c
>
Cl.
concepts des langages de programmation. Il est notable que FORTRAN, L I SP et COBOL, sous >
Cl.
Les langages des années 1 980- 1 990, dans le domaine du génie logiciel, mettent en avant le
u
0
des formes qui ont bien évolué, sont encore largement utilisés aujourd'hui. Les années 1 960 u
0
concept de la programmation objet. Cette notion n' est pas nouvelle puisqu'elle date de la fin
correspondent à l 'approche mathématique de ces concepts, et le développement de ce qu'on des années 1960 avec SIMULA [DN66], certainement le premier langage à objets. SMALL
appelle la théorie des langages. En particulier, beaucoup de notations formelles sont apparues TALK [GR89], C++ [Str86] (issu de C), EIFFEL [Mey92], ou JAVA [GJS96, GRl l ] , ou encore
pour décrire la sémantique des langages de programmation. plus récemment C# [SG08] sont, parmi les très nombreux langages à objets, les plus connus.
De tous les langages, ALGOL 60 (Algorithmic Language) [Nau60] est celui qui a eu le plus 6. À l'origine appelée Forme Normale de BACKUS.
d'influence sur les autres. C'est le premier langage défini par un comité international (présidé 7. American National Standards lnstitute, l'institut de normalisation des États-Unis.
par J. BACKU S et presque uniquement composé d' universitaires), le premier à séparer les 8. International Organization for Standardization, organisme de normalisation représentant 164 pays dans le
aspects lexicaux et syntaxiques, à donner une définition syntaxique formelle, la Forme de monde.
1.4 Construction des programmes 11 12 Chapitre 1 • Introduction
JAVA connaît aujourd'hui un grand engouement, en particulier grâce au web et Internet. les méthodes de construction des programmes commençaient par structurer les actions. La
Ces quinze dernières années bien d'autres langages ont été conçus autour de cette technolo structuration des objets venait ultérieurement. Depuis la fin des années 1 980, le processus
gie. Citons, par exemple, les langages de script (langages de commandes conçus pour être est inversé. Essentiellement pour des raisons de pérennité (relative) des objets par rapport à
interprétés) JAVASCRIPT [Fla l O] destiné à la programmation côté client, et PHP [MaclO] celle des actions : les programmes sont structurés d'abord autour des objets. Les choix de
défini pour la programmation côté serveur HTTP. structuration des actions sont fixés par la suite.
Dans le domaine de l' intelligence artificielle, nous avons déjà cité LISP. Un autre langage, Lorsque le choix des actions précède celui des objets, le problème à résoudre est dé
le langage déclaratif PROLOG (Programmation en Logique) [CKvC83], conçu dès 1 972 par composé, en termes d' actions, en sous-problèmes plus simples, eux-mêmes décomposés
l'équipe marseillaise de A. COLMERAUER, a connu une grande notoriété dans les années en d' autres sous-problèmes encore plus simples, jusqu'à obtenir des éléments directement
1 980. PROLOG est issu de travaux sur le dialogue homme-machine en langage naturel et sur programmables. Avec cette méthode de construction, souvent appelée programmation des
les démonstrateurs automatiques de théorèmes. Un programme PROLOG ne s'appuie plus sur cendante par raffinements successifs, la représentation particulière des objets, sur lesquels
un algorithme, mais sur la déclaration d'un ensemble de règles à partir desquelles les résultats portent les actions, est retardée le plus possible. L'analyse du problème à traiter se fait dans
pourront être déduits par unification et rétro-parcours (backtracking) à l'aide d'un évaluateur le sens descendant d'une arborescence, dont chaque nœud correspond à un sous-problème
spécialisé. bien déterminé du programme à construire. Au niveau de la racine de l'arbre, on trouve
Poursuivons cet historique par le langage IcoN [GHK79]. Il est le dernier d'une famfüe le problème posé dans sa forme initiale. Au niveau des feuilles, correspondent des actions
de langages de manipulation de chaînes de caractères (SNOBOL 1 à 4 et S L5 ) conçus par pouvant s'énoncer directement et sans ambiguïté dans le langage de programmation choisi.
R. GRTSWOLD dès 1 960 pour le traitement de données symboliques. Ces langages intègrent Sur une même branche, le passage du nœud père à ses fils correspond à un accroissement
le mécanisme de confrontation de modèles (pattern matching), la notion de succès et d'échec du niveau de détail avec lequel est décrite la partie correspondante. Notez que sur le plan
de l'évaluation d'une expression, mais l'idée la plus originale introduite par ICON est celle du horizontal, les différents sous-problèmes doivent avoir chacun une cohérence propre et donc
mécanisme de générateur et d'évaluation dirigée par le but. Un générateur est une expression minimiser leur nombre de relations.
qui peut fournir zéro ou plusieurs résultats, et l'évaluation dirigée par le but permet d'exploi En revanche, lorsque le choix des objets précède celui des actions, la structure du pro
ter les séquences de résultats produites par les générateurs. Ces langages ont connu un vif gramme est fondée sur les objets et sur leurs interactions. Le problème à résoudre est vu
succès et il existe aujourd'hui encore une grande activité autour du langage ICON. comme une modélisation (opérationnelle) d'un aspect du monde réel constitué d'objets. Cette
Si l' idée de langages universels des années 1 960 a été aujourd'hui abandonnée, plusieurs vision est particulièrement évidente avec les logiciels graphiques et plus encore, de simula
langages dits multiparadigme ont vu le jour ces dernières années. Parmi eux, citons, pour ter tion. Les objets sont des composants qui contiennent des attributs (données) et des méthodes
miner ce bref historique, les langages PYTHON [Lut09], RUBY [FM08] et SCALA [OSV08]. (actions) qui décrivent le comportement de l' objet. La communication entre objets se fait par
Les deux premiers langages sont à typage dynamique (vérification de la cohérence des types envoi de messages, qui donne l' accès à un attribut ou qui lance une méthode.
de données à l'exécution) et incluent les paradigmes fonctionnel et objet. De plus, PYTHON Les critères de fiabilité et de validité ne sont pas les seuls à caractériser la qualité d'un
intègre la notion de générateur similaire à celle d'ICON, et RUBY permet la manipulation des programme. Il est fréquent qu'un programme soit modifié pour apporter de nouvelles fonc
processus légers (threads) pour la programmation concurrente. SCALA, quant à lui, intègre tionnalités ou pour évoluer dans des environnements différents, ou soit dépecé pour fournir
les paradigmes objet et fonctionnel avec un typage statique fort. La mise en œuvre du langage « des pièces détachées » à d'autres programmes. Ainsi de nouveaux critères de qualité, tels
-0 -0
0
c: permet la production de bytecode pour la machine virtuelle JVM, ce qui lui offre une grande 0
c: que l'extensibilité, la compatibilité ou la réutilisabilité, viennent s'ajouter aux précédents.
=i =i
0 compatibilité avec le langage JAVA . 0 Nous verrons que l'approche objet, bien plus que la méthode traditionnelle de décomposition
"<:t" "<:t"
,..-1
0
,..-1
0
fonctionnelle, permet de mieux respecter ces critères de qualité.
N N
@ @
Les actions mises en jeu dans les deux méthodologies précédentes reposent sur la notion
.......
..c: 1 .4 CONSTRUCTION D E S PROGRAM M E S .......
..c:
d'algorithme 9 . L'algorithme décrit, de façon non ambiguë, l'ordonnancement des actions à
O'l O'l
·c ·c effectuer dans le temps pour spécifier une fonctionnalité à traiter de façon automatique. Il est
> >
Cl.
0
L'activité de programmation est difficile et complexe. Le but de tout programme est de cal Cl.
0
dénoté à l' aide d'une notation formelle, qui peut être indépendante du langage utilisé pour le
u
culer et retourner des résultats valides [Link]. Quelle que soit la taille des programmes, de u
programmer.
quelques dizaines de lignes à plusieurs centaines de milliers, la conception des programmes La conception d' algorithme est une tâche difficile qui nécessite une grande réflexion. No
exige des méthodes rigoureuses, si les objectifs de justesse et de fiabilité veulent être atteints. tez que le travail requis pour l'exprimer dans une notation particulière, c'est-à-dire la pro-
D'une façon très générale, on peut dire qu'un programme effectue des actions sur des
objets. Jusque dans les années 1 960, la structuration des programmes n'était pas un souci 9. Le mot algorithme ne vient pas, comme certains le pensent, du mot logarithme, mais doit son origine à
un mathématicien persan du IX0 siècle, dont le nom abrégé était AL-KHOWÂRIZMÎ (de la ville de Khowârizm).
majeur. C'est à partir des années 1 970, face à des coûts de développement des logiciels
Cette ville située dans l'Üzbekistan, s'appelle aujourd'hui Khiva. Notez toutefois que cette notion est bien plus
croissants, que l'intérêt pour la structuration des programmes s'est accru. À cette époque, ancienne. Les Babyloniens de l' Antiquité, les Égyptiens ou les Grecs avaient déjà formulé des règles pour résoudre
des équations. Euclide (vers 300 av. J.-C.) conçut un algorithme permettant de trouver le pgcd de deux nombres.
1.5 Démonstration de validité 13 14 Chapitre 1 • Introduction
grammati on de 1 ' algorithme dans un langage particulier, est réduit par comparaison à celui le passage de l 'antécédent initial jusqu'au conséquent final, par application des règles de
de sa conception. La réflexion sur papier, stylo en main, sera le préalable à toute program déduction sur toutes les actions qui le composent.
mation sur ordinateur. 11 est important de comprendre que les affirmations ne doivent pas être définies a poste
Pour un même problème, il existe bien souvent plusieurs algorithmes qui conduisent à sa riori, c'est-à-dire une fois le programme écrit, mais bien au contraire a priori puisqu' il s'agit
»
solution. Le choix du « meilleur algorithme est alors généralement guidé par des critères de construire l ' action en fonction de l'effet prévu.
d'efficacité. La complexité d'un algorithme est une mesure théorique de ses performances en Une action A avec son antécédent et son conséquent sera dénotée :
fonction d'éléments caractéristiques de l'algorithme. Le mot théorique signifie en particulier
que la mesure est indépendante de l'environnement matériel et logiciel. Nous verrons à la {ant écéden t }
section 10.5 page 1 14 comment établir cette mesure. A
Le travail principal dans la conception d'un programme résidera dans le choix des ob { conséquent }
jets qui le structureront, la validation de leurs interactions et le choix et la vérification des
algorithmes sous-jacents. Les assertions doivent être le plus formelles possible, si lon désire prouver la validité du
programme. Elles s'apparentent d' ailleurs à la notion mathématique de prédicat. Toutefois,
il sera nécessaire de trouver un comprontis entre leur complexité et celle du programme. En
d'autres termes, s'il est plus difficile de construire ces assertions que le programme lui-même,
1 .5 D É MONSTRATION D E VALIDITÉ
on peut se demander quel est leur intérêt ?
Notre but est de construire des programmes valides, c'est-à-dire conformes à ce que l'on Certains langages de programmation, en fait un nombre réduit 1 1 , intègrent des méca
attend d'eux. Comment vérifier la validité d'un programme ? Une fois le programme écrit, nismes de vérification de la validité des assertions spécifiées par les programmeurs. Dans
on peut, par exemple, tester son exécution. Si la phase de test, c'est-à-dire la vérification ces langages, les assertions font donc parties intégrantes du programme. Elles sont contrô
expérimentale par l 'exécution du programme sur des données particulières, est nécessaire, lées au fur et à mesure de l'exécution du programme, ce qui permet de détecter une situation
elle ne permet en aucun cas de démontrer la justesse à 1 00% du programme. En effet, il d'erreur. En JAVA, une assertion est représentée par une expression booléenne introduite par
faudrait faire un test exhaustif sur l'ensemble des valeurs possibles des données. Ainsi, pour l'énoncé as sert. Le caractère booléen de l'assertion est toutefois assez réducteur car bien
une simple addition de deux entiers codés sur 32 bits, soit 232 valeurs possibles par entier, souvent les programmes doivent utiliser des assertions avec les quantificateurs de la logique
il faudrait tester 232 x 2 opérations. Pour une microseconde par opération, il faudrait 9 x 10 9 du prentier ordre que cet énoncé ne pourra exprimer. Des extensions au langage à l'aide
années ! N . WIRTH résume cette idée dans [Wir75] par la formule suivante : d'annotations spéciales, comme [LC06], ont été récemment proposées pour obtenir une spé
cification formelle des programmes JAVA .
« L 'expérimentation des programmes peut servir à montrer la présence d'erreurs, mais Dans les autres langages, les assertions, même si elles ne sont pas traitées automatique
jamais à prouver leur absence. » ment par le système, devront être exprimées sous forme de commentaires. Ces commentaires
serviront à l'auteur du programme, ou aux lecteurs, à se convaincre de la validité du pro-
La preuve 10 de la validité d'un programme ne pourra donc se faire que formellement de
-0 -a gramme.
0
c:
façon analytique, tout le long de la construction du programme et, évidemment, pas une fois 0
c:
=i =i
0 que celui-ci est terminé. 0
"<:t" "<:t"
,..-1
0
La technique que nous utiliserons est basée sur des assertions qui décriront les propriétés ,.-1
0
N N
des éléments (objets, actions) du programme. Par exemple, une assertion indiquera qu' en tel
@ @
....... point du programme telle valeur entière est négative . .......
..c: ..c:
O'l O'l
·c
>
Nous parlerons plus tard des assertions portant sur les objets. Celles pour décrire les pro ·c
>
Cl.
0 priétés des actions, c'est-à-dire leur sémantique, suivront l'axiomatique de C.A.R. HOARE Cl.
0
u u
[Hoa69]. L'assertion qui précède une action s' appelle l'antécédent ou pré-condition et celle
qui la suit le conséquent ou post-condition.
Pour chaque action du programme, il sera possible, grâce à des règles de déduction, de dé
duire de façon systématique le conséquent à partir de l'antécédent. Notez qu'il est également
possible de déduire l' antécédent à partir du conséquent. Ainsi pour une tâche particulière,
formée par un enchaînement d' actions, nous pourrons démontrer son exactitude, c'est-à-dire
l l . Citons certains langages expérimentaux conçus dans les années 1 970, tels que ALPHARD [M. 8 1 ] , ou plus
lO. La preuve de programme est un domaine de recherche théorique ancien, mais toujours ouvert et très actif. récemment EIFFEL.
16 Chapitre 2 • Actions élémentaires
lu. La lecture d'un objet sur l'entrée standard à placer en mémoire centrale sous le nom x
s'écrit de la façon suivante :
{ i l exi s t e une donnée à l i re sur l ' en t rée s tandard}
l i re ( x )
{ une donnée a é t é l u e s u r l ' en t rée s tandard,
pla cée en mémoire cen t ra l e et l e n om x permet de la dési gner}
Notez que plusieurs commandes de lecture peuvent être exécutées les unes à la suite des
autres. Si le même nom est utilisé chaque fois, il désignera la dernière donnée lue.
Chapitre 2
0
=i
donnée et l'écriture d'un résultat. 0
=i { l ' appel de sin (x) a cal culé l e sinus de x }
"<:t" "<:t"
,..-1 ,..-1
0 0 Bien évidemment, il est possible de fournir plusieurs paramètres « donnée » lors de l' appel
N N
{ l e s n oms x et y dési gnent des val eurs en mémoire} 11 est pourtant utile ou nécessaire, en particulier pour des raisons de fiabilité du programme,
P (x, y, a, b, c) de garantir qu'un nom désigne toujours le même objet en tout point du programme. Un nom
{ l ' appel de l a procédure P s u r l e s données x et y qui ne peut désigner qu'un seul objet, c'est-à-dire que la relation qui les lie ne peut être remise
a cal cu l é troi s rés u l t a t s dés i gnés par a , b, c } en cause, s'appelle une constante.
Y a-t-il d' autres façons d'établir cette relation que les deux que nous venons d'indiquer ?
Remarquez que rien dans la notation de cet appel de procédure ne distingue les paramètres
effectifs « donnée » des paramètres effectifs « résultat ». Nous verrons au chapitre 6 comme La réponse est affirmative. Presque tous les langages de programmation possèdent une action
s'opère cette distinction. élémentaire, appelée affectation, qui associe un nom à un objet.
Notez également que puisqu'une fonction ne produit qu'un seul résultat donné par la dé Chaque langage de programmation a sa manière de concevoir et de représenter 1' action
notation de l' appel, une fonction ne doit pas posséder de paramètre « résultat ». Certains d'affectation, mais cette action comporte toujours trois parties : le nom choisi, l 'objet à dési
langages de programmation en font une règle, mais malheureusement, bien souvent, ils auto gner et le signe opératoire identifiant l'action d' affecter. Dans un langage comme PASCAL, le
risent les fonctions à posséder des paramètres « résultat ». signe d' affectation est : =. L'exemple suivant montre deux actions d'affectation consécutives :
X :=6;
y : =x
2.3 ÉCRITURE D'UN RÉSU LTAT
Il faut bien comprendre que y : =x signifie « faire désigner par y le même objet que celui
Une fois le résultat d'une procédure ou d'une fonction calculé, il est souvent souhaitable désigné par x », en l'occurrence 6, et non pas « faire que les noms x et y soient les mêmes ».
de récupérer ce résultat sur un équipement externe. Il existe pour cela une action élémen Dans notre notation algorithmique, nous choisirons le signe opératoire +-- pour représenter
taire réciproque de celle de lecture. C'est l'action d'écriture. Elle consiste à transférer vers 1' affectation.
un équipement externe désigné, la valeur d'un objet en mémoire. Une transcodification est
associée à cette action dans le cas où le destinataire final est un être humain.
Pour l'instant, nous écrirons les résultats sur l'écran, qu'on nomme la sortie standard. 2.5 DÉCLARATION D'UN NOM
{ l e nom y dési gne une valeur en mémoire}
é c r i re ( y ) Pour des raisons de sécurité des programmes construits, certains langages de programma
{ l a valeur de y a été écri t e sur l a sort i e s t andard} tion exigent que les noms qui servent à désigner les objets soient déclarés. C'est le cas par
exemple en JAVA où tous les noms (non prédéfin is) doivent avoir été déclarés au préalable à
Notez que les actions de lecture et d'écriture correspondent à des appels de procédure et l'aide de commandes de déclaration. Toutefois, les noms prédéfinis peuvent être utilisés tels
que les paramètres effectifs de ces deux procédures sont, respectivement, de type « résultat » quels sans déclaration préalable.
et « donnée ». Afin d'accroître la lisibilité des programmes (même pour des programmes de petite taille),
-0 -0
les noms choisis doivent être significatifs (et certainement longs), c'est-à-dire qu'ils possèdent
0 0
c: c: un sens qui exprime clairement leur utilisation ultérieure. Si nécessaire, un commentaire peut
5 2.4 AFFECTATION D'UN NOM À U N OBJ ET 0
=i
accompagner la déclaration pour donner plus de précision. Notez toutefois que dans le cas
"<:t" "<:t"
,.-1
0
,..-1
0
de noms conventionnels, une seule lettre peut suffire. Par exemple, on notera a, b et c les
N
Nous avons vu qu'il était possible de donner un nom à un objet particulier, soit par une N
trois coefficients d'une équation du second degré, et souvent i l ' indice d'une boucle (voir le
@ @
....... action de lecture, soit par l'intermédiaire d'un paramètre « résultat » d'une procédure. ....... chapitre 8) .
..c: ..c:
O'l O'l
·c Est-ce que la relation établie entre un nom et l'objet qu'il désigne reste vérifiée tout au long ·c
> >-
Cl.
0 de l'exécution du programme ? Cela dépend du programme. Cette relation peut rester vérifiée g- 2.5.1 Déclaration de constantes
u u
durant toute l'exécution du programme, mais aussi cesser. Considérons les deux lectures
consécutives suivantes : Une déclaration de constante établit un lien définitif entre un nom et une valeur particu
lière. Ce nom sera appelé identificateur de constante. L'exemple qui suit présente la déclara
lire (x)
l i re ( x )
tion de deux constantes :
constantes
Après la seconde lecture, la première relation entre x et 1 ' objet lu a cessé, et une seconde nbl e t t r e s 26 { nombre de l e t t res dans l ' alphabet l a t i n }
a été établie entre le même nom x et le second objet lu sur l'entrée standard. Un nom qui sert nbt o u r s 33 { nombre de t ours par min u t e }
à désigner un ou plusieurs objets s' appelle une variable.
2.6 Règles de déduction 19 20 Chapitre 2 • Actions élémentaires
Une déclaration de variable établit un lien entre un nom et un ensemble de valeurs. Le nom d r d + 2
ne pourra désigner que des valeurs prises dans cet ensemble. Ce nom s'appelle un identifica y r y + d
ct r ct + 2
teur de variable et l 'ensemble de valeurs un type. Cette dernière notion sera présentée dans le
y r y + d
chapitre suivant. Dans la déclaration de variables qui suit, le domaine de valeur de la variable
répon s e (introduit par le mot type, voir le chapitre 3) est un ensemble de caractères, alors
Que calcule cette série d'affectations, lorsque l 'antécédent initial est égal à :
que celui des variables r a c i n e l et racine2 est un ensemble de réels.
{y = x2 , d = 2x - l }
variables
répo n s e type caractère
L'application des règles de déduction montre que la variable y prend successivement les
racine l , racine2 type réel
valeurs x2 , ( x+ 1 ) 2 et ( x + 2 ) 2 . Notez qu'en poursuivant, on calcule la suite ( x + i ) 2 .
{y = x2 , d = 2x - l }
d r d + 2
2.6 RÈGLES DE D É DUCTION { y + d = (x + 1) 2, d 2x + l}
y r y + d
L'appel de procédure et l' affectation sont les deux premières actions élémentaires dont {y = (x + 1 ) 2 , d = 2x + l}
nous allons définir les règles de déduction. Pour vérifier la validité de nos programmes, il d r d + 2
nous faut donner les règles de déduction de ces deux actions, c'est-à-dire pouvoir déterminer {y + d = (x + 2) 2 ) , d 2x + 3}
le conséquent en fonction de l 'antécédent par application de l 'action d'affectation ou d'appel y r y + d
{y = (x + 2) 2 , d = 2x + 3}
de procédure.
Tel qu'il est traité, cet exemple applique les règles a posteriori. Rappelons, même si cela
2.6.1 L'affectation est difficile, que les affirmations doivent être construites a priori, ou du moins simultanément
avec le programme.
Pour une affectation x +--- e, la règle de passage de 1 ' antécédent au conséquent s'exprime
de la façon suivante :
2.6.2 L'appel de procédu re
{ A } x f- e { A� }
Les règles de passage de l ' antécédent au conséquent (et réciproquement) dépendent des
Ce qui peut se traduire par : dans l'antécédent A remplacez toutes les apparitions libres 1
paramètres et du rôle de la procédure. Nous verrons comment décrire ces règles plus précisé
de e par x . Par exemple, supposons qu'une variable i soit égale à 10, que vaut i après
ment dans le chapitre 6.
-0
l'affectation i +--- i + 1 ? De toute évidence 1 1 . Montrons-le en faisant apparaître la partie -0
0
c: droite de l'affectation dans l'antécédent, puis en appliquant la règle de déduction : 0
c:
=i =i
=
0 0
{i 10}
"<:t"
,..-1
{i + 1 = 10 + 1 11}
"<:t"
,..-1
2.7 LE PROGRAM M E S I N U S ÉCRIT EN JAVA
0 0
N N
i f- i + l
@
{i = 11} @ Comment s'écrit en JAVA, le programme qui lit un entier sur l 'entrée standard, qui calcule
....... .......
..c:
O'l
..c:
O'l
et écrit son sinus sur la sortie standard ? Rappelons tout d'abord 1 ' algorithme .
Algorithme S i n u s
·c ·c
> Réciproquement, on déduit l' antécédent du conséquent en remplaçant dans le conséquent >
Cl. Cl.
u
0 toutes les apparitions de x par e. u
0
variable x type e n t i e r
{ A� } x f- e { A } { i l exi s t e un en t i er à l i re sur l ' en trée s tandard}
l i re ( x )
Dans l 'exemple suivant, il faut lire de bas en haut à partir du conséquent. { un ent i er a é t é l u s u r l ' en t rée s t andard,
placé en mémoire cen t ra l e et l e n om x permet de l e dés i gner}
{x + y = 1 0 } é c r i r e ( s in ( x ) )
z f- x + y
{ l a val e ur du sinus de x est écri te sur l a s or t i e s tandard}
{z = 10}
l . L'expression e est sans effet de bord, c'est-à-dire qu'elle ne modifie pas son environnement.
2.7 Le programme sinus écrit en Java 21 22 Chapitre 2 • Actions élémentaires
La programmatjon en JAVA de cet algorithme est la suivante : bole d'affectation est le signe =. Attention de ne pas le confondre avec l' opérateur d'égalité
représenté en JAVA par le symbole == 3 !
I** La classe Sinus calcule et affi che sur l a sort i e standard
l e sinus d ' un en t i er l u sur l ' en trée s t andard *I Le dernier énoncé calcule et écrit le sinus de x grâce, respectivement, à la fonction mathé
import j ava . i o . * ; matique s i n et à la procédure p r i n t l n .
class S inus { Les affirmations sont dénotées sous forme de commentaires introduits par deux barres
public static void main ( S t r ing [ ] a r g s ) throws IOExcept i o n obliques //. Remarquez la différence de notation avec le premjer commentaire en tête de
{
programme. Le langage JAVA propose trois formes de commentaires. Nous distinguerons
int x ;
Il i l exi s t e u n ent i er à l i re s u r l ' en t rée s tandard
les commentaires destinés au programmeur de la classe et ceux destinés à l'utilisateur de la
x = S t d i nput . re ad l n i n t ( ) ;
classe.
Il un en t i er a été l u s ur l ' en trée s t andard, Les premiers débutent par // et s'achèvent à la fin de la ligne, ou peuvent être rédigés sur
Il placé en mémoire centrale et *
plusieurs lignes entre les délimiteurs / * et / . Ils décrivent en particulier les affirmations qui
Il l ' i den t i fi ca teur de variable x permet de le dés i gner démontrent la validité du programme.
S y s t em . out . println ( Math . s in ( x ) } ;
Il l a val eur du sinus de x est écri t e sur l a sort i e s t andard
Les seconds, destinés aux utilisateurs de la classe, sont appelés commentaires de docu
*
mentation. Ils apparaissent entre / * * et / et peuvent être traités automatiquement par un
}
Il fin classe Sinus outil, j avadoc, pour produire la documentation du programme au format HTML (Hyper
Text Markup Language).
Ce premier programme comporte un certain nombre de choses mystérieuses et qui le res
teront encore un peu en attendant la lecture des prochains chapitres.
Toutefois, sachez dès à présent, que la structuration d'un programme JAVA est faite autour 2.8 EXERCICES
des objets. Un programme JAVA est une collection de classes (voir plus loin le chapitre 7),
placée dans des fichiers de texte, qui décrit des objets marupulés lors de son exécution. Il doit Exercice 2.1. Modifiez le programme pour rendre l'utilisation de la variable x inutile.
posséder au moins une classe, ici S i nus, contenant la procédure main par laquelle débutera
l'exécution du programme. Ici, cette classe sera conservée dans un fichier qui porte le nom Exercice 2.2. En partant de l'antécédent {fact i !} , appliquez la règle de déduction de
=
de la classe suffixé par j ava, i.e. S i nus . j av a . Par convention, la première lettre de chaque l'énoncé d'affectation pour trouver le conséquent des deux instructions suivantes :
mot qui forme le nom de la classe est en majuscule. i � i+l
fact � fact * i
Ce premier programme comporte en tête un commentaire qui explique de façon concise
son rôle. C'est une bonne habitude de programmation que de mettre systématiquement une
telle information, qui pourra être complétée par le ou les noms des auteurs et la date de
création du programme.
-0 -0
0 0
c:
=i
Le corps de la procédure main, placé entre deux accolades, déclare en premier lieu la c:
=i
0 variable entière x. Les variables sont déclarées en tête de procédure sans mot-clé particulier 0
"<:t" "<:t"
,..-1 pour les introduire. Remarquez que le nom du type précède le nom de la variable. Si le mot-clé ,.-1
0 0
N
final précède la déclaration, alors il s'agit d'une définition de constante (notez qu'aucune
N
@ @
.......
.c:
constante n'est déclarée dans ce premier programme). Par exemple, si nous devions déclarer .......
..c:
O'l
la constante réelle pi, nous pourrions écrire : O'l
·c ·c
> >
Cl. Cl.
0 final double p i = 3 . 1415926; 0
u u
La constante prend une valeur lors de sa déclaration et ne pourra évidemment plus être
modifiée par la suite.
La lecture de l 'entier est faite grâce à la fonction readlnint 2, qui lit sur l'entrée stan
dard une suite de chiffres, sous forme de caractères, et renvoie la valeur du nombre entier
qu'elle représente. Cet entier est ensuite affecté à la variable x . Vous noterez que le sym-
3 . Ce choix du symbole mathématique d'égalité pour l'affectation est une aberration héritée du langage C
2. Cette fonction n'appartient pas à l'environnement standard de JAVA (voir la section 1 5.6 page 177). [ANS89].
24 Chapitre 3 • Types élémentaires
peuvent accéder à l' objet que dans sa totalité. pas défirue, alors que entmax + (1 x) appartient au domaine de défirution.
-
Le plus souvent, les types élémentaires sont prédéfinis par le langage, c'est-à-dire qu'ils
-0 -0
0
c: préexistent, et sont directement utilisables par le programmeur. Il s' agit, par exemple, des 0
c: >- Les types entiers de J AVA
=i =i
0 types entier, réel, booléen ou caractère. 0
"<:t" "<:t" Tout d'abord, notons qu' il n'existe pas un, mais quatre types entiers. Les types entiers
,..-1 ,..-1
0
N
Le programmeur peut également définir ses propres types élémentaires, en particulier pour 0
N
byte, short, int, long sont signés et représentés en complément à 2. Ils se distinguent
@ spécifier un domaine de valeur particulier. Certains langages de programmation offrent pour @ par leur cardinal. Plus précisément, les entiers du type byte sont représentés sur 8 bits, ceux
.......
..c: cela des constructeurs de types élémentaires . .......
..c: du type short sur 1 6 bits, ceux du type int sur 32 bits et ceux du type long sur 64 bits .
O'l O'l
·c ·c
>
Cl. Un langage est dit typé si les variables sont associées à un type particulier lors de leur dé >
Cl.
Pour chacun de ces quatre types, il existe deux constantes qui représentent l'entier mini
0 0
u claration. Pour ces langages, les compilateurs peuvent alors vérifier la cohérence des types des u mum et l 'entier maximum. Ces constantes sont données par la table 3 . 1 .
variables, et ainsi garantir une plus grande fiabilité des programmes construits. Au contraire, Les constantes entières en base 1 0 sont dénotées par une suite de chiffres compris entre
les variables des langages de programmation non typés peuvent désigner des objets de n'im O et 9 . Le langage permet également d'exprimer des valeurs en base 8 ou 1 6, en les faisant
porte quel type et les vérifications de cohérence de type sont reportées au moment de l' exé précéder, respectivement, par les préfixes O et O x .
cution du programme. La programmation avec ces langages est moins sûre, mais offre plus
de souplesse. Il exemples de con s t a n t es en t i ères
3 1 2 5 0 0 7 7 7 3 4 5 6 2 3 4 OxAC 1 2
Dans ce chapitre, nous présenterons les types élémentaires entier, réel, booléen et carac
tère, ainsi que les constructeurs de type énuméré et intervalle.
3.2 Le type réel 25 26 Chapitre 3 • Types élémentaires
type minimum maximum L'arithmétique sur les réels est inexacte. Chaque opération conduit à des résultats appro
byte Byte . MI N_VALUE Byt e . MAX_VALUE chés qui, répétée plusieurs fois, peut conduire à des résultats totalement faux. Les résultats
short Short . MIN_VALUE Short . MAX_VALUE dépendent en effet de la représentation du nombre réel sur l'ordinateur, de la précision avec
int I n t e ge r . MIN_VALUE I n t e ge r . MAX_VALUE laquelle il est obtenu ainsi que de la méthode utilisée pour les calculer.
long Long . MIN_VALUE Long . MAX_VALUE
Il existe plusieurs modes de représentation des réels. La plus courante est celle dite en
TABLE 3.1 Va leurs minimales et maximales des types entiers de JAVA. virgule flottante. Un nombre réel x est représenté par un triplet d'entiers (s, e, m ) , tel que
x = ( - 1 ) 5 x m x Be, avec s E {O, 1 } , -E < e < E et -M < m < M. La valeur de s
La table 3.2 montre les opérateurs arithmétiques et relationnels qui peuvent être appliqués donne le signe du réel, m s' appelle la mantisse, e l' exposant et B la base (le plus souvent 2, 8
sur les types entiers de JAVA. ou 16). E, M et B constituent les caractéristiques de la représentation choisie, et dépendent
opérateur fonction exemple de l'ordinateur. L'imprécision de la représentation provient des valeurs limites E et M. De
opérateurs arithmétiques plus, les langages de programmation raisonnent en termes de nombres en base 10, alors que
-
opposé -
45 ces nombres sont représentés dans des bases différentes.
+ addition 45 + 5
-
soustraction a -
4
Les opérateurs d' arithmétique réelle et de relation sont applicables sur les réels, mais il
* multiplication a * b faut tenir compte du fait qu'ils peuvent conduire à des résultats faux. En particulier, le test
/ division 5 / 45 de l 'égalité de deux nombres réels est à bannir. Comme pour le type entier, ces opérations ne
9-
0 modulo a 9- 4
0
sont pas des lois de composition internes.
opérateurs relationnels La représentation d'un nombre en virgule flottante n'est pas unique tant que l 'emplace
< inférieur a < b ment du délimiteur dans la mantisse n'est pas défini, puisqu'un décalage du délimiteur peut
<= inférieur ou égal a <= b
être compensé par une modification de l'exposant. Ainsi, 1 25.32 peut s'écrire 0. 12532 *
égal a b
103, 1 . 2532 * 102 ou encore 12.532 * 10 1 . Pour lever cette ambiguïté, on adopte générale
-- --
'= différent a != b
> supérieur a > b
ment une représentation normalisée 1 . Les réels sont tels que la valeur de l' exposant est ajustée
>= supérieur ou égal a >= b
pour que la mantisse ait le plus de chiffres significatifs possibles. Après chaque opération, le
résultat obtenu est normalisé.
TABLE 3.2 Opérateurs sur les types entiers. Les exemples suivants mettent en évidence l 'inexactitude des calculs réels. Pour simplifier,
on considère que les nombres réels sont représentés en base 1 0 et que la mantisse ne peut
La déclaration d'une variable entière est précédée du domaine de valeur particulier qu'elle
utiliser que quatre chiffres (M = 1000). Soient les réels x, y et z suivants :
peut prendre. Notez que plusieurs variables d'un même type peuvent être déclarées en même
temps. X = 9.900, y = 1 .000, Z = -0.999
byte unüct e ct , uneNot e ;
short nbMot s ; on veut calculer (x + y) + z et x + (y + z ) . Les erreurs de calculs viennent des ajustements de
-0 -0
0 int pop u l at i o n ; 0 représentation. Par exemple, lors d'une addition de deux entiers, on ajuste la représentation
c: c:
0
=i
long nb É t o i l e s , i n f i n i ; 0
=i du nombre qui a le plus petit exposant en valeur absolue de façon que les deux exposants
"<:t"
,..-1
"<:t"
,.-1
soient égaux. On note x la représentation informatique de x .
0 0
Considérons maintenant les trois réels x = 1 100, y = -5, z = 5.001. On désire calculer Ainsi, les constantes suivantes sont valides :
X X (fJ + z) et (X X fJ) + (X X z).
- 123 . 45 6 34 . 0 .0 12 . 5 6e 3 4 4E-5 4 5 . 67e2 4567
x = 1 100 10° '
y = -5000 10-3 z = 5001 10-3
'
La table 3.4 donne les opérateurs arithmétiques et relationnels applicables sur les types
X X fj = -5500 lOO réels de JAVA .
X X z = 5501 10°
opérateur fonction exemple
(x x y) + (x x z) = 1000 10-3 = 1 opérateurs arithmétiques
-
opposé - 4 5 . 5+5el2
y + z = 1000 10-5 + addition 4 5 . 5+5el2
X X (fJ + z) = 1 100 10-3 = 1.1 -
soustraction a -
4.3
* multiplication a * b
Là encore, seul le deuxième calcul est juste. Enfin, traitons la résolution d'une équation du
/ division 5 / 45
second degré avec les trois coefficients réels a = 1 , b = -200, c = 1 . Nous obtenons :
opérateurs relationnels
a = 1000 10-3 b = -2000 10-1 c = 1000 10-3
' '
< inférieur a < b
<= inférieur o u égal a <= b
égal a -- b
� = J4000 101 - 4000 10 -3 = 2000 10-1 --
'= différent a != b
Dans le calcul de �. la disparition de 4000 10-3 au cours de l'ajustement conduit au > supérieur a > b
calcul d'une racine fausse. On trouve x1 = 0 et x2 = 2000 10-1, alors que la racine x1 est >= supérieur o u égal a >= b
Comme pour les entiers, JAVA définit plusieurs types réels : float et double. Les réels
du type float sont en simple précision codés sur 32 bits, ceux du type double sont en 3.3 LE TYPE BOOLÉEN
double précision codés sur 64 bits. La représentation de ces réels suit la norme IEEE 754.
Le type booléen est un type fini qui représente u n ensemble composé de deux valeurs
"8
-0
Ces deux types possèdent chacun une valeur ntinimale et une valeur maximale données 0 logiques, vrai etfaux, sur lequel les opérations de disjonction (ou), de disjonction exclusive
c: c:
Notez qu'un seul bit est nécessaire pour la représentation d'une valeur booléenne. Mais, IS O/CEi 1 0646 4 . UNICODE propose trois représentations des caractères : UTF32, UTF l 6
le plus souvent, un booléen est codé sur un octet avec, par convention, 0 pour la valeur faux et UTF8, respectivement codées sur 32, 1 6 et 8 bits.
et 1 pour la valeur vrai. Aujourd'hui, la version 6.3 <l' UNICODE comprend plus de 1 09 000 caractères différents
qui permettent de représenter la majorité des langues et langages utilisés dans le monde.
� Le type booléen de J AVA Les 256 premiers caractères du jeu UNICODE sont ceux de la norme ISO 8859, les suivants
représentent entre autres des symboles de langages variés (dont le braille), des symboles
Le type boolean définit les deux valeurs true et fal s e . En plus des opérateurs de
mathématiques ou encore des symboles graphiques (e.g. dingbats). La description complète
négation ( ! ), de disjonction ( 1 ), de disjonction exclusive ( " ) et de conjonction (&), JAVA
du jeu UNICODE est accessible à l'adresse http : / / www . uni code . org.
propose les opérateurs de disjonction et de conjonction conditionnelles représentés par les
symboles 1 1 et & & . Le résultat de p 1 1 q est vrai si p est vrai quelle que soit la valeur de Dans tous les langages de programmation, il existe une relation d'ordre sur les caractères,
q qui, dans ce cas, n'est pas évaluée. De même, le résultat de p & & q est faux si p est faux qui fait apparaître une bijection entre le type caractère et l'intervalle d'entiers [O, ordmaxcar] ,
quelle que soit la valeur de q qui, dans ce cas, n'est pas évaluée. Nous verrons ultérieurement où ordmaxcar est l'ordinal (i.e. le numéro d'ordre) du dernier caractère du jeu de caractères.
que l'utilisation de ces opérateurs a une influence considérable sur le style de programmation. On peut donc appliquer les opérateurs relationnels sur les objets de type caractère.
La déclaration suivante est un exemple de déclaration de variables booléennes.
� Le type caractère de JAVA
boolean p r é sent , onCont i nue ;
Le type char utilise le jeu de caractères UNICODE. Les caractères sont codés sur 1 6 bits
(avec un mécanisme spécifique pour définir les caractères unicode d'ordinaux supérieurs à
65536). Les constantes de type caractère sont dénotées entre deux apostrophes. Ainsi, les
caractères ' a ' et ' ç ' représentent les lettres alphabétiques a et ç. Il est fondamental de com
3.4 LE TYPE CARACTÈRE
prendre la différence entre les notations ' 2 ' et 2 . La première représente un caractère et la
Chaque ordinateur possède son propre jeu de caractères. La plupart des ordinateurs actuels seconde un entier.
proposent plusieurs jeux de caractères différents et normalisés pour représenter des lettres et Certains caractères non imprimables possèdent une représentation particulière. Une partie
des chiffres de diverses langues, des symboles graphiques ou des caractères de contrôle. de ces caractères est donnée dans la table 3.6.
Caractère Notation
Par le passé, seuls deux jeux de caractères américains étaient vraiment disponibles. Le jeu
passage à la ligne \n
de caractères A s cn 2, codé sur 7 bits, ne permet de représenter que 1 28 caractères différents.
Le jeu EBCDIC 3 , spécifique à IBM, code les caractères sur 8 bits et inclut quelques lettres
tabulation \t
retour en arrière \r
étrangères, comme 13 ou ü. saut de page \f
Pour satisfaire les usagers non anglophones, la norme IS0-8859 propose plusieurs jeux de backslash \\
256 caractères codés sur 8 bits. Les 1 28 premiers caractères sont ceux du jeu ASCII et les 1 28 apostrophe \'
-0 suivants correspondent à des variantes nationales. La norme I SO 8859- 1 , appelée Latin- 1 , -0
0 0 TABLE 3.6 Caractères spéciaux.
c:
=i
correspond à la variante des pays de l'Europe de l' Ouest. Elle inclut des symboles graphiques c:
=i
0
ainsi que des caractères à signes diacritiques comme é, à, ç ou encore â, qui existent à la fois 0
"<:t"
,..-1
sous forme minuscule et majuscule (sauf 13 et y). Notez que la norme I SO 8859- 1 5 , appelée
"<:t"
,..-1 Il existe une relation d'ordre sur le type char. On peut donc appliquer les opérateurs de
0 0
N
aussi Latin-9, est identique à la norme ISO 8859- 1 , à l'exception de huit nouveaux caractères N relation <, < =, ==, ! =, > et >= sur des opérandes de type caractère.
@ @
....... dont le symbole de l'euro € . ....... Les caractères peuvent être dénotés par la valeur hexadécimale de leur ordinal, précédée
..c: ..c:
O'l O'l par la lettre u et le symbole \ . Par exemple, ' \u004 1 ' est le caractère d'ordinal 65, c'est-à-dire
·c
>
Mais, face à l ' internationalisation toujours croissante de l ' informatique, ces jeux de carac ·c
>
Cl. Cl. la lettre A. Cette notation particulière trouve tout son intérêt lorsqu'il s'agit de dénoter des
0 tères ne suffisent plus pour représenter tous les symboles des différentes langues du monde. 0
u u caractères graphiques. Par exemple, les caractères ' \ u 1 2cc' et '\u1 356' représentent les lettres
De plus, leurs différences sont un frein à la portabilité des logiciels et des données.
éthiopiennes � et 7·, les caractères ' \u2200' et ' \u2208' représentent les symboles mathéma
Depuis le début des années 1 990, le Consortium Unicode développe une norme UNI tiques \/ et E, et ' \u2708' est le symbole +. Notez que puisqu'en JAVA les caractères UNICODE
CODE pour définir un système de codage universel pour tous les systèmes d'écritures. UNI sont codés sur 1 6 bits, l ' intervalle valide va donc de ' \uOOOO' à '\uFFFF'.
CODE est un sur-ensemble de tous les jeux de caractères existants, en particulier de la norme
La déclaration suivante introduit trois nouvelles variables de type caractère :
2. ASCII est l'acronyme de American Standard Code for Information Interchange. Ce jeu de caractères est une char lett r e , marqu e , symbole;
norme ISO (International Organization for Standardization).
3. EBCDIC est l'acronyme de Extended Binwy Coded Decimal. 4. Cette norme I SO définit un jeu unjversel d e caractères. UNICODE et ISO/CEi 10646 sont étroitement liés.
3.5 Constructeurs de types simples 31 32 Chapitre 3 • Types élémentaires
Les caractères UNICODE peuvent être normalement utilisés dans la rédaction des pro 11 est important de noter que le type enum de JAVA n'est pas un type élémentaire (comme
grammes JAVA pour dénoter des noms de variables ou de fonctions. Afin d'accroître la li c'est le cas dans de nombreux langages de programmation) et que le langage ne définit pas
sibilité des programmes, il est fortement conseillé d'utiliser les caractères accentués, s'ils d'opérateurs sur les énumérations. Ce sont des objets 5 , au sens de la programmation objet,
sont nécessaires. Il est aussi possible d' utiliser toutes sortes de symboles, et la déclaration de issus de la classe j ava . l ang . Enum.
constante suivante est tout à fait valide :
final double n = 3 . 1415926; 3.5.2 Les types intervalles
Si l a saisie directe du symbole 7r n'est pas possible, i l sera toujours possible d'employer la Un type intervalle définit un intervalle de valeurs sur un type de base. Les types de base
notation hexadécimale. possibles sont les types élémentaires et la déclaration doit indiquer les bornes inférieure et
final double \ u 0 3 C O 3 . 1415926; supérieure de l ' intervalle. La forme générale d'une déclaration d'un intervalle est la suivante :
[binf , bsup]
Les bornes inférieure et supérieure sont des constantes de même type. Les opérations pos
3.5 CONSTRUCTEURS D E TYPES S I M PLES
sibles sur les intervalles sont celles admises sur le type de base. Les déclarations suivantes
Les types énumérés et intervalles permettent de construire des ensembles de valeurs parti définissent un type entier naturel et un type lettre alphabétique. L'exemple suivant montre la
culières. L'intérêt de ces types est de pouvoir spécifier précisément le domaine de définition déclaration de deux types intervalles.
des variables utilisées dans le programme. Certains langages de programmation proposent naturel [ 0 , entmax]
de tels constructeurs et permettent de nommer les types élémentaires construits. Dans cette l et t re s [ 'a', 'z'J
section, nous présentons succinctement une notation algorithmique pour définir des types
énumérés et intervalles qui nous serviront par la suite. Nous présentons également les types
énumérés de JAVA, introduits dans sa version 5.0. Les types intervalles n'existent pas en JAVA.
3.6 EXERCICES
3.5.1 Les types énumérés
Exercice 3.1. Parmi les notations de constantes JAVA suivantes, indiquez celles qui sont
Une façon simple de construire un type est d'énumérer les éléments qui le composent. On valides, ainsi que le type des nombres :
indique le nom du type suivi, entre accolades, des valeurs de l 'ensemble à construire. Ces
valeurs sont des noms de constantes ou des constantes prises dans un même type élémentaire. 0 . 31 +273 . 3 0 . 00 5e+3 OxlO
L'exemple suivant montre la déclaration de trois types énumérés. 010 . 38 9 15 Ox5e-4
33 . 75 1 . 5 +2 3 , 250 . El
couleurs = vert , b l eu , gri s , rouge, j aune
-0 -0 1234 3E5 08 l O e-4
0 0
c:
nbpremi e r s ( 1, 3, 5, 7, 11, 13 ) c:
0Xla2 0037 l e2 7 6 8 O x lA2
=i
voye l l e s = 'a ' , 'e ' , ' i ', 'o ' , ' u ' , 'y' )
=i
0 0
,.-1
"<:t" "<:t"
,.-1 Exercice 3.2. Indiquez le type de chacune des constantes JAVA données ci-dessous :
� Il existe une relation d'ordre sur les types énumérés et, d'une façon générale, tous les 0
N
@ opérateurs relationnels sont applicables sur les types énumérés . @ 100 true ra I
2
....... .......
..c: ..c OxlO . 23 "nom" '2,
O'l O'l
"a" ' \n , 2
·c
>- Les types é n u m é rés de J AVA ·c ' \ u0041 '
11 11
> >
Cl. Cl.
0 0
u
Les énumérations de JAVA ne permettent d'énumérer que des constantes représentées par u Exercice 3.3. Est-ce que la disjonction et la conjonction sont des lois commutatives et as
des noms, comme dans la déclaration précédente du type c o u l e u r s . Ce type, introduit par sociatives ? En d'autres termes, les égalités suivantes sont-elles vérifiées ?
le mot-clé enum, est défini en JAVA par :
enum C o u l e u r s { vert , b l e u , gri s , rouge , j aune (p ou q) ou r = p ou (q ou r)
(p et q) et r = p et (q et r)
Le fragment de code suivant donne la déclaration d'une variable de type Couleurs et son
affectation à la couleur rouge : 5. cf chapitre 7.
Cou l e u r s c ;
c = Couleurs . rouge ;
3.6 Exercices 33
Exercice 3.4. Est-ce que la disjonction est distributive par rapport à la conjonction ? Ré
ciproquement, la conjonction est-elle distributive par rapport à la disjonction ? Vérifiez les
égalités suivantes :
(p ou q) et r = (p et q) ou ( q et r)
(p et q) ou r = (p ou q) et ( q ou r)
Exercice 3.5. On dit qu'il y a dépassement de capacité, lorsque les opérations arithmé
tiques produisent des résultats en dehors de leur ensemble de définition. Certains langages de
programmation signalent les dépassements de capacité, d' autres pas. Vérifiez expérimentale
ment l'attitude de JAVA en cas de dépassement de capacité pour des opérandes de type entier
et rée].
Exercice 3.6. Testez le programme JAVA qui écrit sur la sortie standard les résultats des
calculs suivants, et constatez la différence de traitement des réels et des entiers :
0 . 0/0 . 0
1 . 0 /0 . 0
0/0
1/0
Exercice 3.7. Écrivez le programme qui écrit sur la sortie standard la valeur de Mat h . P I
sous la forme 7r = .....
Exercice 3.8. É crivez le programme qui déclare le type énuméré Couleurs de l a page 3 1
et qui écrit sur l a sortie standard la valeur de l a variable c initialisée à Couleurs . j aune.
Exercice 3.9. Complétez votre précédent programme afin d'écrire sur la sortie standard
l'ordinal de la valeur de c en exécutant c . o rdi n a l ( ) . Quel est 1' ordinal de la valeur vert ?
-0
0
c:
=i
0
"<:t"
,.-1
0
N
@
....... .µ
.c: ..c
O'l Ol
·c ï:::
> >-
Cl. 0.
0 0
u u
36 Chapitre 4 • Expressions
pose une écriture des expressions de ]a forme x y + ou x y x z +. Avec cette notation, les
parenthèses sont également inutiles puisque (x + y)/ z s'écrit x y + z /.
Dans ]a suite de ce chapitre, nous n'utiliserons que ]a notation infixée dans ]a mesure où
e11e est ]a plus employée par ]es langages de programmation.
4. 1 ÉVALUATION
Chapitre 4 Le but d'une expression est de calculer, lors de son évaluation, un résultat. En général,
l 'évaluation d'une expression produit un résultat unique. Mais pour certains langages, comme
ICON [GG96], l'évaluation d'une expression peut donner aucun, un ou plusieurs résultats.
Expressions Le résultat d'une expression est déterminé par ] 'ordre d'évaluation des formules simples
qui la composent. Cet ordre d'évaluation, pour une même forme syntaxique, n'est pas for
cément le même dans tous les langages. En supposant que les opérandes sont tous de même
type, nous considérerons trois cas : la composition d'un même opérateur, la composition
d'opérateurs différents et l'utilisation de parenthéseurs.
�
c:
0
=i Dans la plupart des langages, la notation utilisée suit la notation algébrique classique. 4. 1 .2 Composition de plusieurs opérateurs différents
Cette notation est dite infixée, c'est-à-dire que les opérandes se situent de part et d' autre de
,.-1
"<:t" "<:t"
,..-1
0
N
l'opérateur, comme dans x + y ou encore x x y + z. Elle nécessite des parenthèses pour � L'ordre de gauche à droite n'est pas toujours le plus naturel lorsque les opérateurs concer-
@ exprimer par exemple des règles de priorité, x + y x z est différent de (x + y) x z. @ nés sont différents. Afin de respecter les habitudes de notation algébrique, ]a plupart des lan-
.:ë gages de programmation définissent entre les opérateurs des règles de priorité susceptibles de
.......
..c:
O'l Certains langages, comme LISP, utilisent la notation polonaise 1 , également appelée pré O'l
·c
> fixée, qui place l'opérateur systématiquement avant ses opérandes. On écrit par exemple + x y -� remettre en cause l'ordre d'évaluation de gauche à droite. Par exemple, x + y x z correspond
Cl.
0 g- à l'addition de x et du produit y x z, et non au produit de x + y avec z .
u ou x + x y z . Les parenthèses sont inutiles, + x x y z est différent de x + x y z. Remar u
quez que l' appel d'une fonction ou d'une procédure est à considérer comme une notation
préfixée, où l'opérateur est le nom de la fonction ou de la procédure, et ses opérandes sont Règles de priorité en JAVA
les paramètres effectifs.
Les règles de priorité varient considérablement d'un langage de programmation à l'autre,
La notation polonaise inverse, appelée aussi notation postfixée, place l'opérateur à la suite et i l est bien difficile de dégager des règles communes. La table 4. 1 donne les règles de
de ses opérandes. Les possesseurs de calculettes HP connaissent bien cette notation qui im- priorité du langage JAVA, en allant du moins prioritaire au plus prioritaire, des opérateurs vus
au chapitre précédent. À niveau de priorité égal, l'évaluation se fait de gauche à droite, sauf
l . Ainsi appelée car son invention est due au mathématicien polonais JAN LUKASIEWICW. pour 1' affectation.
4.2 Type d'une expression 37 38 Chapitre 4 • Expressions
@ des objets de types différents, et dans ce cas le parenthésage peut servir à délimiter sans @ Les conversions explicites, appelées cast en anglais, sont faites à 1' aide d'un opérateur de
....... ......
..c:
O'l ambiguïté les opérandes de même type dont la composition forme un résultat d'un autre type, ..c:
O'l conversion. Sa dénotation consiste à placer entre parenthèses, devant la valeur à convertir,
·c ·c
>
Cl.
lui-même opérande d'un autre opérateur dans la même expression. Par exemple, l' expression >
Cl. le type dans lequel elle doit être convertie. Dans l'exemple suivant, le réel 1 .4 est converti
u
0
booléenne suivante : u
0
explicitement en un entier. Le résultat de la conversion ( int ) 1 . 4 est l'entier 1 .
On désire écrire un programme qui lit sur l'entrée standard une valeur représentant une
est formée par les deux opérandes booléens de l'opérateur de conjonction eux-mêmes com somme d'argent et qui calcule et affiche le nombre de billets de 500, 200, 1 00, 50 et 1 0 euros
posés à partir de deux opérandes numériques reliés par des opérateurs de relation à résultat qu'elle représente.
booléen.
4.4 Un exemple 39 40 Chapitre 4 • Expressions
> L'a lgorithme I** La classe SommedArgent l i t sur l ' en t rée s t andard une valeur
* représentant une somme d ' argent mul t iple de 1 0, puis cal cu l e
L'algorithme commence par lire sur l'entrée standard l' entier qui représente la somme * et affiche le nombre de billets de 5 0 0 , 2 0 0 , 1 0 0, 50 et 1 0 euros
d'argent et affecte la valeur à une variable c ap i t a l . Pour obtenir la décomposition en * qu ' el l e représen t e
nombre de billets et de pièces de la somme d'argent, on procède par des divisions entières *I
successives en conservant chaque fois le reste. La preuve de la validité de l 'algorithme s'ap import j ava . i o . * ;
puie sur la définition de la division euclidienne : class S ommedArgent
public static void ma i n ( St r i n g [ ] args ) throws IOExcept i o n
Va, b E N, b > 0 {
Cl. Cl.
0 représen t en t l a valeur de capi tal } 0 autres opérateurs dont nous ne parlerons pas pour l'instant. L'intérêt principal de ces
u u
opérateurs est de n'évaluer qu'une seule fois l'opérande gauche.
Les affectations de JAVA ont la particularité 2 d'être des expressions, c'est-à-dire de fournir
>- Le prog ramme en JAVA un résultat, en l'occurrence la valeur de l' opérande gauche après affectation. Nos deux pre
miers programmes JAVA n'utilisent pas cette caractéristique, mais nous verrons un peu plus
À partir de l'algorithme précédent, et des connaissances JAVA déjà acquises, la rédaction tard qu'elle influence la façon de programmer les algorithmes.
du programme ne pose pas de réelles difficultés.
2. On la trouve également dans les langages C et C++, et plus généralement dans les langages d'expression.
Dans ces derniers, toute instruction fournit un résultat.
4.5 Exercices 41 42 Chapitre 4 • Expressions
Les affirmations sous forme de commentaires dans l'algorithme qui montrent sa validité Exercice 4.4. Écrivez en JAVA les trois expressions suivantes :
ont été remplacées par des énoncés a s sert. L'expression booléenne qui suit le mot-clé 1 1
as sert est évaluée à l'exécution du programme et provoque une erreur 3 si l'expression a - b + Jb2 - 4ac a+ b
-
presente une température en degrés Celcius et qui affiche sa conversion en degrés Fahrenheit.
char c = a ; char c = O x 4 1 ; char c I \ u0041 i
La relation qui lie ces deux unités est : F = (9 * C)/5 + 32. La valeur convertie est arrondie
I
final double 7r ;
7r = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 1 ;
final float e = 2 . 7 1 8 2 8 1 8 2 8 4 5 9 0 4 5 1 f ;
7r = e ;
int i , j , k ;
-0 -0
0
c:
double x , y , z ; 0
c:
0
=i
char c ; 0
=i
"<:t"
,..-1
boolean b ; "<:t"
,.-1
0 0
N N
@ @
.......
..c:
indiquez l e type de chacune des expressions suivantes : ......
..c:
O'l O'l
·c ·c
> >
Cl. Cl.
u
0 X 2 i j i j
u
0
X + 2.0 X + 2 i + 2 X + i
X / 2 X / 2.0 i / 2 i / 2.0
9-
X < y i 0 j + y i / j + y i > j > k
i && b i --
j && b i > j && k > j X + y * i
i = c X = ( int ) y c = ( char) ( int ) c + 1) i++
3. Plus précisément une exception, cf l e chapitre 1 4 . D'autre part, l'option - e a doit être fournie à l' interprète
JAVA pour que les assertions soient vérifiées.
44 Chapitre 5 • Énoncés structurés
Règ l e de déduction
Les parenthéseurs sont représentés par les accolades ouvrantes et fermantes. La plupart
des langages de programmation utilise un séparateur entre les énoncés, qui doit être considéré
comme un opérateur de séquentialité. En JAVA, il n'y a pas à proprement parlé de séparateur
d'énoncé. Toutefois, un point-virgule est nécessaire pour terminer un énoncé simple.
Les actions que nous avons étudiées jusqu'à présent sont des actions élémentaires. Une
action structurée est formée à partir d'autres actions, qui peuvent être elles-mêmes élémen 5.2 É NONCÉS CON D ITION N E LS
taires ou structurées. Dans ce chapitre, nous présenterons deux premières actions structurées,
l ' énoncé composé et l'énoncé conditionnel, et pour chacune d'entre elles la règle de déduc Les actions qui forment les programmes que nous avons écrits jusqu'à présent sont exé
tion qui permet d'en vérifier la validité. cutées systématiquement une fois. Les langages de programmation proposent des énoncés
conditionnels qui permettent d'exécuter ou non une action selon une décision prise en fonc
tion d'un choix. Le critère de choix est en général la valeur d'un objet d'un type élémentaire
discret.
5.1 É NONCÉ COM POSÉ
-0
0
Comme pour les expressions, il est possible de parenthéser une suite d'actions. L'énoncé -0
0 5.2.1 Énoncé choix
c: c:
=i composé groupe des actions qui sont ensuite considérées comme une seule action. Dans notre =i
0 0
notation algorithmique, les énoncés à composer seront placés entre les deux parenthéseurs Dans la vie courante, nous avons quotidiennement des décisions à prendre, souvent
"<:t" "<:t"
,..-1 ,..-1
0 début et fin. Par exemple, la composition de trois énoncés E 1 , E2, E3 s'écrit de la façon 0 simples, et parfois difficiles. Imaginons un automobiliste abordant une intersection routière
N N
suivante : contrôlée par un feu de signalisation. Respectueux du code de la route, il sait que si le feu est
@ @
.......
..c:
.......
..c:
rouge, il doit s'arrêter ; si le feu est vert, il peut passer ; si le feu est orange, il doit s'arrêter si
O'l O'l
·c ·c cela est possible, sinon il passe.
> >
Cl. Cl.
0 0 D'un point de vue informatique, il est possible de modéliser le comportement de 1' au
u
Les trois énoncés sont exécutés de façon séquentielle, c'est-à-dire successivement les uns u
tomobiliste à l'aide d'un énoncé choix. Le critère de choix est la couleur du feu dont le
après les autres. L'énoncé E2 ne pourra être traité qu'une fois l'exécution de l' énoncé E1 domaine de définition est l'ensemble formé par les trois couleurs rouge, vert et orange. À
achevée. De même, l'exécution de E3 ne commence qu'après la fin de celle de E 2 . chacune de ces valeurs est associée une certaine action. Nous pouvons écrire ce choix de la
La notion d'exécution séquentielle est à mettre en opposition avec celle d'exécution pa façon suivante :
rallèle, qui permettrait le traitement simultané des trois énoncés. La vérification de la validité choix couleur du feu parmi
des algorithmes parallèles, surtout si les actions s' exécutent de façon synchrone, est beau rouge s ' arrêter
coup plus complexe et difficile à mettre en œuvre que celle des algorithmes séquentiels. Les vert passer
algorithmes présentés dans cet ouvrage sont exclusivement séquentiels. orange s ' arrêter s i p o s s i b l e , s i non p a s s e r
finchoix
5.2 Énoncés conditionnels 45 46 Chapitre 5 • Énoncés structurés
D'une façon plus forme11e, l'énoncé choix précise l'expression dont l'évaluation fournira 5.2.2 Énoncé si
la valeur de 1' objet discriminatoire, puis i1 donne la liste des actions possibles, chacune étant
précédée de la valeur correspondante. L'énoncé choix s'écrit : Lorsque l'énoncé choix est gouverné par la valeur d'un prédicat binaire, c' est-à-dire une
expression booléenne, comme dans la phrase suivante :
choix exp r parmi
val1 Ei si mon salaire augmente alors je reste sinon je change d'entreprise
val2 E2
on est dans un cas particulier de l' énoncé choix, appelé l 'énoncé s i . Au lieu d'écrire :
va l n En choix mon s a l a i r e augmente parmi
finchoix vrai je res t e
faux : je change d ' en t repri se
L'expression est évaluée et seul l'énoncé qui correspond au résultat obtenu est exécuté. finchoix
Que se passe-t-il si l'évaluation de l' expression renvoie une valeur non définie dans l'énoncé ?
En général, plutôt que de signaler une erreur, les langages choisissent de n'exécuter aucune on préférera la formule suivante, plus proche du langage courant :
action. si mon salaire a u gmen t e
alors je rest e sinon j e change d ' ent repri se
Règ l e de déduction finsi
En pratique, { Q } peut être l'union de conséquents { Qk } des énoncés Ek. Notez que 1e L'énoncé E 1 est exécuté si le prédicat booléen B est vrai, sinon ce sera l'énoncé E 2 qui le
conséquent doit être vérifié, même si aucun énoncé Ek n'a été exécuté. sera.
La notion de choix est mise en œuvre grâce à l'énoncé switch. L'expression, dénotée La règle de déduction de l'énoncé si est donnée ci-dessous :
entre parenthèses, doit rendre une valeur d'un type discret, un type entier, booléen, caractère si {P et B} � { Q i } et {P et non B} � {Q2}
ou énuméré, ou encore depuis JAVA 7, une chaîne de caractères. Chaque valeur de la liste des a l o r s { P } énoncé - s i {Q}
valeurs possibles est introduite par le mot-clé case, et il existe une valeur spéciale, appe
L'exécution de l'énoncé E 1 ou celle de E2 doit conduire au conséquent { Q } . Notez que ce
-0
0 lée default. À cette dernière, il est possible d' associer un ou plusieurs énoncés, qui sont -0
0
c: c: dernier peut être l'union de deux conséquents particuliers { Q1 } et { Q2 } des énoncés E1 et
=i exécutés lorsque l'expression renvoie une valeur qui ne fait pas partie de la liste des valeurs =i
0 0 E2 .
"<:t"
,.-1
énumérées. De plus, le programmeur doit explicitement indiquer, à l'aide de 1 ' instruction "<:t"
,..-1
0
N
break, la fin de l'énoncé sélectionné et l' achèvement de l'énoncé switch. On peut regret 0
N
>- Forme rédu ite
@ ter que le mécanisme de terminaison de l'énoncé switch ne soit pas automatique, comme Je @
....... .......
..c:
O'l
proposent d' autres langages. Avec le type énuméré : ..c:
O'l 11 est fréquent que l'action à effectuer dans le cas où le prédicat booléen est faux soit vide.
·c ·c
> > « »
La partie sinon est alors omise et on obtient une forme réduite de l'énoncé si. D'une
Cl.
0
enum Couleur { vert , orange , rouge } ; Cl.
0
u u façon générale, cette forme s'écrit :
l'exemple précédent s'écrit en JAVA comme suit : si B alors E finsi
switch ( cou l e u rDuF eu ) { Par exemple, pour obtenir la valeur absolue d'un entier, nous écrirons l'énoncé suivant :
case rouge : s ' arrêt er; break;
case vert : passer; break; {x est un e n t i er quelconque}
case orange : s ' arrêter si possible, sinon passer; break; si x < 0 alors x +--- -x f insi
{x � 0)
5.3 Résolution d'une équation du second degré 47 48 Chapitre 5 • Énoncés structurés
nulle. Auquel cas, la seconde l'est aussi. Enfin, dans le cas réel, les deux parties ima
Avec l'énoncé conditionnel, il nous est désormais possible d'écrire des programmes plus ginaires sont nulles.
conséquents. La programmation de la résolution d'une équation du second degré a ceci d'in Les opérations de comparaison sont elles aussi dangereuses, en particulier l'opération
téressant qu'elle est d' une taille suffisamment importante (mais pas trop) pour mettre en d'égalité. La comparaison d'un nombre avec zéro devra se faire à un epsilon près. En
évidence, d'une part, la construction progressive d'un algorithme, et, d'autre part, l'influence tenant compte de tout ce qui vient d'être écrit, l' algorithme du calcul des racines réelles
de l'inexactitude de l' arithmétique réelle sur l' algorithme. s'écrit de la façon suivante :
Posons le problème. On désire écrire un programme qui calcule les racines d' une équa
{ ca l c u l des racines rée l l e s }
tion non dégénérée du second degré. Formalisons un peu cet énoncé : soient a, b et c, trois
s i b > O alors rl +- - (b+.y'K) / ( 2 X a )
coefficients réels d' une équation du second degré avec a=1= O , calculer les racines r 1 +i x i 1 et
r2+ix i 2 solutions de l'équation. sinon rl +- ( .y'K-b) / ( 2 X a )
finsi
La suite d'actions qui assure ce travail peut être réduite à une action élémentaire, qui a {rl est la racine la p l u s grande en val e ur abs o l u e }
-0
0 pour données les trois coefficients a, b et c et pour résultats les parties réelles ( r 1 et r 2) et -0
0 si 1 rl 1 < c alors r2 +- 0
c: c:
0
=i imaginaires ( i 1 et i 2 ) des deux racines. Ce que nous écrivons : 0
=i sinon r2 +- cl (axrl)
"<:t" "<:t" finsi
,..-1 {An técédent : a=l:O, b et c réel s, coeffi cien t s de ,.-1
0 0 il +- 0
N l ' équa t i on du second degré, ax2 +bx+c} N
@ @ i2 +- 0
.......
calculer les racines de l ' équat ion ......
{ (x-rl ) (x-r2) =0 }
=
..c: ..c:
O'l { Conséquen t : (x- (rl +i x i l ) ) (x- (r2+i x i2) ) 0) O'l
·c ·c
> >
Cl. Cl. - Le calcul des racines complexes ne pose pas de problème particulier. Les racines com
0 S'il existe une procédure prédéfinie qui calcule les racines et qui respecte le conséquent 0
u u plexes sont données par les expressions suivantes :
final, alors il suffit de l'appeler et notre travail est terminé. Dans le cas contraire, nous devons
procéder au calcul. Un premier niveau de réflexion peut être le suivant. Regardons la valeur rl +- r2 +- -b/ ( 2 X a )
du discriminant �. Si � � 0, les racines sont réelles, sinon elles sont complexes. Cela se i l +- �/ ( 2 X a )
traduit par l' algorithme : i2 +- -il
Algorithme Équat i on du second degré
{An técéden t : a=l:O, b et c réels , coeffi cien t s de En rassemblant les différentes parties, l'algorithme complet de résolution d'une équation
l ' équa t i on du second degré, ax2 +bx+c} du second degré non dégénérée est le suivant :
calculer le di scriminant .6.
5.3 Résolution d'une équation du second degré 49 50 Chapitre 5 • Énoncés structurés
{ (x-rl ) (x-r2) = 0 }
sinon { calcul des racines complexe s } //fin classe Éq2Degré
rl +-- r2 +-- -b/ ( 2 X a ) Les noms de constantes et de variables é et � correspondent aux caractères UNICODE
il +-- �/ ( 2 X a ) \u O 3b5 et \ u O 3 9 4. Remarquez que ce programme emploie une nouvelle construction du
i2 +-- -il langage JAVA , l'expression conditionnelle (le seul opérateur ternaire du langage). Celle-ci a
finsi
la forme suivante :
{ (x- (rl +i x i l ) ) (x- (r2+i x i2) ) 0)
expl ? exp2 : exp3
L'écriture en JAVA du programme est maintenant aisée, il s'agit d'une simple transcription L'expression booléenne expl est évaluée. Si le résultat est vrai, le résultat de l 'expression
de l'algorithme précédent. Insistons encore sur le fait que la tâche la plus difficile, lors de la conditionnelle est le résultat de l'évaluation de l'expression exp2 ; sinon le résultat est celui
-0 -0
0
c: construction d'un programme, est la conception de l'algorithme et non pas son écriture dans 0
c: de l 'évaluation de exp3. Notez que le calcul de r2 aurait tout aussi bien pu s'écrire :
=i =i
0 un langage particulier. 0
if (Math . abs ( r l ) < c ) ) r2 = O ;
"<:t" "<:t"
=
r 2 c / ( a * rl ) ;
,..-1 ,.-1
else
0
N I** La classe Éq2Degré rés o u t une équa t i on du second degré 0
N
Exercice 5.3. Écrivez un programme JAVA qui lit sur l 'entrée standard trois entiers, et qui
écrit sur la sortie standard leur médiane. Ce calcul nécessite, au plus, 2 ou 3 tests.
Exercice 5.4. On possède un compte bancaire avec un solde positif. On veut écrire un
programme qui lit une opération représentée par un caractère, d pour dépôt ou 'r' pour
' '
retrait, ainsi qu'un entier positif qui indique la somme à transférer. Dans le cas d'un retrait, la
somme à transférer devra être nécessairement inférieure au solde. Si l'opération a pu aboutir,
on écrit le nouveau solde.
Exercice 5.5. Écrivez un programme qui lit trois entiers et vérifie s'ils représentent une date
valide Gour, mois, année). On rappelle qu'une année bissextile est une année divisible par 4
mais pas par 100, ou bien elle est divisible par 400. Par exemple, les trois entiers (24, 3, 2014)
representent une date valide, et pas (29, 2, 1900) .
Exercice 5.6. Écrivez un programme qui lit deux entiers et une opération arithmétique sous
forme d'un caractère : '+', ' - ' ' x ' ' / ' , et qui calcule et affiche le résultat de l' opération sur les
deux entiers. Si l'opérateur n'est pas valide, vous écrirez un message d'erreur, non pas sur la
sortie standard S y st em . out, mais sur la sortie d' erreur standard S y s t em . e r r spécifique
ment dédiée aux messages d'erreurs.
Exercice 5.7. Écrivez un programme qui calcule les deux racines réelles d'une équation
du second degré, à partir des formules mathématiques classiques, sans tenir compte de l'in
exactitude de 1' arithmétique réelle. Cherchez des valeurs pour les coefficients a, b et c qui
montrent que la méthode présentée dans ce chapitre fournit des résultats plus satisfaisants.
-0
0
c:
=i
0
"<:t'
,..-1
0
N
@
.._, .µ
..c: ..c
O'l Ol
·c ï:::
> >-
Cl. 0.
0 0
u u
54 Chapitre 6 • Procédures et fonctions
Les routines ont un rôle très important dans la structuration et la localisation, le paramé
trage et la lisibilité des programmes :
- Bien plus qu'une façon d'abréger le texte du programme, c'est un véritable outil de
structuration des programmes en composants fermés et cohérents. Nous avons vu dans
l ' introduction que la programmation descendante par raffinements successifs divisait
le problème en sous-parties cohérentes. Les routines seront naturellement les outils
adaptés à la description de ces sous-parties. Une suite d'énoncés pourra être déclarée
comme routine même si celle-ci n'est exécutée qu'une seule fois.
- Il arrive fréquemment que, pour des besoins de calculs intermédiaires, une suite
Chapitre 6 d'énoncés ait besoin de définir des variables (ou plus généralement des objets) qui
sont sans signification en dehors de cette suite, comme la variable 6. dans le calcul des
racines d'une équation du second degré. Les procédures ou fonctions seront des unités
textuelles permettant de définir des objets locaux dont le domaine de validité sera bien
Procédures et fonctions délimité.
- Certaines suites d'énoncés ont de fortes ressemblances, mais ne diffèrent que par la
valeur de certains identificateurs ou expressions. On aimerait que la suite d'énoncés
qui calcule les racines de l 'équation du second degré donnée au chapitre précédent
puisse faire ce calcul avec des coefficients chaque fois différents. Le mécanisme de
paramétrage d'une fonction ou d'une procédure nous permettra de considérer la suite
d'énoncés comme « un schéma de calcul abstrait 1 » dont les paramètres représente
ront des valeurs particulières à chaque exécution particulière de la suite d'énoncés.
Dans une approche de la programmation organisée autour des actions, les programmes - Une conséquence de l'effet de structuration des programmes à l'aide des routines est
sont d' abord structurés à l'aide de procédures et de fonctions. À l' instar de C ou PASCAL, l'augmentation de la lisibilité du code. De plus, les procédures et fonctions améliore
les langages qui suivent cette approche sont dits procéduraux. Nous avons vu précédemment ront la documentation des programmes.
comment utiliser une procédure ou une fonction prédéfinie ; dans ce chapitre, nous étudierons
comment les construire et le mécanisme de transmission des paramètres lorsqu' elles sont
appelées.
6.2 DÉCLARATION D'U N E ROUTI N E
Le rôle de l a déclaration d'une routine est de lier un nom unique à une suite d'énoncés sur
6.1 I NTÉRÊT des objets formels ne prenant des valeurs effectives qu'au moment de 1' appel de cette rou
-0
0
-0
0 tine. Le nom est un identificateur de procédure ou de fonction. Cette déclaration est toujours
c: c:
0
=i
Il arrive fréquemment qu'une même suite d'énoncés doive être exécutée en plusieurs points 0
=i formée d'un en-tête et d'un corps.
"<:t"
,..-1
du programme. Une procédure ou une fonction permet d'associer un nom à une suite d'énon "<:t'
,.-1
0 0
N cés, et d'utiliser ce nom comme abréviation chaque fois que la suite apparaît dans le pro N > L' en-tête
@ gramme. Bien souvent, dans la terminologie des langages procéduraux, le terme sous-pro @
....... .......
L' en-tête de la routine, appelé aussi signature ou encore prototype, spécifie :
..c:
O'l gramme est utilisé pour désigner indifféremment une procédure ou une fonction. Par la suite, ..c:
O'l
·c
> nous lui préférerons celui, plus récent, de routine.
·c
> le nom de la routine ;
Cl. Cl.
0 0 - les paramètres formels et leur type ;
u
L'association d'un nom à la suite d'énoncés se fait par une déclaration de routine. L'utili u
- le type du résultat dans le cas d'une fonction.
sation du nom à la place de la suite d'énoncés s'appelle un appel de procédure ou de fonction.
La déclaration d'une procédure prend la forme suivante :
Si la plupart des langages de programmation possède la notion de procédures et de fonc
{An t é cédent :
une affirma t i on }
tions, c'est bien parce qu'elle est un outil fondamental de « l 'art de la programmation dont
la maîtrise influe de façon décisive sur le style et la qualité du travail du programmeur »
{ Conséquent : une affirma t i o n }
{Rôle : une a ffi rma t i on donnant l e rôle de l a procédure}
[Wir75].
procédure NomP r o c ( [ <l i s t e de paramèt res formel s > ] )
1 . Ibidem.
6.3 Appel d'une routine 55 56 Chapitre 6 • Procédures et fonctions
Notez que les crochets indiquent que ]a liste des paramètres formels est facultative. Tous { appel de l a procédure Éq2degré}
1es en-têtes de procédures doivent contenir des affirmations décrivant 1eur antécédent, 1eur Éq2degré ( u , l , v+ l , r l l , i gl , r l 2 , ig2 )
conséquent ou leur rôle. L'en-tête correspondant à la déclaration de la procédure qui calcule { deux appel s de l a fonc t i on rac2}
les racines d'une équation du second degré peut être : écrire ( rac2 ( 5 ) , rac2 (tangente ) )
{An t é cédent a#O, b et c rée l s, coeffi ci en t s de
l ' équ a t i on du second degré, ax2 +bx+c}
{ Conséquen t (x- (rl +i x i l ) ) (x- (r2+i x i 2 ) ) = 0 )
{Rôle cal cule les ra cines d' une équa t i on du second degré}
6.4 TRAN S M ISSION D E S PARAM ÈTRES
procédure Éq2degré ( données a, b, c : réel
résultats r l , il, r2 , i2 : rée l )
Un paramètre formel est un nom sous lequel un paramètre d' une routine est connu à l'in
La procédure s' appelle Éq2degré, possède sept paramètres formels a , b , c, qui sont les térieur de celui-ci lors de sa déclaration. Un paramètre effectif est l'entité fournie au moment
données de la procédure, et r 1 , i 1 , r 2 et i 2 , qui sont les résultats qu'elle calcule. Tous ces de l'appel de la routine, sous la forme d'un nom ou d'une expression.
paramètres sont de type réel.
Nous distinguerons deux types de paramètres formels : les données et les résultats. Les
Une fonction est une routine qui représente une valeur résultat qui peut intervenir dans une
paramètres données fournissent les valeurs à partir desquelles les énoncés du corps de la
expression. La déclaration d' une fonction précise en plus le type du résultat renvoyé :
routine effectueront leur calcul. Les paramètres résultats donnent les valeurs calculées par
{An t é cédent une affirma t i on } la procédure. Une procédure peut avoir un nombre quelconque de paramètres données ou
{ Conséquent une affirma t i o n } résultats. En revanche, une fonction, puisqu'elle renvoie un résultat unique, n' aura que des
{Rôle une a ffirma t i on donnant le rôle de l a fon c t i o n } paramètres données. Dans bien des langages de programmation, rien n' interdit de déclarer
fonction NomF o n c ( [ <li s t e de paramètres formel s > ] ) : type - r é s u l t at dans l'en-tête d'une fonction des paramètres résultats, mais nous considérerons que c'est une
faute de programmation.
L'en-tête suivant déclare une fonction qui retourne la racine carrée d'un entier naturel :
{An t é cédent x � 0) Le remplacement des paramètres formels par les paramètres effectifs au moment de 1 'ap
{ Conséquen t : rac2 y'xJ=
pel de la routine se fait selon des règles strictes de transmission des paramètres. Pour de
{Rôle : cal cu l e la ra cine carrée de l ' en tier n a t urel x } nombreux langages de programmation, le nombre de paramètres effectifs doit être identique
fonction rac2 ( donnée x : nature l ) : réel à celui des paramètres formels, et la correspondance entre les paramètres formels et effectifs
se fait sur la position. De plus, ils doivent être de type compatible (e.g. un paramètre effectif
de la fonction rac2 ne pourrait pas être de type booléen). Nous utiliserons cette convention.
� Le corps
Le corps de ]a routine contient la suite d'énoncés. Nous le dé1imiterons par ]es mots-clés {An t é cédent J
@ @
....... .......
.c: ..c: finproc {P}
O'l
·c
{ corps de l a fonc t i on ra c2} O'l
·c
>
Cl. . . sui te d ' énoncés
.
>
Cl.
{ appel de PJ
0 0
u finfonc { rac2} u
P (x, y, z )
L'appel d'une routine est une action élémentaire qui permet l 'exécution de la suite d'énon Parmi tous les modes de transmission de paramètres que les langages définissent (et il en
cés associée à son nom. Il consiste simplement à indiquer le nom de la procédure ou de la existe de nombreux), nous allons en présenter deux : la transmission par valeur et la trans
fonction avec ses paramètres effectifs. mission par résultat.
6.5 Retour d'une routine 57 58 Chapitre 6 • Procédures et fonctions
La transmission par valeur est utilisée pour les paramètres données. Elle a pour effet d' af rendre expr { expr dél i vre un rés u l t a t de type T}
fecter au nom du paramètre formel la valeur du résultat de 1 'évaluation du paramètre effectif.
Le paramètre effectif sert à fournir une valeur initiale au paramètre formel. Dans l ' appel finfonc {F}
P ( Xe ) d'une procédure P dont l' en-tête est le suivant :
Le corps de la fonction peut contenir plusieurs instructions rendre, mais 1 'exécution du
procédure P ( donnée Xf : T)
premier rendre a pour effet d'achever l 'exécution de la fonction, et de revenir au contexte
tout se passe comme si, avant d'exécuter l a suite d'énoncés de la procédure P, l 'affectation d'appel avec la valeur résultat.
x f +--x e était effectuée.
Notez que le paramètre effectif peut donc être un nom ou une expression. D'autre part,
toute modification du paramètre formel reste locale à la routine, cela veut dire qu'un para 6.6 LOCALISATION
mètre effectif transmis par valeur ne peut être modifié.
À 1' intérieur d'une routine, il est possible de déclarer des variables ou des constantes pour
désigner de nouveaux objets. Ces objets sont dits locaux. Chaque fois que des objets n' ont de
6.4.2 Transmission par résultat signification qu'à l'intérieur d'une routine, ils devront être déclarés locaux à la routine. Les
De quelle façon une routine renvoie-t-elle ses résultats ? Pour une fonction se sera par l'in noms locaux doivent être déclarés entre l 'en-tête de la routine et son corps. Dans la procédure
termédiaire de sa valeur de retour et pour une procédure par l' intermédiaire d'un ou plusieurs Éq2degré, la variable � et la constante E doivent être déclarées locales à cette procédure
paramètres dont le mode de transmission est par résultat. Ce mode de transmission est pré puisqu'elles ne sont utilisées que dans cette procédure.
cisé en faisant précéder le nom du paramètre formel par le mot-clé résultat. Dans l'appel {An t é cédenta#O, b e t c réels, coeffi ci en t s de
P ( Xe ) d'une procédure P dont l ' en-tête est le suivant : l ' équ a t i on du second degré, ax2 +bx+c}
{ Conséquent ( x-
( r 1 +i X i 1 ) ) ( x-
( r2 +i X i 2 ) ) = 0 }
procédure P ( résultat Xf : T)
{Rôle : calcule les racines d ' une équa t i on du second degré }
tout se passe comme si, en fin d'exécution de la procédure P, le paramètre effectif Xe était procédure Éq2degré ( données a , b, c : réel ;
L'antécédent de l ' appel de la procédure P est l'affirmation A dans laquelle pfl a été rem
placé par pe 1 ; et son conséquent est l'affirmation c dans laquelle p f 1 et p f 2 ont été rem {An t écédent : a , b, c coeffi cien t s réels de l ' équ a t i on
placés, respectivement, par pel et pe2. ax2 +bx+c=O avec a#O J
{ Conséq<uen t :
(x- (rl +i x i l ) ) (x- (r2+i x i2) ) = O J
{Rôle : calcule les racines d ' une équat i on du second degré }
Règ le 4
procédure Éq2degré ( données :
a , b , c réel
D'une façon générale, l' antécédent de l'appel d'une routine est l' antécédent de la routine résultats r l , i l , r2 , i 2 : rée l )
constante é = ? { dépend de l a préci sion des réels sur l a mach i n e }
dans lequel 1' ensemble des paramètres formels données (et ceux à la fois données et résultats,
s'ils existent) ont été remplacés par les paramètres effectifs données (et ceux à la fois données variable .6. type réel { l e di scriminan t }
et résultats, s'ils existent). Le conséquent de l'appel est le conséquent de la routine dans lequel
.6. +- carré ( b ) - 4 X a X c
l'ensemble des paramètres formels données et résultats (et ceux à la fois données et résultats,
s i .6.#0 alors {calcul des racines rée l l e s }
s'ils existent) ont été remplacés par les paramètres effectifs données et résultats (et ceux à la
si b>O alors rl +-- - (b+\/"K) / ( 2 X a )
fois données et résultats, s'ils existent).
sinon r l +-- ( \/"K-b ) / ( 2 X a )
finsi
Règ le 5 { rl est l a racine l a plus grande en val e ur abso l u e }
Après remplacement des paramètres formels par les paramètres effectifs, les affirmations si 1 rl 1 < é alors r2 +--0
sinon r2 +-- cl (aXrl)
ne doivent plus contenir de références à des paramètres formels.
finsi
En appliquant les règles précédentes, 1 'antécédent et le conséquent de 1' appel de la procé il +-- 0
dure Éq2degré de la page 55 sont : i2 +-- 0
{ (x-rl ) (x-r2 ) =0}
{An técédent : u#O, 1 et v+l réels , coeffi cien t s de s inon { ca l cul des racines complexes }
l ' équa t i on du second degré, ux2 +x+v+l }
rl +-- r 2 +-- -b/ ( 2 X a )
Éq2degré (u, l , v+ l , rl l , igl , rl2 , ig2 )
{ Conséquen t : (x- (rl l +i x i gl ) ) (x- (rl2+i x i g2) ) = OJ
il +-- �/ ( 2 X a )
i2 +-- -i l
finsi
{ (x- (rl +i x i l ) ) (x- (r2+i x i2) ) 0}
finproc {Éq2degré}
6.8 EXE M PLES
L'écriture de 1' algorithme de la page 49 dans lequel le calcul des racines est fait par une On désire maintenant écrire un programme qui affiche la date du lendemain, avec le mois
procédure est récrit de la façon suivante : en toutes lettres. La date du jour est représentée par trois entiers : jour, mois et année. Le
-0 -0
0
c: Algorithme Équat ion second degré 0
c: calcul de la date du lendemain ne pourra se faire que sur une date valide dont 1 ' année est
=i =i
0 {Ant é céden t coeffl#O, coeff2 et coeff3 réels, coeffi cien t s 0 postérieure à 1 582 2. Ainsi, si les données sont 31, 12, 2014 le programme affichera 1 janvier
"<:t"
,.-1 d ' une équ a t i on d u second degré } "<:t"
,..-1 2015.
0 0
{ Conséquent (x- (préel l +i x imagl ) ) (x- (préel2+i x imag2) ) OJ =
N N Algorithme Date du lendemain
@ variables @
....... ....... {Ant é céden t : trois en t i ers qui représentent une da t e }
..c: { l e s trois coeffi cien t s de l ' équa t i on } ..c:
O'l O'l { Conséquen t : l a da t e d u lendemai n , si e l l e exi s t e , e s t affi chée}
·c coe f f l , coe f f2 , coef f 3 , ·c
> > lire ( jour, moi s , année)
Cl. Cl.
0 { l es deux ra cines} 0
u u vé rifier s i la date est valide
préel l , préel2 , pimagl , pimag2 type réel si non valide alors
signaler l ' e rreur
{ l i re les 3 coeffi cien t s de l ' équa t i on }
sinon
l ire ( coeffl, coeff2 , coeff3 )
{ coeffl , coeff2, coeff3 coeffi cien t s réels de l ' équa t i on
2. Date de l 'adoption du calendrier grégorien.
coeff3x2 +coeff2x+coeff3=0 et coeffl# O }
Éq2degré ( coeff l , coeff2 , coeff3 , prée l l , pimagl , prée l 2 , pimag2 )
{ (x- (préel l +i xpimagl ) ) (x- (préel 2+i xpimag2 ) ) = O J
{ é crire les résul t a t s }
écrire (prée l l , pimagl , prée l 2 , pimag2 )
6.8 Exemples 63 64 Chapitre 6 • Procédures et fonctions
choix m parmi
1 é c r i re ( " j anvier " )
2 é c r i r e ( " févri e r " )
3 é c r i r e ( " mar s " )
4 é c r i r e ( " av r i l " )
5 é c r i r e ( " mai " )
6 écrire ( " juin " )
7 é c r i r e ( " j u i l l et " )
8 é c r i r e ( " août " )
9 é c r i re ( " septembre " )
10 é c r i r e ( " octobre " )
11 é c r i r e ( " novembre " )
12 é c r i r e ( " décemb re " )
finchoix
é c r i r e ( ' '-' ' , a)
finproc { écri reDa t e }
6.9 EXERCICES
Exercice 6.1. Écrivez en JAVA et testez la fonction b i s s e xt i l e qui teste s i une année
passée en paramètre est bissextile ou non.
Exercice 6.2. Écrivez en JAVA et testez la fonction max2 qui renvoie le maximum de deux
entiers passés en paramètres.
Exercice 6.3. En utilisant la fonction max2 précédente, écrivez une fonction max3 qui ren
voie le maximum de trois entiers passés en paramètres.
Exercice 6.4. Écrivez la fonction f ahrenhei t qui prend en paramètre un réel représentant
-0 une température en degrés Celcius et qui renvoie sa conversion en degrés Fahrenheit.
0
c:
=i
0 Exercice 6.5. Écrivez en JAVA et testez la procédure é changer qui prend en paramètre
"<:t"
,..-1 deux entiers et échange leurs valeurs. Le procédure main aura la forme suivante :
0
N
@ public static void main ( S t r ing [] args) {
...... .µ
..c: int X = 3 , y = 8; ..c
O'l Ol
·c échanger ( x , y ) ; ï:::
> >-
Cl. Syst em . out . p r i n l n ( x , y ) ; 0.
0 0
u u
La classe Rectangle fournira comme service à ses clients, l'accès à ses attributs : sa
longueur et sa largeur. Nous verrons dans la section 7.2 comment déclarer les méthodes. La
Chapitre 7 déclaration d'une variable r de type Rect a n g l e s'écrit de la façon habituelle :
variable r type Re ctangle
Il est important de bien comprendre la différence entre les notions de classe et d'objet 1 .
Pour nous, les classes sont des descriptions purement statiques d'ensembles possibles d'ob
jets. Leur rôle est de définir de nouveaux types. En revanche, les objets sont des instances
particulières d'une classe. Les classes sont un peu comme des « moules » de fabrication dont
sont issus les objets. En cours d'exécution d'un programme, seuls les objets existent.
La programmation par objets est une méthodologie qui fonde la structure des programmes La déclaration d'une variable d'une classe donnée ne crée pas l'objet. L'objet, instance de
autour des objets. Dans ce chapitre, nous présenterons les éléments de base d'un langage de la classe, doit être explicitement créé grâce à l'opérateur créer. Chaque objet créé possède
classe : objets, classes, instances et méthodes. tous les attributs de la classe dont il est issu. Chaque objet possède donc ses propres attributs,
distincts des attributs d'un autre objet : deux rectangles ont chacun une largeur et une longueur
qui leur sont propres.
7.1 OBJ ETS ET CLASS E S Les attributs de l'objet prennent des valeurs initiales données par un constructeur. Pour
chaque classe, il existe un constructeur par défaut qui initialise les attributs à des valeurs
Nous avons vu qu'un objet élémentaire n'est accessible que dans sa totalité. Un objet initiales par défaut. Ainsi la déclaration suivante :
structuré est, par opposition, construit à partir d'un ensemble fini de composants, accessibles
variable r type Re ctangle créer Rectangl e ( )
indépendamment les uns des autres.
-0 -0
0 0
c: Dans le monde de la programmation par objets, un programme est un système d'interaction c: définit une variable r qui désigne un objet créé de type Rectangle, et dont les attributs sont
=i =i
0 d'une collection d'objets dynamiques. Selon B . MEYER [Mey88, Mey97], chaque objet peut 0 initialisés à la valeur réelle 0 par le constructeur Rect ang l e ( ) . La figure 7 . 1 montre une
"<:t" "<:t"
,..-1
0
être considéré comme unfournisseur de services utilisés par d'autres objets, les clients. Notez ,.-1
0 instance de la classe Rect angle avec ses deux attributs initialisés à O. La variable r qui
N que chaque objet peut être à la fois fournisseur et client. Un programme est ainsi vu comme N
désigne l'objet créé est une référence à l'objet, et non pas l'objet lui-même.
@ @
....... un ensemble de relations contractuelles entre fournisseurs de services et clients . .......
..c: ..c:
O'l O'l Rectangle
·c Les services offerts par les objets sont, d'une part, des données, de type élémentaire ou ·c
> >
Cl. structuré, que nous appellerons des attributs, et, d' autre part, des actions que nous appellerons Cl.
0 0
u u
méthodes. Par exemple, un rectangle est un objet caractérisé par deux attributs, sa largeur et largeur = 0.0
sa longueur, et par des méthodes de calcul de sa surface ou de son périmètre. longueur = 0.0
Les langages de programmation par objets offrent des moyens de description des objets
manipulés par le programme. Plutôt que de décrire individuellement chaque objet, ils four
nissent une construction, la classe, qui décrit un ensemble d' objets possédant les mêmes pro FIGURE 7.1 r désigne u n objet de type Rectangle.
priétés. Une classe comportera en particulier la déclaration des données et des méthodes. Les
langages de programmation à objets qui possèdent le concept de classe sont appelés langages l . Dans la plupart des langages à objets, cette différence est plus subtile, puisqu'une classe peut être elle-même
de classes. un objet.
7. 1 Objets et classes 69 70 Chapitre 7 • Programmation par objets
Le fonctionnement d'une affectation d'objets de type classe n'est plus tout à fait le même Ainsi, dans cette classe, la notation l a rgeur désigne l'attribut de même nom de l'ob
que pour les objets élémentaires. Ainsi, l' affectation q +--r affecte à q la référence à l'objet jet courant. La notation r . largeur désigne l'attribut l argeur de l'objet désigné par la
désigné par r. Après l' affectation, les références r et q désignent le même objet (voir la variable r déclarée précédemment.
figure 7 . 2).
Rectangle
7 . 1 .4 Attributs de classe partagés
Nous avons vu que chaque objet possède ses propres attributs. Toutefois, il peut être inté
r
largeur = 0.0 q
ressant qu' un attribut de classe soit partagé par toutes les instances d'une classe. Nous appel
longueur = 0.0 lerons attribut partagé, un attribut qui possède cette propriété. Un tel attribut possédera une
information représentative de classe tout entière. Le mot-clé partagé devra explicitement
précéder la déclaration des attributs partagés.
FIGURE 7.2 r et q désignent le même objet.
71 2
. . Destruction des objets La syntaxe des classes en JAVA suit celle des classes données ci-dessus dans notre pseudo
langage. La classe Rectangle s'écrira :
Que faire des objets qui ne sont plus utilisés ? Ces objets occupent inutilement de la place
en mémoire, et i l convient de la récupérer. Selon les langages de programmation, cette tâche class Re ctangle
public double l a rgeu r , l ongue u r ;
est laissée au programmeur ou traitée automatiquement par le support d'exécution du lan
gage. La première approche est dangereuse dans la mesure où elle laisse au programmeur la
responsabilité d' une tâche complexe qui pose des problèmes de sécurité. Le programmeur
est-il bien sûr que l'objet qu'il détruit n' est plus utilisé ? Au contraire, la seconde approche La création d'un objet se fait grâce à l'opérateur new et son initialisation grâce à un
simplifiera l'écriture des programmes et offrira bien évidemment plus de sécurité. constructeur dont le nom est celui de la classe. La variable r précédente est déclarée et initia
lisée en JAVA de la façon suivante :
Notre notation algorithmique ne tiendra pas compte de ces problèmes de gestion de la
mémoire et aucune primitive ne sera définie. Rectangle r = new Re ctangle ( ) ;
7.2 LES M ÉTHODES Si les méthodes possèdent des en-têtes différents, bien qu'elles aient le même nom, elles
sont considérées comme distinctes et dites surchargées. La notion de surcharge est normale
Le second type de service offert par un objet à ses clients est un ensemble d'opérations ment utilisée lorsqu'il s'agit de définir plusieurs mises en œuvre d'une même opération. La
sur les attributs. Le rôle de ces opérations, appelées méthodes, est de donner le modèle opé plupart des langages de programmation la propose implicitement pour certains opérateurs ;
rationnel de l'objet. traditionnellement, l 'opérateur + désigne l 'addition entière et l'addition réelle, ou encore la
Nous distinguerons deux types de méthodes, les procédures et les fonctions. Leurs dé concaténation de chaînes de caractères, comme en JAVA. En revanche, peu de langages pro
clarations et les règles de transmission des paramètres suivent les mêmes règles que celles posent aux programmeurs la surcharge des fonctions ou des procédures. Le langage EIFFEL
énoncées au chapitre 6. Toutefois, nous considérerons que les procédures réalisent une action l 'interdit même, arguant que donner aux programmeurs la possibilité du choix d'un même
sur l'état de l 'objet, en modifiant les valeurs des attributs de l'objet, et que les fonctions se nom pour deux opérations différentes est une source de confusion. Dans une classe, chaque
limitent à renvoyer un état de l'objet. Les procédures modifieront directement les attributs de propriété doit posséder un nom unique 3 .
l'objet sans utiliser de paramètres résultats.
Notez que ce modèle ne pourra pas toujours être suivi à la lettre. Certaines fonctions pour 7.2.1 Accès aux méthodes
ront être amenées à modifier des attributs, et des procédures à ne pas modifier l'état de l'objet.
L'accès aux méthodes suit les mêmes règles que celles de 1' accès aux attributs. Dans la
Pour toutes les instances d' une même classe, il n'y a qu'un exemplaire de chaque méthode classe, toute référence à une méthode s'applique à 1' instance courante de 1' objet, et depuis un
de la classe. Contrairement aux attributs d'instance qui sont propres à chaque objet, les objets client il faudra utiliser la notation pointée. Pour rendre les méthodes accessibles aux clients,
d' une classe partagent la même méthode d'instance. il faudra, comme pour les attributs, les désigner publiques.
Nous pouvons maintenant compléter notre classe Rectangle avec, par exemple, deux
r . changerLargeur ( 2 . 5 )
fonctions qui renvoient le périmètre et la surface du rectangle, et deux procédures qui modi
r . changerLongueur ( 7 )
fient respectivement la largeur et la longueur du rectangle courant. Notez que la définition de { r désigne un rectangle de l argeur 2 . 5 et de l on gueur 7 }
ces deux dernières méthodes n'est utile que si les attributs sont privés.
classe Rectangle
public p é r imè t r e , surface , changerLargeu r , changerLongueur 7 .2.2 Constructeurs
{ l es a t tribut s }
l a rgeur , l ongueur type r é e l Nous avons déjà vu que lors de la création d'un objet, les attributs étaient initialisés à des
valeurs par défaut, grâce à un constructeur par défaut. Mais, il peut être utile et nécessaire pour
{ l es méthodes} une classe de proposer son propre constructeur. Il est ainsi possible de redéfinir le constructeur
par défaut de la classe Rectangle pour initialiser les attributs à des valeurs autres que zéro.
{Rôle : renvoie l e périmètre du rect an gl e }
fonction p é r imètre ( ) : réel classe Rectangle
rendre 2 X ( l a r geur+ l ongue u r ) public
-0 -0
0 finfonc {périmètre} 0 p é rimèt r e , s u r face , change rLargeur , changerLongueur
c: c:
=i =i { l es a t t ribut s }
0 0
"<:t" {Rôle : renvoie l a s urface du rectangl e } "<:t" largeur, l ongueur type réel
,..-1 ,..-1
0 fonction s u r f ace ( ) réel 0 constructeur Rectangl e ( )
N N
@ rendre l argeur X longueur @ l a rgeur +-- 1
....... ....... l ongueur +-- 1
..c: finfonc { surface} ..c:
O'l O'l
·c ·c fincons
> >
Cl.
0 {Rôle : met la l argeur du rectangle à lg} Cl.
0
{ l e s méthodes}
u u
procédure changerLargeur ( donnée l g : rée l )
largeur f- l g finclasse Rectangle
finproc
Bien souvent, il est souhaitable de proposer plusieurs constructeurs aux utilisateurs d'une
{Rôle : met l a l ongueur du rectangle à l g} classe. Malgré les remarques précédentes, nous considérerons que toutes les classes peuvent
procédure changerLongueur ( donnée l g : rée l ) définir un ou plusieurs constructeurs, dont les noms sont celui de la classe, selon le mécanisme
l o n gueur +- l g de surcharge. La classe Rect angle pourra définir, par exemple, un second constructeur
finproc
finclasse Rectangle 3. Lire avec intérêt les arguments de B. MEYER [Mey97] contre le mécanisme de surcharge.
7.2 Les méthodes 73 74 Chapitre 7 • Programmation par objets
pour permettre aux clients de choisir leurs propres valeurs initiales. Les paramètres d'un
constructeur sont implicitement des paramètres données « ». I** Rôle : i n i t i a l i s e les champs l a rgeur e t l ongueur à 1 *I
public Rectangl e ( )
constructeur Rectangl e ( données l g l , lg2 : rée l )
this ( l , 1 ) ;
l a rgeur f- lgl
l ongueur f- l g2
fincons
Il les méthodes
Les créations de rectangles suivantes utilisent les deux constructeurs définis précédem
I** Rôle : renvoie l e périmètre du rectangle *I
ment :
public double p é r imèt r e ( ) {
variables return 2 * ( l argeur + longueu r ) ;
r type Rectangle créer Rectangl e ( )
{ r dés i gne un rectangl e (1, 1 ) )
s type Rectangle créer Re ctangle ( 3 , 2 ) I** Rôle : renvoie l a s urface du rectangle *I
{ s dés i gne un rectangl e (3, 2) ) public double surface ( ) {
return l a rgeur * longueu r ;
Les méthodes et les constructeurs peuvent être surchargés dans une même classe. Leur Si le contrat n'est pas respecté 5 , l'exécution du programme provoquera une erreur. Toute
distinction est faite sur le nombre et le type des paramètres, mais, attention, pas sur celui du fois, peut-on quand même poursuivre l'exécution du programme lorsque la rupture du contrat
type du résultat. est avérée, tout en garantissant la validité du programme ? Nous verrons au chapitre 1 4 com
JAVA permet de déclarer une méthode statique en faisant précéder sa déclaration par le ment la notion d'exception apporte une solution à ce problème.
mot-clé static. Comme pour les attributs, les méthodes statiques existent indépendamment
de la création des objets, et sont accessibles en utilisant dans la notation pointée le nom de
classe. La méthode main est un exemple de méthode statique. Elle doit être déclarée statique 7.4 EXEM PLES
puisqu'aucun objet de la classe qui la contient n'est créé. Notez que la notion de méthode
statique remet en cause le modèle objet, puisqu'il est alors possible d'écrire un programme Dans Je chapitre précédent, nous avons vu comment structurer avec des routines la résolu
JAVA sans création d'objet, et exclusivement structuré autour des actions. tion d'une équation du second degré et le calcul de la date du lendemain. Nous reprenons ces
deux exemples en les réorganisant autour des objets.
4. Ibidem. 5 . S i l'on suppose que les classes sont justes, ce sont les antécédents des méthodes appelées qui ne seront pas
respectés.
7.4 Exemples 77 78 Chapitre 7 • Programmation par objets
7 .4.2 Date du lendemain * Invariant de classe : les a t t ributs j our, mois e t année
* représen tent une da te val ide � annéeMin
L'objet central du problème est bien évidemment la date. Nous définissons une classe *I
Date, dont les principaux attributs sont trois entiers qui correspondent au jour, au mois et class Date {
à 1 ' année. Cette classe offre à ses clients les services de calcul de la date du lendemain et private static final int AnnéeMin 1582;
d'affichage (en toutes lettres) de la date. Notez que la liste de services offerte par cette classe private int j ou r , moi s , anné e ;
n'est pas figée ; si nécessaire, elle pourra être complétée ultérieurement. I * * Conséquent : jour = j , mois m et année a représentent
* une da t e val i de
Le calcul de la date du lendemain modifie 1' objet courant en ajoutant un jour à la date *I
courante. La vérification de la validité de la date sera faite par le constructeur au moment de public Date ( int j , int m, int a)
la création de l 'objet. Ainsi, 1' invariant de classe affirme que la date courante, représentée par i f ( a < annéeMi n )
les trois attributs j our, mo i s et année, constitue une date valide supérieure ou égale à une S y s t em . e r r . p r i n t l n ( " Annéeu i n c o r r e c t e " ) ;
année minima. L'attribut qui définit cette année minima est une constante publique. S y s t em . exit ( l ) ;
classe Date
année = a ;
{ In variant de classe : les a t t ributs jour, moi s et année
Il l ' année est val i de, t e s t e r l e mois
représen t e n t une da t e val i de � annéeMi n }
public dema i n , a f f i che r , annéeMin
if ( m < l 11 m> l 2 )
S y s t em . e r r . p r i n t l n ( " M o isu i n c o r r e c t " ) ;
S y s t e m . exit ( l ) ;
j ou r , moi s , année type e nt i e r
constante annéeMin = 1582
mois = m ;
I l l ' année et l e mois sont val i des, t es t er le jour
{An t écédent : }
{ Conséquen t : jour j , mois = m e t année = a
if ( j < l 11 j >nbJou r s D ansMoi s (moi s ) )
S y s t em . er r . println ( " J o u r u i n c o r r e c t " ) ;
représen tent une dat e valide}
S y s t em . exit ( l ) ;
constructeur Date ( données j , m, a : e nt i e r )
}
Il l ' année, l e mois e t l e jour son t val i des
fincons
j ou r = j;
/**
return -1;
* La classe Date représen t e des da tes de l a forme
* jour mois année .
7.4 Exemples 81 82 Chapitre 7 • Programmation par objets
I** Ant écédent j our, moi s, année représen tent une da te val ide Le constructeur Date vérifie la validité de la date. Si la date est incorrecte, il se contente
* Conséquent jour, moi s, année représen t en t l a da t e du lendemai n d'écrire un message sur la sortie d'erreur standard et d' arrêter brutalement le programme.
*I
La classe de test donnée ci-dessous crée un objet de type Date. Le jour, le mois et l'année
public void demai n ( )
assert j ou r<=nbJoursDansMo i s ( mo i s ) & & l <=mo i s & &
sont lus sur l'entrée standard. Elle calcule la date du lendemain et l 'affiche.
mo i s <= l 2 & & année >=annéeMi n ; import j ava . i o . * ;
Il jour � e t mois dan s [ 1 , 1 2] e t année � annéeMin class DateduLendemain
if ( j our<nbJou rsDansMo i s ( mo i s ) ) public static void ma i n ( St r i n g [ ] args ) throws IOExcept ion
Il l e mois et l ' année ne changent pas {
j our++ ; Date d = new Date ( St d i nput . readint ( ) ,
else { Stdinput . readint ( ) ,
Il c ' es t l e dern i er jour du moi s, i l fa u t passer a u Stdi nput . readl n i nt ( ) ) ;
Il premier jour du mois s u i vant d . demain ( ) ;
j ou r= l ; S y s t em . out . p r i nt l n ( d ) ;
i f (moi s < l 2 ) mo i s + + ; }
el se Il fin classe DateduLendema in
Il c ' es t l e dern ier mois de l ' année, i l fa ut passer
Il a u premier mois de l ' année s u i van t e
mois= l ; année++ ;
7 .5 EXERCICES
Le produit de deux complexes z l et z2 est donné par les deux équations suivantes :
-0
0
c:
=i
0
"<:t"
,.-1
0
N
@
....... .µ
..c: ..c
O'l Ol
·c ï:::
> >-
Cl. 0.
0 0
u u
86 Chapitre 8 • Énoncés itératifs
Pa
t
Pn Po
I
En
� 1
pn-1 1 P1
1
Chapitre 8 1
\ I
. 1 \ p. 1
pi+ ; 1-
Énoncés itératifs
Ei + l
� et non B
B �p.t
itérer
Jusqu' à présent, nous avons vu qu'un énoncé ne pouvait être exécuté que zéro ou une fois. E1
Est-il possible de répéter plus d'une fois l'exécution d'un énoncé ? La réponse est oui, grâce arrêt si B
aux énoncés itératifs. E2
finitérer
Sa règle de déduction est donnée ci-dessous. L' invariant de boucle est l'affirmation Q.
8.1 FOR M E G É N ÉRALE
si { P} � {Q} et {Q et n on B } � { P}
Un énoncé itératif, comme le montre sa représentation circulaire donnée par la figure 8. 1 , alors {P} itérer E1 arrêt si B E2 finitérer { Q et B }
est souvent appelé boucle. On entre dans la boucle avec l'antécédent Pa , et à chaque tour de À partir de la forme générale de l'énoncé itératif, deux énoncés particuliers peuvent être
-0
0
boucle les énoncés qui la composent sont exécutés. L'arrêt de la boucle se produira lorsque -0
0
déduits selon que l' ensemble des énoncés de A1 à Ai ou l'ensemble des énoncés de Ai+ 1 à
c:
=i le prédicat d'achèvement B sera vérifié. c:
=i An est vide. Ces deux énoncés itératifs, qui existent dans la plupart des langages de program
0 0
"<:t" L'affirmation Pa est l'antécédent de la boucle, Pc le conséquent, et Po, P1 , ... , Pn des -.::t"
mation, sont les énoncés tantque et répéter.
,..-1 ,..-1
0 affirmations toujours vraies quel que soit le nombre d'itérations. Ces demjères sont appelées 0
N N
@ invariants de boucle. La règle de déduction de l' énoncé itératif s'exprime comme suit : @
.......
..c:
O'l
·c si
E1 E';. Ei Ei+l E
{ Po } � { P1 } =7" { P2 } . . . { Pi - d � { Pi } =;.. { Pi+1 } . . . { Pn-d � { Pn }
�
·c
8.2 L'É NONCÉ TANTQUE
> >
Cl. et { Pn } =;.. {Po } et {Pa } =;.. { Po } Cl.
Lorsque les énoncés E1 à Ei sont vides, nous sommes en présence d'un énoncé tantque.
0 E ·+1 0
u u
et { Pi et B } =;.. { Pc } et { Pi et non B } � {Pi + i } Sa syntaxe est généralement :
a l o r s { Pa } é n oncé- i t é r a t i f-général { Pc }
tantque B faire E fintantque
L'emplacement spécifique de 1' affirmation Pi , i.e. juste avant le prédicat d'achèvement, lui
confère un rôle privilégié. On considérera cette affirmation comme 1' invariant de boucle. Cet où le prédicat d'achèvement B est une expression booléenne. Tant que l'évaluation de l'ex
invariant décrit la sémantique de l'énoncé itératif, et doit être un préalable, ce qui n'est pas pression B donne la valeur vrai, l' énoncé E est exécuté. La boucle s' achève lorsque B est
toujours évident, à la programmation de la boucle. Une syntaxe possible d'un énoncé itératif faux. Si le prédicat d' achèvement est immédiatement vérifié, l'énoncé E n'est pas exécuté.
général est la suivante : L'énoncé E peut donc être exécuté zéro ou plusieurs fois. Plus formellement, la règle de
déduction de cet énoncé tantque est :
8.3 L'énoncé répéter 87 88 Chapitre 8 • Énoncés itératifs
, ,
� �
8.5.1 Factorielle
Le prédicat d'achèvement B est là encore une expression booléenne. L'énoncé E est exé
Nous désirons écrire la fonction f a c t o r i e l l e qui calcule la factorielle d'un entier natu
cuté jusqu'à ce que l'évaluation de l'expression B donne la valeur vrai. Tant que la valeur
rel passé en paramètre. Rappelons que la factorielle d'un entier naturel n est égale à :
estfaux, l'énoncé E est exécuté. Notez que l'énoncé E est exécuté au moins une fois. Ainsi,
le choix entre l'utilisation d'un énoncé t antque et répéter dépendra du nombre d'itéra n! = 1 X 2 X 3X ... X (i - 1 ) X i X . . . X n
tions minimal à effectuer. Plus formellement, la règle de déduction de l 'énoncé répéter est
donnée ci-dessous. Notez que l'invariant de boucle est l' affirmation Q et non pas P ! Une première façon de procéder est de calculer une suite croissante de produits de 1 à
n. Peut-on déterminer immédiatement l'invariant de boucle ? Au ie produit, c'est-à-dire à
s i { P } E { Q } et { Q et non B } E { Q }
alors { P } répéter E jusqu ' à B { Q et B } la je itération qu'a-t-on calculé ? Réponse il et ce sera notre invariant. L'algorithme et la
démonstration de sa validité sont donnés ci-dessous :
Un langage de programmation pourrait se limiter à la seule définition de l 'énoncé itératif
{An técéden t : n�O J
tantque. En effet, tous les autres énoncés itératifs peuvent s'exprimer à partir de celui-ci.
{Conséquent : factori e l l e n!J
Par exemple, les énoncés itérer et répéter s'écrivent comme suit :
-0 -0 fonction factorielle ( donnée n : nature l ) n at u r el
0 0
c: c: variables
=i itérer {::}- E1 tantque non B faire E2 E 1 fintantque =i
0 0 i , fact type n a t u r e l
"<:t" répéter {::}- E tantque non B faire E f intantque "<:t"
,.-1 ,..-1
0 0
N N i +- 0
@ @ fact +- 1 { In variant : fact = i ! J
...... .......
..c: > L'énoncé répéter de JAVA ..c: tantque i <n faire
O'l O'l
·c ·c { fact X (i+l) = i ! X (i+l ) = (i+l) ! et i <n }
> >
Cl. Cet énoncé s'écrit de la façon suivante : Cl.
0 0 i +- i + l
u u
{ fact X i = i!J
do E while ( B ) ;
fact +- fact x i
{ fact = i ! J
Notez que la sémantique du prédicat d'achèvement est à l'opposé de celle de la forme fintantque
algorithmique. L'itération s'achève lorsque le prédicat B prend la valeur faux. La règle de { i = n e t fact = i ! n!}
déduction est donc légèrement modifiée : rendre fact
finfonc { fact oriel l e }
si { P } E { Q } et { Q et B ) E { Q )
alors { P } do E while ( B ) ; { Q et non B) Une fonction qui permet de démontrer la finitude de l a boucle est f (i ) = n - i . Lorsque
i = n,le prédicat d'achèvement est vérifié.
8.5 Exemples 89 90 Chapitre 8 • Énoncés itératifs
m i n +-- +oo Le plus grand commun diviseur de deux nombres naturels est 1 'entier naturel le plus grand
max +-- - oo qui les divise tous les deux. Il est tel que :
i +-- 0
répéter pgcd( a, b) = pg cd( a - b, b) avec a > b = pgcd( a, b - a) avec a < b
i +-- i + l
lire ( x )
s i x < min alors min +-- x f insi L'algorithme proposé par EUCLIDE 1 procède par soustractions successives des valeurs a
Nk E [1, i] , min�Xk }
et b, jusqu'à ce que a et b soient égaux.
si x > max alors max +-- x f insi
Nk E [1, i] , min�xk et max?:xk }
{An t é céden t a>O et b > O J
jusqu' à i = n { Conséquent pgcd (a, b) =
pgcd (a-b, b) et a>b
Nk E [1, i], min�xk e t max?:xk et i =n } pgcd (a, b-a) et a <b }
finproc {MinMax} fonction pgcd ( données a , b : nature l ) : naturel
tantque a#b faire
Comme pour factorielle, la fonction f (i) = n - i permet de démontrer la finitude de la {pgcd (a, b) = pgcd (a-b, b) et a>b
boucle. =
pgcd (a, b-a) et a <b }
-0 -0 s i a>b alors
0 0
c: c:
=i =i {pgcd (a, b) =
pgcd (a-b, b) et a>b}
0 8.5.3 Division entière 0
"<:t" "<:t" a +--- a-b
,..-1 ,..-1
0
N Rappelons tout d'abord, la définition de la division entière de deux entiers naturels a et b : 0
N
sinon
b +--- b-a
@ @
...... ....... {pgcd (a , b) pgcd (a, b-a) et a <b }
..c:
O'l Va � 0, b> 0, a = quotient x b + reste , 0� r <b ..c:
O'l finsi
·c ·c
> > fintantque
Cl. Cl.
u
0 Pour calculer la division entière, nous allons procéder par soustractions successives de la u
0
{ a = b = pgcd (a, b) J
valeur b à la valeur a jusqu'à ce que le résultat de la soustraction soit inférieur à b. D'après ce rendre a
que nous venons de dire l ' invariant est la définition même de la division entière. finfonc {pgcd}
{An técédent : a?:O, b > O J
{ Conséquen t : a = quo t i en t x b + res te, 0 �res t e <b } L'application successive des fonctions f(a, b) = a - b lorsque a > b et f'(a, b) = b - a
procédure Divi sionEntière ( données a , b naturel :
lorsque a < b tend vers l'égalité de a et de b. Le prédicat d'achèvement sera donc vérifié.
résultats quo t i ent , r e s t e nature l )
reste +-- a
quot ient +-- 0 l . EUCLIDE, mathématicien grec du Ille siècle avant J.-C.
8.5 Exemples 91 92 Chapitre 8 • Énoncés itératifs
Le calcul de la somme converge lorsqu'une précision é est atteinte, c'est-à-dire lorsqu'un Exercice 8.9. On désire calculer la racine carrée d'un nombre réel x par la méthode de
nouveau terme t est tel que ltl �é. Note : ne pas utiliser la fonction factorielle, ni la fonction HÉRON L' ANCIEN 2 . Pour cela, on calcule la suite rn = (rn - 1 + x/rn_i )/2 jusqu'à obtenir
puissance. une approximation rn = JX telle que Ir; - xi est inférieur à un é donné. Vous pourrez
choisir r0 = x/2. Écrivez un algorithme itératif qui procède à ce calcul.
Exercice 8.5. Les opérateurs < < et > > du langage JAVA permettent de faire des décalages
binaires vers la gauche ou vers la droite. L'opérande gauche est la configuration binaire à Exercice 8.10. En utilisant la suite rn = (2rn - l + x/(rn_1) 2 )/3, écrivez l 'algorithme qui
décaler, et l 'opérande droit est le nombre de bits à décaler. Pour un décalage vers la gauche, calcule la racine cubique d'un entier naturel x .
des zéros sont systématiquement réinjectés par la droite. Pour un décalage vers la droite, les
bits réinjectés à gauche sont des uns si la configuration binaire est négative, sinon ce sont des
zéros. L'opérateur > > > décale vers la droite et réinjecte exclusivement des zéros.
À l'aide des opérateurs de décalage binaire et de l'opérateur & (et logique), récrivez le pro
duit de deux entiers naturels x et y, ainsi que l 'élévation de x à la puissance y . L'algorithme
utilisera la décomposition binaire de y . On rappelle que la multiplication par deux d'un en
tier correspond à un décalage d'un bit vers la gauche, et que la division par deux d'un entier
correspond à un décalage d'un bit vers la droite. D'autre part, l 'opération x & 1 retourne la
valeur du bit le plus à droite de x.
Exercice 8.6. Calculez pgcd(2015, 1680) à l 'aide de l ' algorithme donné page 90. Combien
d'itérations sont nécessaires à ce calcul ? L' algorithme d' EUCLIDE s'écrit également sous la
forme suivante :
Exercice 8.8. Programmez la fonction booléenne e s t P remi e r qui teste si l' entier naturel
passé en paramètre est un nombre premier. On rappelle qu'un nombre est premier s'il n'admet
comme diviseur que 1 et lui-même. En d'autres termes, n est premier si le reste de la division
entière de n par d n'est jamais nul, Vd, 2 � d � n 1 .
-
La variable t 1 est un tableau à deux composants de type entier. Le type des indices est le
type booléen. La variable t 2 est un tableau à dix composants de type caractère. Le type des
indices de t 2 est le type intervalle [ 1 , 1 O ] . La figure suivante montre une représentation de
ces deux tableaux en mémoire. Les composants sont rangés de façon contiguë. Chaque case
est un composant particulier, dont le type est indiqué en italique, et à gauche figure la valeur
Chapitre 9
de l 'indice qui permet d'y accéder.
tl t2
entier caractère
Les tableaux
faux 1
vrai entier 2 caractère
3 caractère
4 caractère
5 caractère
6 caractère
7 caractère
8 caractère
9 caractère
Les tableaux définissent un nouveau mode de structuration des données. Un tableau est 10 caractère
un agrégat de composants, objets élémentaires ou non, de même type et dont 1' accès à ses
composants se fait par un indice calculé. Dans certains langages de programmation, comme C ou PASCAL, le nombre de com
posants d'un tableau est constant et figé par sa déclaration à la compilation. Au contraire,
d'autres langages définissent des tableaux dynamiques dont le nombre d'éléments peut varier
à l'exécution.
9.1 DÉCLARATION D'UN TABLEAU
D'une façon générale, les composants d'un tableau sont en nombre fini, et sont accessibles
individuellement sans ordre particulier de façon directe grâce à un indice calculé. Cette der 9.2 D É NOTATION D'UN COM POSANT DE TABLEAU
-0 nière opération s'appelle une indexation. La définition d'un tableau doit faire apparaître : -0
0 0
c: c:
=i =i Les composants sont dénotés au moyen du nom de la variable désignant l'objet de type
0 - le type des composants ; 0
"<:t" "<:t" tableau et de l' indice qui désigne de façon unique le composant désiré.
,..-1 - le type des indices. ,.-1
0 0
N N On désignera un composant d'un tableau t d' indice i , par la notation t [ i ] (prononcée t
@ La déclaration d'un tableau t possédera la syntaxe suivante : @ de i), qui est une expression fournissant comme valeur un objet du type des composants ; i
....... ......
..c: ..c: peut être une expression pourvu qu'elle fournisse une valeur du type des indices. La variable
O'l t type tableau [ T i l de T2 O'l
·c ·c
> > t est de type tableau alors que t [ i ] est du type des composants. Avec les déclarations
Cl. Cl.
0 0 précédentes, les notations suivantes sont valides :
u où T 1 et T2 sont, respectivement, le type des indices et le type des composants. Il n'y a u
aucune restriction sur le type des composants, alors que le type des indices doit être discret. t l [ v ra i ] t l [ faux] t 1 [non vra i ] {de type en t i er}
En général, tous les types simples sont autorisés (hormis le type réel) sur lesquels doit exister t2 [ 1 ] t2 [10] t2 [3+4] { de type caractère }
une relation d'ordre. Notez que cette déclaration définit une application de T1 vers T2 .
Il est important de bien comprendre que l'indice doit renvoyer une valeur sur le type des
Un tableau définit un ensemble de valeurs dont la cardinalité est égale à 1 T2 l lTi l , où 1 T 1
indices, ce qui n'est pas toujours facilement décelable.
désigne le nombre d'éléments du type T. La cardinalité du type T1 correspond au nombre de
composants du tableau. t2 [ 2 3 ]
t 2 [ i+ j ]
9.3 Modification sélective 97 98 Chapitre 9 • Les tableaux
La première notation est manifestement fausse, alors que la seconde dépend des valeurs de Cette déclaration ne crée pas les composants du tableau. Remarquez qu'elle n'indique
i et de j , qui risquent de n'être connues qu' à l'exécution du programme. Donner un indice pas non plus leur nombre. Elle définit simplement une référence à un objet de type tableau.
hors de son domaine de définition est une erreur de programmation. Selon les programmes, La création des composants du tableau se fait dynamiquement à l'exécution du programme.
les langages et les compilateurs, cette erreur sera signalée dès la compilation, à l'exécution, Celle-ci doit être explicitée à l 'aide de l'opérateur new qui en précise le nombre de compo
ou encore pas du tout. Cette dernière façon de procéder est celle du langage C, rendant la sants.
construction de logiciels fiables plus difficile. tl new int [ 1 0 ] ;
t2 new Rectangle [ 5 ] ;
9.4 OPÉRATIONS SUR LES TABLEAUX Le tableau t 1 est formé de trois composants dont les valeurs sont 4 , - 5 et 1 2 . Le ta
bleau t 2 possède deux composants de type Rectangle, le premier est la référence null,
Les langages de programmation n'offrent, en général, que peu d'opérations prédéfinies le second est une référence sur une instance particulière (i.e. un rectangle de largeur 3 et de
sur les tableaux. Le plus souvent, ils proposent les opérations de comparaison, qui testent si longueur 5).
deux tableaux sont strictement identiques ou différents, ainsi que l' affectation. Deux tableaux
Le type des indices des tableaux est toujours un intervalle d'entiers dont la borne inférieure
sont égaux s'ils sont de même type, et si tous leurs composants sont égaux. L'affectation
est zéro et la borne supérieure le nombre d'éléments moins un. Le premier élément, t [ O ] , est
affecte individuellement chaque composant du tableau, en partie droite de l'affectation, aux
à l 'indice 0, le deuxième, t [ 1 ] , à l' indice 1 , etc. Le dernier élément est t [ t . length- 1 ] .
composants correspondants du tableau en partie gauche.
Si un indice i n' appartient pas au type des indices d'un tableau t , l'erreur produite par la
variables t l , t 2 type tableau [ ( bleu, rouge , vert ) J de réel notation t [ i ] sera signalée à l'exécution par un message d'avertissement 1 •
À la création d'un tableau, les éléments de type numérique sont initialisés par défaut à
t2 r tl
-0
0
-0
0 zéro, à faux lorsqu'ils sont de type booléen, et à ' \uOOOO' pour le type caractère. Si ce sont des
c: c:
=i =i objets de type classe (ou des tableaux à leur tour), les instances de classe de chaque élément
0 si t l = t2 alors . . . 0
"<:t" "<:t" du tableau ne sont pas créées. La valeur de chaque élément est égale à la référence null.
,.-1 ,.-1
0 0
N N Dans la mesure où une variable de type tableau est une référence à une instance de tableau,
@ @ les instructions suivantes :
....... .......
..c: ..c:
O'l
·c
9.5 LES TABLEAUX EN JAVA O'l
·c tl = t2
==
>- > tl t2
Cl. Cl.
0
8 Une déclaration de tableau possède la forme suivante : u
n'affectent pas les éléments de t 2 à t 1 et ne comparent pas les éléments des tableaux t 1 à t 2 .
Tc [) t; Au contraire, elles affectent et comparent les références à ces deux tableaux. L'affectation et
la comparaison des éléments devront être programmées explicitement élément à élément. La
où Tc est le type des composants du tableau t . Par exemple, Les déclarations d'un tableau méthode c l one de la classe Ob j e ct et les méthodes copyOf, copyOfRange et e qu a l s
d'entiers t l et de rectangles t 2 s' écrivent : de l a classe j ava . ut i l . Arrays aideront l e programmeur dans cette tâche.
int [ ) t l ; Il un tableau d ' en t i ers Notez que cette situation se produit également lors d'un passage d'un tableau 2 en para
Rectangle [ ) t 2 ; Il un tableau d ' objets de type Rectangle mètre. Seule la référence au tableau est transmise par valeur.
Nous allons définir une classe T abA l é a dont le constructeur se chargera d'initialiser le ta
Nous voulons initialiser de façon aléatoire les composants d'un tableau d'entiers et recher
bleau. Puisque JAVA permet la construction dynamique des composants du tableau, le nombre
cher dans le tableau 1' occurrence d'une valeur entière particulière. Les déclarations suivantes
d'éléments sera passé en paramètre au constructeur.
définissent un tableau t ab à n composants entiers.
La classe Random, définie dans le paquetage j ava . u t i l , permet de fabriquer des généra
constante n = 1 0 {nombre d ' é l émen t s du tabl ea u } teurs de valeurs entières ou réelles calculées de façon pseudo-aléatoire. La méthode next Int
variable t ab type tableau [ [ l , n ] ] de ent i e r renvoie le prochain entier.
import j ava . ut i l . * ;
class TabAléa {
L'algorithme d'initialisation suivant utilise la fonction r a n dom, qui renvoie un entier tiré
I * * Invariant de classe TabAléa représen te une s u i t e
de façon aléatoire.
* aléat oire de n en t i ers
Algorithme I n i t i a l i s at i on a l é a t o i r e *I
{Rôle : i n i t i al i se le tabl eau tab de façon a l éa t oi re } int [ ] t ab ;
variable i type entier
i +- 0 I * * Rôle : créer les n composan t s du tabl eau t ab
répéter * et i n i t i a l i ser t ab de façon aléat o i re
{Vi E [ 1 , i ] , tab [ i ] est i n i t ia l i sé aléatoiremen t } *I
i +- i + l TabAl é a ( int n )
t ab [ i ] +- random ( ) { random renvoie u n n b a l é a t o i remen t } {
jusqu' à i=n Il créer un généra teur de n ombres a l é a t o i res
{Vi E [1 , n ] , tab [i ] est i n i t ia l i sé aléatoiremen t } Random rand = new Random ( ) ;
Il créer les n composan t s du t ableau
tab = new int [ n ] ;
int i=O ;
L'initialisation du tableau est un parcours systématique de tous les éléments, du premier do
au dernier. Le nombre d'itérations est donc connu à l' avance. Remarquez que les énoncés Il Vi E [0, i -1 ] , tab [i ] est ini t ia l i sé aléatoiremen t
itératifs répéter et tantque ne sont pas vraiment adaptés puisqu'ils réclament un prédicat tab [ i+ + ] = rand . next i n t ( ) ;
d' achèvement. Nous verrons au prochain chapitre comment l'énoncé itératif pour offre une while ( i <n ) ;
notation simple pour l'écriture d'une telle boucle. Il Vi E [0, n-1 ] , tab [i ] est i n i t i a l i s é aléatoirement
L'algorithme de recherche parcourt le tableau de façon linéaire à partir du premier élément. I * * Antécédent x ent i er à rechercher
-0
0 Si l'élément est trouvé, il renvoie la valeur vrai, sinon il renvoie la valeurfaux. -0
0 * Conséquent renvoie x E t ab
c: c:
=i =i */
0 Algorithme Recherche l i né a i r e 0
"<:t" "<:t" boolean rechercher ( int x )
,..-1
0
{An t écédent : x ent i er à rechercher dans le tabl eau t ab } ,.-1
0
N { Conséquen t : renvoie E t ab } x N
@ @ int i=O ;
....... variable i type ent i e r .......
..c: ..c: do
O'l i +- 1 O'l
·c ·c / / V i E [ 0, i -1 J , x:f: t ab [ i J
> répéter >
Cl. Cl. if ( t ab [ i + + ] ==x ) Il t ro u vé
u
0 Ni E [ 1 , i -1 ) , x -:f-tab [i ] } u
0
return true;
si t ab [ i ] =x { t rouvé}
while ( i < t ab . l engt h ) ;
alors rendre vrai
Il Vi E [0, tab . length-1 ] , x:f: t ab [i ]
sinon i +- i+l
return false;
finsi
jusqu' à i=n
Il fin classe TabAléa
Ni E [ 1 , n] , x-:f-t ab [ i ] }
rendre faux
L'expression i + + incrémente la variable i de un, et renvoie comme résultat la valeur de
i avant l ' incrémentation. N'oubliez pas qu'en JAVA une affectation est une expression. La
9.7 Les chaÎnes de caractères 1 01 1 02 Chapitre 9 • Les tableaux
comparaison ( t ab [ i ++ ] == x ) compare donc t ab [ i ] à x avant l ' incrémentation de i . De plus, le langage définit l 'opérateur + qui concatène deux chaînes 3 , et d'une façon
Notez aussi que 1 ' expression + + i renvoie comme résultat la valeur de i après incrémentation. générale deux objets munis de la méthode t o S t r ing.
Le fragment de code suivant déclare deux variables de type St ring et met en évidence
les méthodes données plus haut :
9.7 LES CHAÎN ES DE CARACTÈRES S t r i n g c l = new S t r ing ( " b o n j o u r._. " ) ; // ou String cl "bonj our ";
S t ring c2 ;
Dans les chapitres précédents, nous avons déjà manipulé des chaînes de caractères sans
connaître exactement la nature de cet objet. Nous savons qu'une constante chaîne se dénote c2 = c l + " à ._. t o u s " ;
entre deux guillemets : S y s t em . out . p r i nt l n ( c 2 ) ;
S y s t em . out . println ( c 2 . l ength ( ) ) ;
"toto" " b on j ou r à t ou s " S y s t em . out . print l n ( c 2 . charAt ( 3 ) ) ;
" l ' ét é " S y s t em . out . println ( c 2 . indexüf ( ' o ' ) ) ;
S y s tem . out . p r i n t l n ( c l . compa reTo ( " à._. t o u s " ) ) ;
et se repose sont des palindromes. Écrivez une fonction qui teste si une chaîne de caractères Écrivez et démontrez la validité de la méthode produit S c a l a i r e qui renvoie le produit
est un palindrome. scalaire du vecteur transmis en paramètre et du vecteur courant. On vérifiera que les deux
vecteurs possèdent bien la même dimension. On rappelle que le produit scalaire de deux
Exercice 9.4. On désire rechercher tous les nombres premiers compris entre 2 et une cer vecteurs v et v ' est égal à :
taine valeur maximale n, selon l'algorithme du crible d' ÉRATOSTHÈNE 4 . Le crible est la
structure qui contient la suite d'entiers ; il est représenté habituellement par un tableau. n
'
Écrivez et démontrez la validité d'une procédure qui affiche sur la sortie standard les '""""
V . V ' = � Xi X i
i= l
nombres premiers compris entre 2 et n selon l' algorithme :
Algorithme Crible d' É R A T O S T H È N E
init i a l i s er l e crible
vide r faux
répéter
{le plus pet i t nombre con t en u dans l e crible est premier}
- a f f i cher ce nombre s u r l a s o r t i e st andard
- l ' enlever du c r ib l e avec tous ses mul t i p l e s
si l e c r i b l e e s t v i d e alors
vide r vrai
fin si
jusqu' à vide
Notez que les éléments du tableau peuvent être simplement des booléens, puisque seule la
présence ou l ' absence du nombre dans le crible est significative.
Exercice 9.5. On cherche à définir une classe pour représenter des vecteurs de la forme
v = [x1, x2, x3 , . , Xn] . Les composantes réelles de chaque vecteur seront représentées par
. .
� t,
0 0
N N
@ @
.......
xf
.......
..c:
O'l ll v ll = ..c:
O'l
·c ·c
> >
Cl. Cl.
0 0
u u
et que la normalisation d'un vecteur est donnée par :
4. Mathématicien et philosophe, connu pour ses travaux en arithmétique et en géométrie, ÉRATOSTHÈNE vécut
au Ille siècle avant J.-C. à Alexandrie.
1 06 Chapitre 10 • L'énoncé itératifpour
Chapitre 1 0 Avec cet énoncé, l'algorithme d'initialisation d'un tableau, donné à la page 99, s'écrit
simplement :
Algorithme I n i t i a l i s at i o n a l é a t o i r e
{Rôle : i n i t i a l i s e le tableau tab de façon a l éa t oi re }
L'énoncé itératif pour pourtout i de 1 à n faire
{Vk E [ l , i -1 ] , tab [k] est i n i t i a l i s é aléatoiremen t }
{random renvoie un nombre de façon aléatoire }
tab [ i ] +--- random ( )
finpour
{Vi E [ l , n ] , tab [ i ] est i n i t i a l i s é aléatoiremen t }
La règle de déduction de l'énoncé pour restreint dans le cas d'un parcours croissant des
Il arrive souvent que l'on ait besoin de faire le même traitement sur toutes les valeurs valeurs de l ' interva11e est donné ci-dessous. La fonction pred renvoie Je prédécesseur d'un
d'un type donné. Par exemple, on désire afficher tous les caractères contenus dans le type élément dans 1 ' interva11e ]min , max] .
caractère du langage de programmation avec lequel on travaille. Beaucoup de langages de Ex
programmation proposent une construction adaptée à ce besoin spécifique, appelée énoncé si { v = min et P } � {Qv rn;,J et { Qpred(vx ) } :=; {Qvx } Vx E ]min, max]
itératif pour. Une forme générale de cette construction est un énoncé qui possède la syntaxe alors
Depuis sa version 5.0, le langage propose une forme généralisée de l 'énoncé pour, appelé Par exemple, le polynôme 3x3 «
- 2x2 + 4x + 1 est récrit » sous la forme :
foreach, lorsqu'il s'agit d' appliquer un même traitement à tous les éléments d'un tableau ou
d'une collection 1 • Cet énoncé possède la syntaxe suivante : ( (3x - 2)x + 4)x + 1
for (T X : TC) E
L' invariant de l'algorithme du calcul de ]a valeur p(x ) , par 1e schéma de HORNER,
Il exprime que la variable x prendra successivement les valeurs (de type T) de tous les valeur = I:�=n cf [k] xk - i, se démontre aisément par application de la règle de déduction
éléments du tableau ou de la collection TC, et pour chacune de ces valeurs, l'énoncé E sera donnée plus haut.
exécuté. On écrira, par exemple, la méthode t o S tr i n g de notre classe TabA l éa comme
Algorithme H 0 R N E R
suit :
{Rôle : cal cul de l a valeur d ' un polynôme
I** Rôle renvoie l a représen t a t i on sous forme de chaîne de de degré n à une variable
* caractères de l ' objet courant de type TabAléa x et le t ableau des coeffi c i en t s cf}
-0
*I -0 valeur +-- cf [ n ]
n
0
c: public St r i n g t o S t r ing ( ) 0
c: { valeur = "I:,�= n cf[k]xk - = cf[n ] }
=i =i
String s = " "
0 ; 0 pourtout i de n - 1 à 0 faire
{ valeur = "I:, � =n cf[k ] xk - i }
"<:t" for ( int x : tab) "<:t"
,..-1 ,.-1
0 0
s+=x + '
Il Il .
N � N v a l e u r +-- v a l e u r X x + c f [ i )
@ return s ; @
....... ...... finpour
{ va l e ur = "I:, � =n cf[k]xk }
..c: ..c:
O'l O'l
·c ·c
> > rendre valeur
Cl.
0
Notez que s ' il est nécessaire de faire une modification sélective d'un élément de tableau Cl.
0
u dans le corps de la boucle, cet énoncé foreach ne peut être utilisé. Ainsi, le code ci-dessous u
n' initialise pas à 1 les éléments du tableau t . C'est la variable x, locale à l 'énoncé itératif, à
qui est affectée la valeur 1 à chaque itération. Prog ram mation en J AVA
int t [ ] = new int [ l O J ;
La programmation en JAVA de l'algorithme précédent est donnée ci-dessous. On considère
for ( int x : t ) x= l ;
que Je tableau cf est un attribut de ]a classe dans 1aque1le la fonction Borner a été définie.
Il les é l emen t s de t ne sont pas i n i t i a l i sés à 1
2. Redécouverte au début du XIXe siècle par l ' anglais W. HORNER, cette méthode est due au mathématicien
l . Voir j ava . ut i l . C o l l e c t i on. chinois CHU SHIH-CHIEH, 500 ans plus tôt.
10.4 Exemples 1 09 110 Chapitre 10 • L'énoncé itératifpour
I** Rôle cal cul de la valeur d ' un polynôme Algorithme Tri p a r s é lection ordinaire
* de degré n à une variable {Rôle : Trie par sélec t i on ordinaire en ordre croi ssant }
*I { les n valeurs d ' un tabl eau t }
public double Horne r ( double x ) { pourtout i de 1 à n - 1 faire
int n=c f . length- 1 ; { In variant : l e s o us-tableau de t [l ] à t [i - 1 ) est trié
double valeur=cf [ n ) ; et ses élémen t s son t inférieurs o u éga ux
Il valeur = 2:,�=n cf[k ] xk - n = cf[n] a ux élémen t s t [i ] à t [n ] }
for ( int i=n- 1 ; i>=O ; i--) min +-- i
Il val e ur = 2:,�=n cf[k]xk - i { chercher l ' indi ce du minimum sur l ' in t erva l l e [i , n ] }
val eur = valeur*x+c f [ i ] ; pourtout j de i + l à n faire
si t [ j ] < t [ mi n ] alors min +-- j finsi
Il valeur = 2:,�=n cf[k]xk
return val eu r; finpour
{échanger t [i ] et t [min ] }
t [i] B t [m i n ]
finpour
{ l e tabl eau de t [l ] à t [n ] est t ri é }
1 0.4.2 Un tri interne simple
Une primitive de tri consiste à ordonner, de façon croissante ou décroissante, une liste
d'éléments. Par exemple, si nous considérons la liste de valeurs entières suivante : Programmation en J AVA
53 914 827 302 631 785 230 11 5 67 350 La procédure suivante programme l'algorithme de tri par sélection ordinaire. Remarquez
une opération de tri ordonnera ces valeurs de façon croissante et retournera l a liste : les déclarations des variables min et aux dans le corps de la boucle for la plus externe.
11 53 230 302 350 567 631 785 827 914 I** Rôle : Trie par sél ect i on ordinaire en ordre croissan t
Nous présentons une méthode de tri simple, appelée tri par sélection ordinaire. Ce tri est * les val eurs du tabl eau t
*I
dit interne car l 'ensemble des éléments à trier réside en mémoire principale, dans un tableau.
public void s é lecti onOrdinaire ( int [] t) {
Il existe de nombreuses méthodes de tri, plus ou moins performantes, que nous étudierons en
for ( int i = O ; i <t . l ength- 1 ; i++) {
détail au chapitre 23 page 321.
Il Invariant : l e sous-tableau de t [ OJ à t [i - 1 ) est trié
Le principe de la sélection ordinaire est de rechercher le minimum de la liste, de le pla Il et ses élément s sont inférieurs o u éga ux
cer en tête de liste et de recommencer sur le reste de la liste. En utilisant la liste d'entiers Il a ux élémen t s t [i ] à t [t . l en gth-1 )
précédente, le déroulement de cette méthode donne les étapes suivantes. À chaque étape, le int min= i ;
minimum trouvé est souligné. Il chercher l ' i ndice d u minimum sur l ' i n t erva l l e [i, t . l ength]
-0 -0 for ( int j = i ; j <t . length; j++)
0 0
c:
=i
1 53 914 827 302 631 785 230 11 5 67 350 c:
=i if ( t [ j ] <t [m i n ] ) min= j ;
0 11 1 914 827 302 631 785 230 53 5 67 350 0
Il échanger t [i ] et t [min]
"<:t" 11 53 1 827 302 631 785 230 914 5 67 350 "<:t"
,.-1 ,..-1
0 0 int aux=t [ i ] ; t [ i ] =t [m i n ] ; t [ min ] =aux;
N 11 53 230 1 302 631 785 827 914 567 350 N
}
@ 11 53 230 302 1 631 785 827 914 5 67 350 @
Il l e tabl eau de t [O ] à t [t . l en gth-1 ) est trié
....... .......
.c: 11 53 230 302 350 1 785 827 914 5 67 631 ..c:
O'l O'l
·c 11 53 230 302 350 5 67 1 827 914 785 631 ·c
> >
Cl. Cl.
u
0 11 53 230 302 350 5 67 631 1 914 785 827
8 1 0.4.3 Confrontation de modèle
11 53 230 302 350 5 67 631 785 1 914 827
11 53 230 302 350 5 67 631 785 827 1 914
La confrontation de modèle (en anglais « pattern matching ») est un problème classique
L' algorithme de tri suit un processus itératif dont l'invariant spécifie, d'une part, qu' à la de manipulation de chaînes de caractères. Il s' agit de rechercher la position pos d'une oc
je étape la sous-liste formée des éléments de t [ 1 ] à t [ i -1 ] est triée, et, d'autre part, que currence d'un mot (le modèle) de longueur lgmot dans un texte de longueur lgtexte. Le texte
tous ses éléments sont inférieurs ou égaux aux éléments t [ i ] à t [ n ] On en déduit que le . et le mot sont formés de caractères pris dans un alphabet �- L'idée générale est de compa
nombre d'étapes est n - 1 . rer de manière répétée le mot à une portion du texte, appelée fenêtre, possédant le même
nombre de caractères que le mot recherché. Une occurrence est trouvée lorsque la fenêtre et
le mot sont identiques.
10.4 Exemples 111 112 Chapitre 1 0 • L'énoncé itératifpour
La premjère méthode qui vient immédiatement à l'esprit est de comparer le mot à toutes les placée immédiatement après cette lettre. La comparaison entre le n et le r échoue. La lettre
fenêtres possibles du texte, en commençant par le premier caractère du texte, puis le second, n est présente dans le mot, 1 ' ajustement produit un déplacement de trois caractères. Enfin, le
etc. jusqu'à trouver une concordance entre le mot et une fenêtre. Cet algorithme est donné mot et la fenêtre sont identiques.
ci-dessous. Notez qu'il ne dépend pas de l' alphabet L::. La fonction é g a l renvoie la valeur
L'algorithme donné ci-dessous renvoie l' indice dans le texte du premier caractère du mot
vrai si le mot est égal à la fenêtre de position po s dans le texte.
recherché s'il est trouvé, sinon il renvoie la valeur 1 Nous représentons le mot et le texte
Algorithme n a ï f
- .
par deux tableaux de caractères. La variable p os indique la position courante dans le texte.
pos +-- 1
Nous traiterons le calcul de la valeur du déplacement plus loin.
tantque pos < l g t e x t e - l grnot faire
si égal (rnot , t e xt e , p o s ) alors Algorithme Boyer-Moore
{ l e mot est à l a posi t i on pas dans l e t ext e } variables pos , i, j de type nat u r e l
rendre p o s variable d i f férent de type booléen
sinon
{ dépl acer l a fenêtre d ' une pos i t i on } pos +--- 1
pos +-- pos+l tantque pos � l gt exte- l gmo t + l faire
finsi d i f f é rent +--- faux
fintantque { On compare à partir de l a fin du mot e t de l a fen ê t re }
{ l e mot n ' a pas é t é trouvé dans l e t ext e } i +--- lgmot
j +--- p o s + l gmot - 1
répéter
Cette méthode n'est pas très efficace car elle teste toutes les positions possibles du mot si mot [ i ] =text e [ j ] alors
dans le texte. Une seconde méthode beaucoup plus efficace, proposée par BOYER et MOORE, { égal i t é � on poursui t l a comparaison}
exploite deux idées 3 : i +--- i - 1
j +--- j - 1
- On peut comparer un mot à une fenêtre en commençant par les caractères situés à leurs sinon { di fféren ce � on s ' arrê t e }
extrémités. di f f é rent +--- vrai
- Après un échec, au lieu d' avancer d'un caractère, il est possible de faire un saut plus finsi
jusqu' à i = O ou di f fé rent
important. Si le dernier caractère testé dans la fenêtre n'est pas présent dans le mot,
si i=O alors { l a comparaison a réussi, renvoyer la pos i t i on }
on peut déplacer la fenêtre immédiatement après ce caractère. Sinon, on fait le plus
rendre pos
court déplacement qui aligne le dernier caractère testé de la fenêtre avec un caractère sinon { échec : dépl acer l a fenêtre vers l a droi t e }
identique du mot. p o s +--- pos + { va l e ur d u déplacement }
finsi
Si, par exemple, nous recherchons le mot noir dans le texte anoraks noirs, les différentes
-0 -0 fintantque
0
c: comparaisons et les déplacements d produits lors des échecs sont donnés ci-dessous : 0
c: { l e mot n ' a pas é t é t rouvé dans l e t ext e }
=i =i
0 0 rendre - 1
"<:t" a n o r a k s n o i r s "<:t"
,.-1 ,..-1
0 0
N N
@ @
....... ....... Lorsque l a comparaison entre le mot et l a fenêtre courante a échoué, c'est-à-dire lorsque
..c: n 0 i r échec ==> d = 1 ..c:
O'l O'l mot [ i ] :;itexte [ j ] , il faut déplacer la fenêtre vers la droite. Une façon de procéder est de
·c ·c
> >
Cl. n 0 i r échec ==> d = 4 Cl. chercher un caractère mot [ k ] égal au caractère t exte [ j ] , et de produire le saut qui les
0 0
u n 0 i r échec ==> d = 3 u place l'un en face de 1' autre. La figure 1 0. 1 montre les trois cas quj peuvent se présenter : ( 1 )
n 0 i r succès ce caractère n'est pas présent dans l e mot ; (2) il apparaît dans l e mot avant t e x t e [ j ] ; (3)
il apparaît dans le mot après te xte [ j ] .
Après le premier échec, l'alignement des deux lettres o provoque un déplacement d'un
Il est important de noter que le calcul du déplacement ne dépend que du mot, mais nécessite
caractère. Après le deuxième échec, et puisqu'il n'y a pas de a dans le mot, la fenêtre est
la connaissance de l'alphabet �. On pose ts(c) l'indice du caractère c le plus à droite dans
3. Il en existe une troisième qui tient compte de suffixes déjà reconnus dans le mot, mais qui ne sera pas évoquée mot ; si c � mot alors ts(c) = O. La valeur du déplacement à produire est alors égale à
ici. i ts(texte[j]) . Notez que cette valeur peut être inférieure ou égale à zéro (troisième cas).
-
Plutôt que de provoquer un retour en arrière, on décale la fenêtre vers la droite d'un caractère.
10.4 Exemples 113 1 14 Chapitre 1 0 • L'énoncé itératif pour
..c:
O'l Il éga l i t é =? on poursui t l a comparai son ..c:
O'l linéaire O(n). Si on multiple par 100 le nombre d'éléments à trier, le programme utilisera un
·c ·c
> i-- ,· > tableau 100 fois plus grand.
Cl. Cl.
0 j--; 0
u u Quelle est la signification de la notation 0 ( n2 ) utilisée plus haut et pourquoi ne considère
if (i<O) Il la compara i son a réu s s i , renvoyer pos t-on que le terme n2 alors que le nombre exact de comparaisons est � (n2 - n) ?
return p o s ; Soient deux fonctions positives f et g, on dit que f(n) est O(g(n)) s'il existe deux
else I l échec : dépl acer l a fenêtre vers l a droi t e constantes positives c et n0 telles que f (n) � cg(n) pour tout n � n0 . L'idée de cette
pos +=Math . max ( l , i - t s [ t e xt e . cha rAt ( j ) ] ) ;
définition est d'établir un ordre de comparaison relatif entre les fonctions f et g. Elle indique
}
qu'il existe une valeur n0 à partir de laquelle f (n) est toujours inférieur ou égal à cg(n). On
Il l e mot n ' a pas été t rouvé dans l e texte
return - 1 ;
dit alors que f (n) est de l 'ordre de g(n). La figure 10.2 donne une illustration graphique de
cette définition.
10.5 Complexité des algorithmes 115 116 Chapitre 1 0 • L'énoncé itératif pour
temps d'exécution L'intérêt de la notation 0 est d'être un véritable outil de comparaison des algorithmes.
ou encombrement mémoire cg(n) ,/
'
Par exemple, si, pour effectuer un tri, nous devons choisir entre le tri par sélection dont la
complexité, nous venons de le voir, est O(n2 ) et le tri en tas (voir la section 23.2.2, page 324)
'
'
de complexité O(n log2 n), notre choix se portera sur le premier parce que n est petit et que
le tri par sélection est simple à mettre en œuvre, ou alors sur le second parce que le nombre
d'éléments à trier est tel qu'il rend le premier algorithme inutilisable.
f(n)
1 0.6 EXERCICES
On désire effectuer des calculs sur des entiers naturels trop grands pour être représentés à
l 'aide du type prédéfini ent i e r . On définit pour cela un type nouveau GrandEnt i e rNat.
Un grand entier naturel est divisé en chiffres, exprimés dans une base b. Les chiffres sont
élément caractéristique mémorisés dans un tableau, de telle façon que la valeur d'un nombre a de n chiffres est
de l'algorithme égale à :
L [Link][i]
=
a = x bi
Montrons que la fonction � (n2 - n) est de l' ordre de O(n2 ). La définition de la notation 0 i =O
nous invite à rechercher deux valeurs positives c et n0 telles que � ( n2 - n) � en2 . En prenant
par exemple c = �' il est évident que n' importe quel no > 0 vérifie l' inégalité. Remarquez Exercice 10.1. Définissez la classe GrandEnt i e rNat avec les attributs nécessaires à la
que nous aurions pu tout aussi bien écrire que � (n2 - n) est O(n3) ou O(n5), mais O(n2 ) représentation d'un grand entier.
est plus précis. D'une façon générale, dans la notation f(n) = O(g (n)), on choisira une Exercice 10.2. Écrivez quatre constructeurs qui initialisent un grand entier, respectivement,
fonction g la plus proche possible de f, en respectant les règles suivantes : à zéro, à la valeur d'un entier passé en paramètre, à la valeur entière définie par une chaîne
cf (n) = O(f(n)) pour tout facteur constant c.
- de caractères passée en paramètre, à la valeur d'un GrandEnt i e rNat passé en paramètre.
f(n) + c = O(f(n)) pour tout facteur constant e.
-
Exercice 10.3. Écrivez les quatre opérations élémentaires, addition, soustraction, multipli
f ( n) + g (n) = O(max (f (n) , g (n)) ).
-
cation et division.
f(n) x g (n) = O(f(n) x g (n)).
-
- si f(n) est un polynôme de degré m , f(n) = ao + ain + a2 n2 + . . . + a nm, alors Exercice 10.4. Écrivez les fonctions de comparaison qui testent si deux grands entiers sont
m
-0 f (n) est de l'ordre du degré le plus grand, i.e. O(nm). -0 inférieurs, inférieurs ou égaux, supérieurs, supérieurs ou égaux, égaux ou différents.
0
c: n
nm = O(c ) pour tout m > 0 et c > 1 . 0
c:
=i =i
0 - log nm = O(log n) pour tout m > O. 0 Exercice 10.5. Écrivez une fonction factorielle et calculez 32!
"<:t" "<:t"
,..-1
0
- log n = O(n) ,..-1
0 Exercice 10.6. Calculez la somme l: k l/k! pour k variant de 1 à 32. Cette somme est égale
N N
@ En appliquant ces règles, on en déduit facilement que n6 + 3n3 5 est de l'ordre de
-
@ à la base e des logarithmes népériens.
...... .......
..c: O(n6), ou encore que 3n3 + n log n est de l'ordre de O(n3). Le tableau suivant présente des ..c:
O'l
·c fonctions, et les termes employés pour les désigner, qui interviendront souvent dans l'analyse
O'l
·c Exercice 10.7. Donnez la notation 0 des fonctions n3 log n + 5, 2n 5 1 2 et 2n.
> >
Cl. Cl.
0 de la complexité des algorithmes que nous étudierons par la suite. 0 Exercice 10.8. Comment qualifiez-vous la fonction n log 2 n ? l inéaire ou logarithmique ?
u u
constante Exercice 10.9. Montrez que 2n+2 est 0 (2n) et que (n + 2 )4 est O(n4).
logarithmique Exercice 10.10. Quelles sont les complexités temporelles et spatiales de l'algorithme de
n linéaire calcul de la valeur d'un polynôme selon le schéma de HORNER ?
n2 quadratique
n3 cubique Exercice 10. 1 1 . Recherchez la ke plus grande valeur d' une suite quelconque d'entiers lus
en (Ve > 1 ) exponentielle sur l'entrée standard. Proposez deux algorithmes en O (n 2 ) . Notez qu'il existe une méthode
dont la complexité est 0 ( n log 2 n) (voir le chapitre 22).
10.6 Exercices 117
Exercice 10.13. Dans l'analyse d'un algorithme, on est bien souvent amené à distinguer
trois complexités. La complexité la meilleure, lorsque les données sont telles que l 'algorithme
a les meilleures performances, la complexité la pire, dans le cas défavorable où les données
conduisent aux performances les moins bonnes, et enfin la complexité moyenne dans le cas
général. Donnez les trois complexités temporelles de l'algorithme de BOYER-MOORE.
Exercice 10.14. La méthode des rectangles permet d'approximer l 'intégrale d'une fonc
tion f continue. Cette méthode consiste à découper en n intervalles [ai , ai+ 1 ] , de longueur
identique m, l' intervalle [a , b] sur lequel on veut intégrer la fonction f, puis à additionner
l'aire des rectangles de hauteur f ((ai + ai+ i)/2) et de largeur m. L'aire A, approximation
de l' intégrale de f sur l 'intervalle [a , b], vaut donc :
n
A= Lm x J ( (ai + aH 1 )/2)
i=l
La figure suivante illustre la méthode des rectangles dans le cas où n = 3.
f(x)
-0
0
c:
=i
0
"<:t'
,.-1
0 X
N a <- - - - - - - - :;... b = a + 3m
@ m
....... .µ
..c: ..c
O'l
·c
Programmez en JAVA une méthode qui calcule l'intégrale de la fonction cosinus sur un in Ol
ï:::
> tervalle [a, b] . Testez votre méthode, par exemple, sur l'intervalle [O, 7r /2] ; quel est le nombre >-
Cl. 0.
0 0
u d'intervalles nécessaires pour obtenir un résultat exact à la cinquième décimale ? u
constantes
m = 10
n = 20
variable
mat r i c e type tableau [ [ 1 , m] , [1,n] ] de r é e l s
Les opérations sur les tableaux à plusieurs dimensions sont identiques à celles sur les 1 1 .6.1 Initialisation d'une matrice
tableaux à une dimension (voir la section 9.4 page 97).
Soit la déclaration de la matrice à m lignes et n colonnes suivante :
variable
On désire initialiser tous les éléments de la matrice à une valeur réelle v. Pour cela, il est
Les tableaux à plusieurs dimensions sont traités comme des tableaux de tableaux. Le
nécessaire de parcourir toutes les lignes, et pour chaque ligne toutes les colonnes, à l'aide de
nombre de dimensions peut être quelconque et les règles pour leur déclaration et leur création
deux énoncés itératifs pourtout emboîtés.
sont semblables à celles données dans la section 9 5 page 97. On déclare un tableau t à n
Algorithme i n i t
.
répéter constantes
l +--- l + l m ? {nombre l i gnes de l a ma trice a }
c +--- 1 n = ? {nombre l i gnes de la ma tri ce b et
répéter nombre colonnes de l a ma t rice a }
si mat r i c e [ l , c ] # mat r i c e [ c , l ] alors p =
? {nombre colonnes de la ma tri ce c }
p a s s ymét r i que +--- vrai variables
sinon a type tableau [ [ l , m ) , [ l , p ] J de réel
c +--- c+l b type tableau [ [l , p) , [ l , n) J de r é e l
finsi c type tableau [ [ l , m) , [ l , n ) ) de réel
= l ou p a s s ymét ri qu e
jusqu ' à c s omme type réel
{passymétrique ou Vi, j E [l , l ] , ma tri ce [i, j ] =matrice [j, i ] }
jusqu' à l = n ou p a s s ymét r i que pourtout i de 1 à m faire
{passymétrique ou Vi, j E [ 1, n ] , ma tri ce [ i, j] =ma t ri ce [ j, i ] } pourtout j de 1 à n faire
rendre non p a s symétrique Nx E [l , i -1 ] , 'lly E [l , j -1 ] , c [x, yJ ='L,�=l a [x, k] X b [k, y] }
s omme t-- 0
pourtout k de 1 à p faire
On donne en JAVA la programmation de la fonction symé t r i que qui teste une matrice s omme t-- s omme+a [ i , k ) X b [ k , j )
passée en paramètre. Notez la suppression de la variable booléenne pas symét r i que par finpour
l'utilisation de l' instruction return. c [i, j J t-- s omme
finpour
I * * Rôle : t e s t e s i une mat rice est symétrique o u n on *I
finpour
public boolean symét r i que ( int [ ] [ ] mat r i c e ) {
int l = 0;
(l/i E [l , m] , 'llj E [l , n ] , c[i, j J ='L,�=l a [i, k] X b [k, j ] }
do {
l++; Notez qu'il existe une autre méthode, celle de STRASSEN, basée sur la décomposition de
int c = 0; la matrice en quatre quadrants, de complexité inférieure à O(n3 ) .
do
if ! = mat r i ce [ c ] [ l ] )
(mat r i c e [ l ] [ c ]
Il l a ma t ri ce n ' es t pas symét ri que
1 1 .6.4 Carré magique
return false;
On appelle carré magique 2 une matrice carrée d'ordre n, contenant les entiers compris
c++;
entre 1 et n 2, telle que les sommes des entiers de chaque ligne, de chaque colonne et des
while (c < l) ;
II V i , j E [ 0, 1 ] , ma trice [ i , j ] =mat ri ce [ j , i] deux diagonales sont identiques. La matrice suivante est un carré magique d'ordre 3 :
while ( 1 < mat r i c e . l ength- 1 ) ;
-0
0
-0
0
4 9 2
c: Il Vi, j E [O, matrice . length-1 ) , ma trice [i, j ] =ma t ri ce [j, i ] c:
=i Il l a ma tri ce est symétri que =i 3 5 7
0 0
"<:t" return true; "<:t" 8 1 6
,..-1 ,..-1
0 0
N N
Nous présentons une méthode de complexité O(n2) pour créer des carrés magiques
@ @
.......
..c:
.......
..c: d'ordre impair. Le chiffre 1 est mis dans la case située une ligne en dessous de la case
O'l O'l
·c 1 1 .6.3 Produit de matrices ·c centrale. Lorsqu'on a placé un entier x dans une case de coordonnées (i, j ) , on place x + 1
> >
Cl.
0
Cl.
0 dans la case (l, k) = (i + 1 , j + 1 ) . Cependant, ces coordonnées ne sont pas nécessairement
u u valides. Si un indice est égal à la valeur n + 1, on lui affecte la valeur 1 ; et si la case est
Soient trois matrices a( m, p), b(p, n) et c ( m, n), on désire programmer le produit matriciel
c = a x b. Rappelons que les éléments de la matrice c sont tels que : déjà occupée, alors on place le nombre en (l + 1 , k 1 ) , mais si k 1 = 0 alors k prend la
- -
valeur n. Il est remarquable que si le premier calcul de coordonnées correspond à une case
p
Vi E [l, m] , Vj E [l, n] , Cij = L aik bkj 2. Les carrés magiques sont très anciens, puisqu'on trouve leur trace, il y a près de 3 000 ans, sur la carapace
k= l d'une tortue dans la légende chinoise de Lo SHU. En Europe, le premier carré magique apparaît en 1 5 1 4 sur une
gravure du peintre allemand A. DÜRER. Si durant de nombreux siècles ces carrés étaient attachés à des superstitions
L'algorithme suit précisément cette équation, qui sera l' invariant de boucle. Il possède trois divines, à partir du xvnc siècle, ils ont fait l'objet de nombreuses études mathématiques.
énoncés pourtout emboîtées et sa complexité est O(n3).
1 1.6 Exemples 125 1 26 Chapitre 1 1 • Les tableaux à plusieurs dimensions
déjà occupée, le calcul suivant conduit toujours à une place libre. Écrivons formellement Il si oui , trouver une place libre
l'algorithme selon cette méthode. Une case libre a pour valeur O. if ( ca r r é [ i ] [ j ] ! = 0 ) {
if ( ++i==n ) i = O ;
Algorithme Carré Magique
if ( -- j < O ) j =n- 1 ;
{on consi dère le carré initialisé à O J
{écrire l e premier nombre dans l a première case } assert carré [ i ] [ j ] == O ;
j +-- ( n+ l ) / 2
Il carré [i] [j] est l a place de l ' en tier k
i +-- j + l
carré [ i ] [ j ] =k ;
carré [ i , j J +-- 1
{pl acer les nombres suivants de 2 à n2 }
pourtout k de 2 à n2 public String t oS t ring ( ) {
{ ca l culer les prochaines coordonnées (i+l , j+l) } String s=" " ;
i +-- i + l
for ( int [] l i gne : carré )
s i i > n alors i +-- 1 finsi
for ( int x l i gn e )
j +-- j + l
S += X + ,
Il Il .
Exercice 1 1 .3. En utilisant les méthodes précédentes, écrivez les méthodes s ymé t r i que - choisir le pivot ;
et produit dont les algorithmes sont décrits plus haut. La première méthode teste la symé - normaliser la ligne, c'est-à-dire diviser la ligne k du système par le pivot (i.e. les akj
trie de la matrice courante, et la seconde renvoie une matrice (i.e. de type Mat rice) produit et bk) ;
de la matrice courante et d'une seconde passée en paramètre. Pour que le produit de deux ma - pour toute ligne i du système allant de k + 1 à n, lui soustraire la ligne de rang k
trices soit valide, vous vérifierez si le nombre de colonnes de la première est égal au nombre multipliée par a ik .
de lignes de la seconde. Notez que ces opérations ne modifient évidemment pas la solution du système d'équations.
«
Celle-ci se calcule par substitution en remontant » à partir de la dernière équation : Xn = b�,
Exercice 1 1.4. Écrivez une méthode qui renvoie le vecteur résultat du produit de la matrice
X n - 1 = b�_ 1 - a�_1 n X n , etc. L'algorithme de C . F. GAUSS a la forme suivante :
courante par un vecteur passé en paramètre. On rappelle que le produit d'une matrice a de '
dimension m x n et du vecteur v de dimension n est le vecteur v' de dimension m dont les Algorithme Gau s sRSL ( données A , B résultat X )
composantes sont définies par : {Antécédent A , B données d u système l inéaire}
n { Conséquent : X sol ution du système AX=B}
v� = L
ai k x vk
k=l { triangulari sation de l a matrice A}
pourtout k de 1 à n faire
Exercice 1 1 .5. Soit le système linéaire de n équations à n inconnues suivant : { A [k, k] est l e pivo t }
pivot +- A [ k , k ]
{normaliser la ligne k de la ma tri ce A e t }
ainXn = bi { du vecteur B par le pi vot t e l que A [k, k ] =l }
a2nXn = b2
pourtout i +- k + l à n faire
{soustraire à l a ie l i gne de la mat ri ce A e t }
{ du vecteur B l a l i gne k mul t ipliée par A [i , k] }
On représente ce système par l'équation AX = B, où A est la matrice des coefficients aij finpour
et B est le vecteur des coefficients bi . finpour
La résolution de ce système linéaire par la méthode de GAUSS 4 se fait en deux étapes. La X [k] +- s o l
-0 -0
0 0 finpour
c: première transforme la matrice du système en une matrice triangulaire avec seulement des c:
=i =i
0 1 sur sa diagonale. La seconde calcule par substitution les solutions du système, à partir de 0
"<:t" "<:t"
,.-1 la matrice triangularisée, en partant de la dernière équation jusqu'à la première. Ainsi, après ,.-1
0
N
0
N Complétez l'algorithme précédent, avec dans un premier temps, akk comme valeur de
triangularisation, le système est transformé en un système équivalent : pivot.
@ @
....... .......
..c: ..c:
O'l I O'l Que se passe-t-il si le pivot akk est nul ? Une solution est de chercher un pivot avk i=- 0
·c 1 ai 2 b'1 ·c
>
Cl.
>
Cl. avec k + 1 � v � n, puis, d'échanger la ligne v qui contient ce pivot avec la ligne k. Notez
0 0 1 b'2 0
u u que si on ne peut trouver de pivot différent de zéro, le système est lié et n'admet pas une
0 solution unique. D'une façon générale, pour diminuer les risques d' erreurs dans les calculs
0 0 1 b'n numériques, on choisit le pivot le plus grand en valeur absolue entre les lignes k et n. Modifiez
1' algorithme en conséquence. Quelle est la complexité de cet algorithme ?
Pour triangulariser la matrice A, on procède de façon itérative du rang 1 au rang n. À la
ke étape, la sous-matrice ( k - 1 , k - 1 ) est triangularisée et les opérations à effectuer pour Exercice 1 1 .6. On désire calculer l'inverse d'une matrice A. Pour cela, on procède de la
poursuivre la triangularisation sont les suivantes : même manière que dans l'exercice précédent, mais B est la matrice identité. Pour inverser la
matrice A, la méthode de GAUSS doit se poursuivre pour obtenir une double triangularisation
4. C . F. GAUSS, astronome, mathématicien et physicien allemand ( L 777- 1855). (inférieure et supérieure) de A. Le système initial est le suivant :
1 1. 7 Exercices 1 29
1 0 0
0 1 0
0 0 1
1 0 0 bn b1 2 b1 n
0 1 0 b2 1 b22 b2n
0 0 1 bn l bn2 bnn
Écrivez l' algorithme d'inversion d' une matrice.
-0
0
c:
=i
0
"<:t"
,.-1
0
N
@ .µ
...... ..c
..c: Ol
O'l ï:::
·c >-
> 0.
Cl. 0
0 u
u
1 32 Chapitre 12 • Héritage
{le constructeur}
constructeur C a r ré ( donnée c ré e l )
côté f- c
fincons
{ l es méthodes}
{Rôle : ret ourne l e périmètre du carré}
fonction périmètre ( ) : réel
rendre 4 X côté
finfonc {périmètre}
Chapitre 1 2
{Rôl e : retourne la surface du carré }
fonction s u r face ( ) : réel
Vous pouvez constater que cette classe ressemble fortement à la classe Rect angl e . Ceci
est normal dans la mesure où un carré est un rectangle particulier dont la largeur est égale
à la longueur. Aussi, plutôt que de définir entièrement une nouvelle classe, il est légitime de
réutiliser certaines des caractéristiques d'un rectangle pour définir un carré. L'héritage est
une relation entre deux classes qui permet à une classe de réutiliser les caractéristiques d'une
Une des principales qualités constitutives des langages à objets est la réutilisabilité, c'est autre. Nous définirons la classe Carré comme suit :
à-dire la réutilisation de classes existantes. La réutilisation de composants logiciels déjà pro
classe Carré hérite de Re ctangle
grammés et fiables permet une économie de temps et d'erreurs dans la construction de nou
{ Invariant de classe : l ongueur = l argeur � 0 }
veaux programmes. Chaque année, des millions de lignes de code sont écrites et seul un {le constructeur }
faible pourcentage est original. Si cela se conçoit dans un cadre pédagogique, ça l'est beau constructeur C a r ré ( donnée c : ré e l )
coup moins dans un environnement industriel. Des algorithmes classiques sont reprogrammés Rectangl e ( c , c )
des milliers de fois, avec les risques d'erreurs que cela comporte, au lieu de faire partie de fincons
bibliothèques mises à la disposition des programmeurs. La notion d'héritage, que nous allons finclasse Carré
aborder dans ce chapitre, est l'outil qui facilitera la conception des applications par réutilisa
tion. Tous les attributs et toutes les méthodes de la classe Rect angle, la classe parent, sont
-0
0
-0
0
accessibles depuis la classe Ca rré, le descendant. On dit que la classe Carré hérite 1 de la
c: c: classe Rect a n g l e . La classe Carré ne définit que son constructeur, qui appelle celui de sa
=i =i
0 0
classe parent, et hérite automatiquement des attributs, largeur et l o ngueur, ainsi que des
"<:t"
,..-1
0
1 2. 1 CLASSES H É RITIÈRES "<:t"
,..-1
0 méthodes p é r imèt r e et s ur face.
N N
@ Dans l e chapitre 7 , nous avons défini une classe pour représenter et manipuler des rec @ La figure 12. 1 montre de façon graphique la relation d'héritage entre les classes
....... .......
..c: tangles. Si, maintenant, nous désirons représenter des carrés, il nous faut définir une nouvelle ..c: Rectangle et Carré. Chaque classe est représentée par une boîte qui porte son nom et
O'l O'l
·c ·c l'orientation de la flèche signifie hérite de.
>
Cl. classe. Chaque carré est caractérisé par la longueur de son côté, son périmètre, sa surface, etc. >
Cl.
0 0
u La classe pour représenter les carrés est définie comme suit : u La déclaration d'un carré c et le calcul de sa surface sont alors obtenus par :
classe Carré variable c type Carré créer Carré ( S )
{ In variant de classe : côté � O J
public p é rimèt r e , s u r f ac e , côté . . . c . surface ( )
{ l ' a t tribut } La réutilisabilité des classes déjà fabriquées est un intérêt majeur de l'héritage. Il évite une
côté type r é e l perte de temps dans la récriture de code déjà existant, et évite ainsi l ' introduction de nouvelles
Évidemment, les classes héritières peuvent définir leurs propres caractéristiques. La classe
Rectangle Carré possède déjà son propre constructeur, mais pourra définir, par exemple, une méthode
de mise à jour du côté d'un carré.
classe Carré hérite de Re ctangle
{ Invariant de classe : l ongueur l argeur � O J
public changerCôté
{ l e constructeur}
constructeur C a rré ( donnée c ré e l )
Rectangle ( c , c )
Carré fincons
alors la classe C hérite également de A. Dans beaucoup de langages de classe, toutes les
La relation d'héritage peut donc aussi être considérée comme un mécanisme d'extension
classes possèdent un ancêtre commun, une classe souvent appelée Object, qui est la racine de
de classes existantes, mais également de spécialisation. Les informations les plus générales
l'arborescence d'héritage (voir la figure 1 2.2).
sont mises en commun dans des classes parentes. Les classes se spécialisent par 1' ajout de
nouvelles fonctionnalités. Il est important de comprendre que n'importe quelle classe ne peut
Object
étendre ou spécialiser n'importe quelle autre classe. La classe Carré qui hériterait d'une
classe I ndividu n'aurait aucun sens. Le mécanisme d' héritage permet de conserver une
cohérence entre les classes ainsi mises en relation.
0
î î
,..-1
0
,..-1
0
on désire adapter son action à des besoins spécifiques. Imaginons, par exemple, que la classe
N N Rectangle possède une méthode d'affichage, la classe Carré peut redéfinir cette méthode
@ @ pour l 'adapter à ses besoins .
....... .......
..c: ..c:
O'l O'l
·c B F ·c classe Rectangle
> >
Cl. Cl.
0 0
u u {Rôle : a ffiche une description du rectangl e couran t }
î procédure a f f i ch e r ( )
é c r ire ( " re c t angle de l argeu r " , largeur,
" e t de l ongueu r " , longueu r )
c finproc
finclasse Rectangle
-0
0 1 2.3 RECH E RCH E D'UN ATTRI BUT OU D'U N E M ÉTHODE -0
0 Ellipse Rectangle
c: c:
=i =i
0 0
"<:t" S i la méthode que l'on désire appliquer à une occurrence d'un objet d'une classe C n'est "<:t"
,..-1 ,..-1
0
N
pas présente dans la classe, celle-ci devra appartenir à l'un de ses ancêtres. 0
N
@ D'une façon générale, chaque fois que l'on désire accéder à un attribut ou une méthode @ Cercle Carré
....... .......
..c: d'une occurrence d'objet d'une classe C, il (ou elle) devra être défini(e), soit dans la classe ..c:
O'l O'l
·c ·c
>
Cl.
C, soit dans l'un de ses ancêtres. Si l' attribut ou la méthode n' appartient pas aux classes >
Cl.
0 parentes, l' attribut ou la méthode n'est pas trouvé et c'est une erreur de programmation. 0 FIGURE 1 2.3 Arbre d'héritage des figures géométriques.
u u
S'il y a eu des redéfinitions de la méthode, sa première apparition en remontant l 'arbores
cence d'héritage est celle qui sera choisie. Ainsi, avec les déclarations suivantes : Les éléments d'un tableau de type F o rme pourront désigner, à tout moment, des ellipses,
des cercles, des rectangles ou encore des carrés. Sans le polymorphisme, il aurait fallu décla
variable c type Carré rer autant de tableaux qu'il existe de formes géométriques.
variable r type Re ctangle
2. Du grec poly plusieurs et mmphe forme.
c . périmètre ( ) , provoque l'exécution de la méthode p érimèt re définie dans la classe
Rectangle, alors que r . changerCôté ( ) provoque une erreur.
12.5 Classes abstraites 1 37 1 38 Chapitre 12 • Héritage
CercledansCarré
-0
0 1 2. 5 CLASSES ABSTRAITES -0
0
c: c:
=i =i FIGURE 12.4 Graphe d'héritage CercledansCarré.
0 0
"<:t"
Dans la section précédente, nous n'avons pas rédigé le corps des méthodes de la classe "<:t'
,..-1 ,.-1
0 F o rme . Puisque cette classe représente des formes quelconques, il est bien difficile d'écrire 0
N N
les méthodes s urf ace ou a f f i che r . Toutefois, ces méthodes doivent être nécessairement Lorsqu'une classe ne possède qu'une seule classe parente, l 'héritage est simple. En re
@ @
.......
..c: définies dans cette classe pour qu'il y ait polymorphisme ; la variable t est un tableau de .......
..c:
vanche, si une classe peut hériter de plusieurs classes parentes différentes, l'héritage est alors
O'l O'l multiple. Avec l'héritage multiple, les relations d'héritage entre les classes ne définissent plus
·c Fo rme et t [ 1 ] a f f icher ( ) doit être définie. Il est toujours possible de définir le corps des
. ·c
> >
Cl.
0 méthodes vide, mais alors rien ne garantit que les méthodes seront effectivement redéfinies Cl.
0
une simple arborescence, mais de façon plus générale un graphe, appelé graphe d'héritage 3 .
u u
par les classes héritières. L'héritage multiple introduit une complexité non négligeable dans le choix de la méthode
Une classe abstraite est une classe très générale qui décrit des propriétés qui ne seront à appliquer en cas de conflit de noms ou d'héritage répété. Pour le programmeur, le choix
définies que par des classes héritières, soit parce qu'elle ne sait pas comment le faire (e.g. la d'une méthode à appliquer peut ne pas être évident. C'est pour cela que certains langages de
classe Forme), soit parce qu'elle désire proposer différentes mises en œuvre (voir les types programmation, comme JAVA 4, ne le permettent pas.
abstraits, chapitre 1 7). Nous définirons, par exemple, la classe F orme comme suit :
3. Voir les chapitres 19 et 20 qui décrivent les types abstraits graphe et arbre.
4. Toutefois, ce langage définit la notion d' inte1face qui permet de mettre en œuvre une forme particulière de
l 'héritage multiple.
12.7 Héritage et assertions 1 39 140 Chap it r e 12 • Héritage
1 2.7 H É RITAG E ET ASSERTIONS Cette règle possède l'avantage d'être simple et nous l'utiliserons chaque fois que cela est
possible. Toutefois, elle ne pourra être appliquée systématiquement, et nous verrons par la
Le mécanisme d'héritage introduit de nouvelles règles pour l a définition des assertions des suite des cas où elle devra être mise en défaut.
classes héritières et des méthodes qu'elles comportent.
L'invariant d'une classe héritière est la conjonction des invariants de ses classes parentes En JAVA, l'héritage est simple et toutes les classes possèdent implicitement un ancêtre com
et de son propre invariant. Dans notre exemple, l'invariant de la classe Carré est celui de mun, la classe Ob j e c t . On retrouve dans ce langage les concepts exposés précédemment, et
la classe Rect angle, i.e. la largeur et la longueur d'un rectangle doivent être positives ou dans cette section, nous n'évoquerons que ses spécificités.
nulles, et de son propre invariant, i.e. ces deux longueurs doivent être égales. Les classes héritières, ou sous-classes, comportent dans leur en-tête le mot-clé extends 5
suivi du nom de la classe parente. Le constructeur d'une sous-classe peut faire appel à un
constructeur de sa classe parente, la super-classe appelée super. S'il ne le fait pas, un appel
1 2. 7 .2 Assertions sur les méthodes implicite au constructeur par défaut de la classe parente, c'est-à-dire super ( ) , aura systé
matiquement lieu. Notez que le constructeur par défaut de la classe mère doit alors exister.
Les règles de définition des antécédents et des conséquents sur les méthodes doivent être
complétées dans le cas particulier de la redéfinition. Nous prendrons ici les règles données La classe Carré s'écrit en JAVA :
par B . MEYER [Mey97]. public class Carré extends Rectangle
Une assertion A est plus forte qu'une assertion B, si A implique B. Inversement, nous
I** Invariant de classe : l ongueur l argeur ): 0 *I
dirons que B est plus faible. Lors d'une redéfinition d'une méthode m, que nous appellerons
'
m , i 1 faudra que :
Il le constructeur
public Carré ( double c ) {
( 1 ) l'antécédent de m' soit plus faible ou égal que celui de m ; Il appel du constructeur de Rectangl e
super ( c , c ) ;
(2) le conséquent de m' soit plus fort ou égal que celui de m.
I** Rôle : met à jour l e côté du carré courant *I
Pour comprendre cette règle, il faut la voir à la lumière de la liaison dynamique. Une public void changerCôté ( double c ôt é )
variable déclarée de type classe A peut appeler la méthode m, mais exécuter sa redéfinition super . changerLargeur ( côt é ) ;
'
m dans la classe héritière B sous l'effet de la liaison dynamique. Cela indique que toute super . changerLongueu r ( côt é ) ;
assertion qui s'applique à m doit également s'appliquer à m' . Aussi, la règle ( 1 ) indique
-0 que m' doit accepter l 'antécédent de m, et la règle (2) que m' doit également vérifier le -0
I** Rôle : met à jour l a largeur et l a l ongueur du carré couran t *I
0 0 @Override
c:
=i
conséquent de m. c:
=i
0 0 public void change r L a r geu r ( double l a rgeu r ) {
"<:t" "<:t" changerCôt é ( l argeu r ) ;
,..-1 ,..-1
0 0
N
�
.......
1 2.8 RE LATION D'H ÉRITAG E OU D E CLIE NTÈLE @
.......
I** Rôle : met à jour la largeur et la l ongueur du carré couran t *I
..c: .c: @Ove r r i de
O'l O'l
·c Lors de la construction d'un programme, comment choisir les relations à établir entre les ·c public void changerLargeur ( double longueur ) {
> >-
Cl. Cl. changerCôté ( l ongueur ) ;
u
0 classes ? Une classe A doit-elle hériter d'une classe B, ou en être la cliente ? Une première u
0
réponse est de dire que si on peut appliquer la relation est-un, sans doute faudra-t-il utiliser
I** Rôle : convert i t l e carré couran t en cha îne de caract ères *I
l'héritage. Dans notre exemple, un carré est-un rectangle particulier dont la largeur et la lon
@Override
gueur sont égales. La classe Carré hérite de la classe Rectangle. En revanche, si c'est public S t r ing t o S t r i n g ( ) {
une relation a-un qui doit s' appliquer, il faudra alors établir une relation de clientèle. Une return " c a r r é �de�côt é � é g a l � à " + l a rgeur;
voiture a-un volant, une école a-des élèves. La classe Voit ure, qui représente des automo }
biles, possédera un attribut vo lant qui le décrit. De même, les élèves d'une école peuvent Il fin classe Carré
être représentés par la classe É lèves, et la classe É c o l e possédera un attribut pour désigner
tous les élèves de 1' école. 5. Ce qui indique bien l 'idée d' extension de classe.
12.9 L'héritage en Java 141 142 Chapitre 12 • Héritage
La création d'un carré c de côté 7 et l' affichage de sa surface s'écriront comme suit : Type primitif Classe correspondante
byte Byte
Carré c = new C a r r é ( 7 ) ;
S y s t em . out . p rintln ( c . surface ( ) ) ; short Short
int Int eger
On introduit, sans obligation, la redéfintion d'une méthode par l' annotation de documenta long Long
tion @Ove r r i de . La redéfinition des méthodes dans les sous-classes ne peut se faire qu' avec f loat F l oat
des méthodes qui possèdent exactement les mêmes signatures. La liaison dynamique est donc double Double
mise en œuvre sur des méthodes qui possèdent les mêmes en-têtes et qui diffèrent par leurs boolean B o o l e an
instructions, comme la méthode t o St r ing des classes Rectangle et Carré. char Char acter
JAVA n'a pas de mécanisme qui interdit l'héritage de méthodes publiques par la sous
classe. Pour garantir l' invariant de la classe Carré précédente, il est donc nécessaire de TABLE 12.1 Correspondance types primitifs - classes.
machine sur laquelle est exécuté le programme. Les affirmations, antécédents et conséquents,
décrivent l 'état du programme, avant et après l'exécution d'une instruction, et reflètent bien
souvent le changement de valeurs des variables mises en jeu. En programmation impérative,
l'action élémentaire est donc l'affectation.
Dans les langages de programmation purement fonctionnels, comme par exemple HAS
KELL [HHJW07], la notion d' affectation des langages impératifs n'existe pas. Dans ces lan
gages, l' affectation permet de lier un nom à une valeur qu'on ne peut modifier. On parle
d'affectation unique (en anglais single assignment). Toutefois, d'autres langages fonction
nels, comme LISP ou SCHEME ([Cha96]) par exemple, permettent en plus de cette affectation
Chapitre 1 3 unique, l'affectation classique des langages impératifs.
Dans ce chapitre, nous présenterons d' abord l'utilisation des fonctions anonymes comme
paramètres de fonctions, et comme résultats de fonction. Ces fonctions sont dites d'ordre su
Fonctions anonymes périeur (en anglais higher-orderfunctions). Nous évoquerons ensuite la notion de fermeture.
Ces aspects seront mis en évidence à travers des exemples classiques de la programmation
fonctionnelle et nous expliquerons la façon dont on les programme avec la nouvelle ver
sion 8 de JAVA.
(x, y) ---+ x + y À la fin du chapitre 1 0, page 1 1 7, nous vous avons proposé de programmer une fonction
qui calcule, par la méthode des rectangles, l'aire de la fonction cosinus sur un intervalle [a , b].
On peut remarquer que dans cette notation n'apparaît pas le nom de la fonction (ni L'écriture algorithmique de cette fonction est la suivante :
d'ailleurs les ensembles de départ et d'arrivée). En fait, cette notation désigne une valeur { An t écédent : n nombre de rectangles à définir sur [a, b] }
fonctionnelle (comme 10 désigne une valeur entière, ou " h e l l o " une chaîne de caractères). { Rôle : retourne l ' aire de cosinus sur [a, b] }
Cette fonction est dite anonyme. fonction c a l cu l e rAire ( données a , b : rée l s , n : natu r e l ) r ée l
-0 Ces fonctions anonymes, appelées aussi lambda, lambda expressions ou lambda fonc -0 variables l argeurRect , x, aire : rée l s ;
0 0
c: c:
0
=i tions 1 , sont la notion fondamentale des langages de programmation dits fonctionnels. Dans 0
=i
"<:t" la suite de ce chapitre, les termes lambda ou fonction anonyme seront employés indifférem "<:t"
l a r geurRect f- ( b -a ) / n
,..-1 ,..-1 x f- a + l a rgeurRe c t / 2
0 ment. 0
N N
aire f- 0
@ Le modèle de programmation fonctionnelle repose sur la notion de fonction. Dans le cha @ pourtout i de 1 à n faire
....... .......
..c: ..c:
O'l
·c
pitre 2, nous avons présenté un programme comme une fonction f dont l 'ensemble de départ O'l
·c { aire = .L:�= l (xk+l - Xk) X cos((xk + Xk+ 1 ) /2) J
> V est un ensemble de données, et l'ensemble d'arrivée R est un ensemble de résultats. Dans > aire f- a i r e + l a rgeurRect * C O s ( x ) ;
Cl. Cl.
0 0
u le modèle impératif, la description de cette fonction est faite par une suite d' actions (instruc u finpour
tions). Dans le modèle fonctionnel, le programme sera naturellement décrit par une fonction, rendre a i r e
composition de plusieurs autres fonctions. L'exécution du programme sera 1 ' application de la finfonc
fonction sur des valeurs prises dans V pour obtenir des résultats dans R.
Telle quelle, cette fonction ne calcule bien évidemment que l' aire de la fonction cosinus
Le modèle de programmation présenté dans ce livre est celui de la programmation impé
sur l'intervalle [a, b] . Il est évident que la méthode des rectangles doit pouvoir s'appliquer à
rative. Fondamentalement, il est basé sur la notion de changement d'état de la mémoire de la
d'autres fonctions que cosinus et, comme pour les bornes de l'intervalle, il est naturel donc de
l . À l'origine, les lambda fonctions sont à la base du système de lambda calcul proposé par A. CHURCH dans paramétrer le calcul de l' aire avec la fonction dont on veut calculer l'aire. Pour calculer l'aire
les années 30. de la fonction sinus sur l'intervalle [ü, 7r] ou celle de la fonction x2 sur [-2, 2], on appelle la
13. 1 Paramètres fonctions 145 146 Chapitre 13 • Fonctions anonymes
fonction c alcul erAire en lui passant deux lambda (en gras ci-dessous) en paramètre qui Dans l' API, le nouveau paquetage j ava . ut i l . function propose tout un ensemble
prennent un réel en paramètre et qui renvoient un réel : d'interfaces fonctionnelles prêtes à l'emploi. Ce paquetage contient, par exemple, l'interface
fonctionnelle générique Funct i o n < T , R> qui représente les fonctions anonymes à un para
c a l c u l e rAire ( 0 , rr , 1 0 0 , (x) ---+ sin(x) )
calculerAire ( - 2 , 2 , 1 0 0 , (x) ---+ x2 )
mètre de type quelconque T renvoyant un résultat également de type quelconque R.
En JAVA, le corps d'une lambda ne se limite pas à une simple expression. Puisque JAVA
L' en-tête de la fonction c a l c u l erAi re contiendra alors une fonction paramétrique est un langage impératif, un bloc d'instructions, avec des déclarations de variables, peut être
comme quatrième paramètre formel : défini, comme dans les exemples suivants :
fonction c a l cu l e rAi r e ( données a , b : réel s , n : naturel (x) -> assert x>= O ;
fonction f ( donnée x : rée l ) : rée l ) : réel return Math . s qrt ( x ) ;
Dans le corps de la fonction c a l c u l erAi re, l'appel à la fonction cos est remplacé par
l' appel à f. hypothénuse = ( a , b ) - > {
Funct i o n <Doubl e , Double> carré = (x) -> X*X;
Dans la version 8 de JAVA, une fonction anonyme est représentée par une interface, appelée () ->
interface fonctionnelle, qui ne contient qu'une unique 2 méthode abstraite. Par exemple, pour S y s t em . out . p r i nt ( " � n �= � ) ;
"
représenter des lambda qui prennent deux réels en paramètre et renvoient un réel comme int n = Stdinput . readlnint ( ) ;
résultat, nous déclarerons l' interface fonctionnelle suivante : S y s t em . out . p r i n t l n (Math . s qrt ( n ) ) ;
};
@Funct i on a l i n t e r f a c e
interface FoncDouble Cette dernière lambda est représentée par 1' interface fonctionnelle :
double app l y ( double x, double y ) ;
@Funct i on a l i n t e r f a c e
interface P rocédure {
void app l y ( ) throws IOExcep t i o n ;
où appl y est le nom de la méthode qui permettra l'évaluation de la lambda.
La lambda donnée au début de ce chapitre s'écrit en JAVA de façon similaire à sa notation
mathématique : Notez que la variable n n'est pas locale à la lambda. Si une autre variable n a déjà été
déclarée dans le bloc de définition de la lambda, le compilateur signalera l'erreur : n déjà
(X, y) -> X + y
déclarée.
-0 -0
0 On remarque que le type des paramètres x et y n' est pas précisé. Mais, on aurait pu égale 0 Avec les versions antérieures de JAVA, pour paramétrer la méthode c a l cu lerAire sur
c: c:
=i ment le faire en écrivant : =i différentes fonctions, il faut créer et passer un objet, représenté par une classe (éventuellement
0 0
"<:t"
,..-1
"<:t"
,..-1
anonyme) contenant la fonction à évaluer.
0 ( double x , double y ) - > x+y 0
N N Avec la version 8 de JAVA, on écrit la méthode c a l cu l erAire avec un paramètre fonc
@ @ tionnel de type Fun et i o n . La déclaration ci-dessous précise que ce paramètre fonctionnel f
.......
..c:
Toutefois, dans la plupart des cas, le compilateur est en mesure de déterminer automa .......
..c:
O'l tiquement le type des paramètres par inférence en fonction du contexte d'utilisation de la O'l prend un paramètre de type Double et renvoie un résultat de type D ouble.
·c ·c
> >
Cl.
0
lambda. L'indication du type des paramètres est alors inutile. Le type du résultat de la lambda Cl.
0 public double c a l c u l e rAi re ( double a , double b , int n ,
u est, quant à lui, toujours déterminé par inférence. u Function<Doub l e , Double> f )
Dans l'exemple donné ci-dessous, on affecte à la variable f la lambda précédente, puis on
double l argeurRect= ( b - a ) / n ;
l' évalue.
double x=a+la rgeurRect / 2 ;
F o n cDouble f = (x, y) - > x+y ; double a i r e = O ;
S y s t em . out . p rintln ( f . app ly ( 2 . 0 , 3 . 0) ) ; Il 5 . 0 for ( int i = l ; i <= n ; i++, x+=largeurRe ct )
11 a i re = I:�=l
(xk+1 - Xk) * J((xk + Xk+ 1 )/2)
2. Il est également possible de définir des méthodes par défaut avec leur corps. Ces méthodes pourront être a i r e + = l argeurRect * f . appl y ( x ) ;
redéfinies dans une classe implémentant l 'interface fonctionnelle. return a i r e ;
13. 1 Paramètres fonctions 147 148 Chapitre 1 3 • Fonctions anonymes
L'évaluation de la lambda désignée par le paramètre f se fait en exécutant la méthode public static < T , R> L i s t e<R> map ( Function < T , R> f , T . . . arg s ) {
app l y déclarée dans l'interface fonctionnelle. L i s t e<R> r = new L i s t eTableau<R> ( ) ;
Pour calculer les aires des fonctions sinus et x 2, on effectuera les appels à la méthode int i=l ;
for (T x args )
c a l culerAire suivants : Il ajouter en fin de liste r la valeur f (x)
c a l c u l e rAire ( O , Math . P I , 100, (x) - > Mat h . s i n ( x ) ) r . a j ou t e r ( i ++ , f . app l y ( x ) ) ;
c a l c u l erAi re ( -2 , 2, 100, (x) -> X*X) ) return r ;
Si on souhaite simplement calculer n!, il suffit de passer la lambda identité ( x ) - >x. @Funct i onal lnterface
interface Fonc <R> {
int fact ( int n ) {
R apply ( int x , int y ) ;
return factAve cCont inuat i on ( n , ( x ) ->x ) ;
On peut remarquer que la fonction factorielle avec continuation possède maintenant une Pour obtenir le quotient et reste d' une division entière, on pourra écrire :
récursivité terminale. Le processus de calcul avec continuation est itératif. La continuation Il l e quo t i e n t
agit par le calcul au fur et à mesure de résultats intermédiaires placés dans un accumulateur div i s i onEuc l i dienne ( 4 5 , 4, (q, r) -> q) ; 11 1 1
caché. Il l e res t e
La programmation par continuation s'utilise dans de nombreux contextes, qu'on ne peut divi s ionEu c l i di enne ( 4 5 , 4, (q, r) -> r ) ; 11 1
tous décrire ici. Toutefois, nous allons en présenter un qui permet simplement de programmer Il l e quo t i en t et l e res t e
divi s ionEu c l i di enne ( 4 5 , 4,
des fonctions qui renvoient plusieurs résultats.
(q, r) - > { return new Couple ( q , r ) ; } ) ; Il (1 1 , 1 )
Dans des langages qui ne possèdent pas le mécanisme de transmission par résultat (ou ré
férence), une façon pour une fonction de renvoyer plusieurs résultats est de renvoyer un objet
structuré, qui agrège les différents résultats. Par exemple, une fonction qui calcule le quo
tient et le reste de la division euclidienne, renverra un couple ( q, r ) représenté, par exemple, 1 3.2 FONCTIONS ANONYM E S EN RÉSULTAT
par une instance d'une classe Couple qui possède deux variables de type entier. L'écriture
récursive en JAVA de la division euclidienne est donnée ci-dessous : Une autre caractéristique des fonctions d'ordre supérieur est de pouvoir être le résultat
class Couple {
d'une autre fonction, permettant ainsi de créer dynamiquement une nouvelle fonction. Cette
int q , r ; propriété est au cœur des langages fonctionnels, et permet en particulier de mettre en œuvre
Couple ( int x , int y ) { q x; r y; } la composition ou la curryfication de fonctions.
L'appel de g o f appliqué à 7r /2 s'écrit : Écrivons en JAVA la fonction c u r r y f i e r qui prend en paramètre une lambda à deux
paramètres. Tout d'abord, définissons l' interface fonctionnelle générique pour représenter
gof . app l y (Math . P I / 2 )
une telle lambda :
S i nous voulons créer une fonction qui prend en paramètre un tableau d'entiers et qui @Funct i on a l l n t e r f ace
renvoie le carré de sa dernière valeur. Nous pouvons définir cette fonction comme la com interface Functi on2 < T , U , R>
position de deux fonctions, la première renvoie la dernière valeur du tableau et la seconde R apply ( T x , U y) ;
assure l'élévation au carré. Le fragment de code suivant affiche 25.
Funct i o n < I nt eger [ ] , Integer> gof = c ompo s e ( x -> x [ x . l e n gth- 1 ) , x -> X * x ) ;
La fonction générique curryf i e r prend une lambda f à deux paramètres et renvoie une
S y s t em . out . p r i n t l n ( g o f . apply (new Integer [ ) {2, 4,5} ) ) ;
lambda à un paramètre renvoyant elle-même une lambda qui évalue la fonction f. La fonction
Nous souhaitons maintenant écrire une méthode qui renvoie la composition de n fois une curry f i e r est donnée ci-dessous :
fonction f passée en paramètre. Cette méthode renvoie fn = f o f . . . o f. On écrit cette < T , U , R> Funct i o n < T , Funct ion<U , R> > curryf i e r ( Function2 < T , U , R> f )
fonction de façon itérative en utilisant la fonction comp o s e précédente. Notez que les types
des ensembles de départ et d'arrivée de la lambda doivent être les mêmes. return (x) -> (y) - > f . ap p l y ( x , y ) ;
I**
* Ant é cédent : n�l Utilisons cette fonction pour curryfier la fonction d'addition de deux entiers, et construi
* Rôle : renvoie f o J . . . o f, n foi s .
sons deux lambda d'incrémentation et de décrémentation à partir de celle-ci :
*I
<T> Func t i o n < T , T > comp o s eNFo i s ( Funct i o n< T , T > f , int n ) { Il curryfier l a lambda x+y
Funct ion< T , T > frond = f; Funct i on < I nt e ge r , Funct i o n < I n t e ge r , I n t e g e r > > add
while ( n -- > 1 ) frond = compo s e ( f r ond, f) ; = curry f i e r ( ( x , y) -> x+y ) ;
return f rond; Il définir les lambda incrémen ter et décrémen ter
Funct i o n < I n t e ge r , Integer> incrémenter (x) - > add . apply ( x ) . apply ( l ) ;
Funct ion< Intege r , Integer> décrémenter = (x) - > add . apply ( x ) . apply ( - 1 ) ;
n initialisée à
appel à la lambda produite renvoie 1 ' entier suivant. Pour programmer le générateur, on
10 et exécute la lambda associée à f avec le paramètre x de g.
a besoin d'un compteur et nous utiliserons la fermeture pour le mémoriser. La fonction
n +-- 5 crée rGénérat eur peut s'écrfre comme suit :
f f- (X) ---7 X * n
g +-- ( x ) ---+ ( variable n : ent i e r , n +--2 0 , rendre f ( x ) ) @Funct i ona l i n t e r f ace
interface F o n c < T >
Quelle est la valeur de g(2) ? T app l y ( ) ;
1 3. 3 . 1 Fermeture en Java Malheureusement, nous venons de le dire, les occurrences libres, ici cpt, doivent être
final. On ne peut donc 1 ' incrémenter dans la lambda renvoyée.
En JAVA la portée est statique. Les valeurs des variables libres sont donc recherchées dans
Dans un contexte purement séquentiel, une solution pour contourner ce problème consiste
l'environnement de définition de la fonction anonyme. Toutefois, en JAVA, il y a quelques
à utiliserune référence sur un objet mutable, comme par exemple un tableau. On récrit alors
contraintes que nous allons voir maintenant. Récrivons l'exemple précédent en JAVA.
-0
0
-0
0
la lambda comme suit :
c: int n=S ; c:
=i =i
0 0 Fonc<Fon c < I nt eg e r > > c ré e rGé n é r ate u r () -> {
Funct i o n < I n t e g e r , I nteger> f = x - > X * n ;
"<:t"
,..-1
"<:t"
,..-1 int [] cpt = { 0 };
0 Funct i on < I nt e g e r , I nt e ger> g = x - > { int n=2 0 ; return f . ap p l y ( x ) ; }; 0
N N return () - > cpt [ O ] ++ ;
S y s t em . out . p ri nt l n ( g . apply ( 2 ) ) ;
@ @ };
....... ......
..c: Malheureusement, ce fragment de code provoque une erreur de compilation. On l ' a déjà ..c:
O'l O'l
·c
> évoqué plus haut, la déclaration d'une variable dans une lambda ne lui est pas locale. Elle ap ·c
>
Plus de problème ! L'occurrence libre cpt n'est pas modifiée, seule la valeur référencée
Cl. Cl. l'est, et la lambda peut enfin créer des générateurs.
0 partient à l'environnement de déclaration de la lambda. Le compilateur signale donc, ligne 3, 0
u u
que la variable n est déjà déclarée.
Si nous enlevons le mot-clé int dans la lambda associée à g, pour modifier la première 1 3.3.2 Lambda récursives
variable, ce n'est pas mieux, encore un message d'erreur du compilateur. Le compilateur
réclame que la variable n soit (effectivement) final, c'est-à-dire qu'elle ne puisse plus être Une autre difficulté, mais qui n'est pas spécifique à JAVA, est l'écriture de lambda récur
modifiée après son initiabsation 5 . JAVA a fait ce choix pour éviter les problèmes d'accès à sives. Puisque ce sont des fonctions anonymes, donc sans nom, comment dénoter l 'appel
des variables dans des contextes concurrents non synchronisés. récursif ?
Si on veut une lambda qui calcule factorielle, la première idée est d'écrire : Exercice 13.4. En utilisant les méthodes app l y et rnap précédentes, écrivez la méthode
produit S c a l a i re qui calcule de produit scalaire de 2 vecteurs.
@Funct i onal interface
interface Fonct i o n i n t int Rappel : soient 2 vecteurs x = (x1 , x2 , . . . , Xn) et if = (y1 , y2 , . . . , Yn), leur produit scalaire
int apply ( int ) ; est égal à [Link] = X1 ·Yl + X2 .Y2 + . . . + Xn ·Yn ·
Exercice 13.5. Récrivez la fonctionfibonacci en utilisant une continuation.
F o n ct i o n i nt i nt fact = n -> n == 0 ? 1 : n * fact . apply ( n - 1 ) ; Écrivez la méthode décurryf i e r qui met en œuvre l'opération inverse de
Exercice 13.6.
la curryfication d'une fonction à deux paramètres.
Mais le compilateur JAVA signale une erreur indiquant que la variab]e fact peut ne pas
avoir été initialisée, puisqu'on utilise la variable fact alors que celle-ci n'a pas encore de Exercice 13.7. Généralisez la fonction de curryfication de fonctions à deux paramètres pour
valeur. La solution est d'utiliser une variable auxiliaire : curryfier des fonctions à n paramètres (n � 1).
F o n c t i o n i n t int fact = (x) -> {
Exercice 13.8. Modifiez la lambda qui crée un générateur de suites d'entiers, pour lui ajou
Fonct i on i nt i nt f = null ;
ter un paramètre qui correspond à la valeur initiale de la suite à produire. Les appels au
f = n -> n == 0 ? 1 : n * f . apply ( n- 1 ) ;
générateur créé ci-dessous, produiront la suite 3, 4, 5 . . . . .
return f . apply ( x ) ;
}; F o n c < Integer> générateur = créerGénérateur . ap p l y ( 3 ) ;
Sauf qu' encore une fois le compilateur JAVA indique une erreur sur f . app l y ( n- 1 ) car
Exercice 13.9. Ajoutez à la lambda qui crée un générateur, un paramètre fonctionnel (i.e.
f n'est pas une variable (effectivement) final et ne peut donc être référencée dans une
une lambda) qui définit un algorithme de calcul de valeur aléatoire. Cette lambda est une
lambda.
fonction f qui peut être décrite par récurrence Xn = f (xn- 1 ) . Le premier paramètre sera
Encore une fois, la solution passe par l'utilisation de structures mutables. On déclare la alors vu comme le germe de la fonction f, i.e. x0.
variable auxiliaire f de type tableau.
Vous pouvez utiliser, par exemple, comme fonction f :
F o n c t i o n i n t int fact = (x) ->
fonction f ( donnée/résultat ent i e r x ) : ent i e r
Fonct i o n int int [] f = { null };
X = X * 1 103515245 +12345
f [O] -> n == 0 ? 1 : n * f [ O ] . apply ( n - 1 ) ) ;
rendre x / 6 5 5 3 6 ) mod 3 2 7 6 8
return f [ O ] . ap p l y ( x ) ;
finfonc
};
S y s t em . out . p r i n t l n ( " f a c ( 6 ) = " + fact . apply ( 6 ) ) ; // 72 0 Exercice 13.10. Le calcul de la nème valeur aléatoire peut être vu comme n fois la compo
sition de la fonction aléatoire. Utilisez votre fonction cornpo seNF o i s pour produire la nème
valeur aléatoire et vérifiez que vous obtenez la même valeur que précédemment.
-0 -0
0 0
§ 1 3.4 EXERCICES c:
=i
0 0
"<:t" "<:t"
,.-1
� Exercice 13.1. Écrivez en JAVA de façon récursive la méthode compo s eNF o i s qui corn- 0
N
@ pose n fois une fonction f. f et n ): 1 sont les deux paramètres de cette fonction qui renvoie @
.......
.:ë la lambda fn = f o f o . . . f o f. ..c:
O'l O'l
·c ·c
Écrivez la méthode générique app l y qui applique une lambda, passée en >
El Exercice 13.2. Cl.
0 0
u paramètre, à une suite de valeurs également passée en paramètre. Par exemple, les appels à la u
méthode a pp 1 y suivants :
appl y ( ( x , y ) -> x+y , 3, 4, 10, - 7, 6)
apply ( ( x , y ) - > x & y , true, false, true )
'
'
1
'
1
L' exécution anormale d'une action peut provoquer une erreur de fonctionnement d'un
programme. Jusqu'à présent, lorsqu'une situation anormale était détectée, les programmes
que nous avons écrits signalaient le problème par un message d' erreur et s' arrêtaient. Cette
façon d'agir est pour le moins brutale, et expéditive. Dans certains cas, il serait souhaitable 1
1
qu'ils reprennent le contrôle afin de poursuivre leur exécution. Les exceptions offrent une 1
1
EJ'..__
I
solution à ce problème.
exception
produite l'exception. Si l'exception est transmise à l'environnement d'appel, seul le maintien pas un entier), la clause catch capture l'exception et propose une nouvelle lecture. Notez
de 1 ' invariant de classe est nécessafre. que le nombre de lectures possibles n'est pas borné.
Nous allons présenter maintenant la façon dont les exceptions sont gérées dans le langage
public int l i reEnti e r ( ) {
JAVA.
try {
return Stdinput . readint ( ) ;
Une méthode qui contient une action susceptible de produire des exceptions n'est pas tenue
1 4.3.1 Traitement d'une exception de la placer dans une clause try suivie d'une clause catch. Dans ce cas, elle doit indiquer
dans son en-tête, précédée du mot-clé throws, les exceptions qu'elle ne désire pas capturer.
Pour traiter une exception produite par l'exécution d'une action A, il faut placer la méthode Si une exception apparaît, alors l 'exécution se poursuit dans l'environnement d'appel de la
dans une clause try, suivie obligatoirement d'une clause catch qui contient le traitement méthode. Chaque méthode d' une chaîne d'appel peut traiter l'exception ou la transmettre à
-0 de l 'exception. -0
0
c:
0
c:
son environnement d'appel.
=i =i
0 try { 0
"<:t" "<:t'
,..-1 A
0
N � 1 4.3.2 Émission d'une exception
@ catch ( UneExcept i on e ) { @
....... ....... L'émission explicite d'une exception est produite grâce à l ' instruction throw. Le type de
..c: B ..c:
O'l O'l
·c
>
·c
>
l'exception peut être prédéfini dans 1' API, ou défini par le programmeur.
Cl. Cl.
0 0
u u
Si l ' action A contenue dans la clause try du fragment de code précédent détecte une if ( une s it u a t i on a n o rmale )
throw new Ari thmet icExcept i on ( ) ;
anomalie qui émet une exception de type UneExcept ion, son exécution est interrompue. Le
if ( une s i t u a t i o n a n o rma l e )
programme se poursuit par l'exécution de l' action B placée dans la clause catch associée
throw new MonExcept ion ( " un mess a g e " ) ; ......
à l'exception UneExcept ion. Si aucune situation anormale n'a été détectée, l 'exception
UneExcept i o n n'a donc pas été émise, l'action A est exécutée intégralement et l' action B
ne l'est pas du tout. Dans la clause catch, e désigne l'exception qui a été récupérée et qui Notez qu'une méthode (ou un constructeur) qui émet explicitement une exception doit
peut être manipulée par B. La méthode suivante contrôle la validité d' une valeur entière lue également le signaler dans son en-tête avec le mot-clé throws, sauf si l'exception dérive de
sur l'entrée standard. Si la lecture produit l'exception I OExcept i o n (e.g. la valeur lue n'est la classe RuntimeExcept i on.
14.4 Exercices 161
1 4.4 EXERCICES
Exercice 14.1. Complétez le code ci-dessous pour obliger 1 'utilisateur à fournir un entier.
public static void rnain ( S t ring [ ] s) {
int i = 0;
boolean nonLu = true;
do
try
S y stem . out . println ( " un ......e n t i e r ......: " ) ;
i= Stdi nput . re a d l n i n t ( ) ;
catch ( IOException e ) {
while ( . . . . . . . . . . . . . . ) ;
S y s t ern . out . print l n ( " e n t i e r ......l u ...... : ......" + i ) ;
1 5. 1 DÉCLARATION D E TYPE
Un objet de type fichier séquentiel forme une suite de composants tous de même type T,
élémentaire ou structuré. La déclaration est notée :
fichier de T
Le type T des éléments peut être n'importe quel type à l 'exception du type fichier ou de
tout type structuré dont un composant serait de type fichier. En d' autres termes, les fichiers
de fichiers ne sont pas autorisés.
Chapitre 1 5 Le nombre de composants n'est pas fixé par cette déclaration, c'est-à-dire que Je domaine
des valeurs d'un objet de type fichier est (théoriquement) infini.
Par exemple, les déclarations suivantes définissent deux variables f 1 et f 2 , respective
Les fichiers séquentiels ment, de type fichier d'entiers et de rectangles :
variables
f l type fichier de ent i e r
f2 type fichier de Re ctangle
courante, désignés par f . L'élément courant situé à la position courante est noté ft :
-0 effective dans les mécanismes d'entrées-sorties avec le monde extérieur grâce aux dispositifs -0
0 0 f- -+
c:
=i
périphériques de 1 'ordinateur. Les fichiers permettent de conserver de 1 ' information sur des c:
=i f f & f
0 supports externes, en particulier des disques. Mais dans bien des systèmes, comme UNIX 0 -+
"<:t"
,..-1 par exemple, les fichiers ne se limitent pas à cette fonction, ils représentent également les
"<:t"
,..-1 ft prernier ( f )
0 0
N mécanismes d'entrée et de sortie standard (i.e. le clavier et l'écran de l'ordinateur), les péri N
@ @ Une suite de composants de fichier est notée entre les deux symboles < et > . Par exemple,
....... phériques, des moyens de communication entre processus ou réseau, etc . ....... < 5 - 3 1 0 > définit une suite de 3 entiers, et < > la suite vide .
..c: ..c:
O'l O'l
·c
>
Il existe plusieurs modèles de fichier. Celui que nous étudierons, et que tous les langages ·c
> La déclaration de type fichier n'indique pas le nombre de composants. Ce nombre est
Cl. de programmation mettent en œuvre, est le modèle séquentiel. Les fichiers séquentiels jouent Cl.
0 0 quelconque. Nous verrons plus loin qu'il nous sera nécessaire, lors de la manipulation des
u u
un rôle essentiel dans tout système d'exploitation d'ordinateur. Ils constituent la structure fichiers, de savoir si nous avons atteint la fin du fichier ou pas. Pour cela, nous définissons la
appropriée pour conserver des données sur des dispositifs périphériques, pour lesquels les fonction f df, pourfin de fichier, qui renvoie vrai si la fin de fichier est atteinte et faux sinon.
méthodes de lecture ou d'écriture des données passent dans un ordre strictement séquentiel. La signature de cette fonction est :
Les fichiers séquentiels modélisent la notion de suite d'éléments. Quelle est la signification fdf : Fichier ---+ booléen
du qualificatif séquentiel ? Il signifie que l'on ne peut accéder à un composant qu'après avoir
Notez que :
accédé à tous ceux qui le précédent. Lors du traitement d'un fichier séquentiel, à un moment
-+
donné, un seul composant du fichier est accessible, celui qui correspond à la position courante fdf ( f ) => f = <>
du fichier. Les opérations définies sur les fichiers séquentiels permettent de modifier cette
position courante pour accéder au composant suivant.
15.3 Manipulation des fichiers 165 1 66 Chapitre 1 5 • Les fichiers séquentiels
Les deux grandes formes de manipulation des fichiers sont l'écriture et la lecture. La pre Une fois le fichier créé, i1 peut être utile de consulter ses composants. La consultation d'un
mière est utilisée pour la création des fichiers, la seconde pour leur consultation. fichier débute par une initialisation en lecture grâce à la procédure In i t Lect ure. Pour
décrire son fonctionnement, nous distinguerons ]e cas où ]e fichier est initialement vide et le
cas où il ne l'est pas.
1 5. 3 . 1 Écriture
{f = <>}
Nous nous servirons des opérations d'écriture chaque fois que nous aurons à créer des I n i t Le c t u re ( f )
<- _,
fichiers. Pour créer un fichier, il est nécessaire d'effectuer au préalable une initialisation grâce { fdf (f) et f = f <>J
à la procédure I n it É criture. Son effet sur un fichier f est donné par :
{f = x}
{} I n i t É criture ( f ) { f = <> e t fdf (f) J I n itLecture ( f )
_, <-
L'initialisation en écriture d'un fichier a donc pour effet de lui affecter une suite de com {f = f, f = <> et n on fdf (f) et fl=premi er (x) }
posants vide et peu importe si le fichier contenait des éléments au préalable. Notez que l'initialisation en lecture d'un fichier non vide a pour effet d'affecter à ]a va
La procédure é c r i r e ajoute un composant à la fin du fichier. Remarquez que le prédicat riable tampon la première valeur du fichier.
f df ( f ) est toujours vrai.
L'opération de lecture, l i re, renvoie la valeur de l'élément courant et change la position
x,
{f = e = t E T et fdf (f) } } courante (i.e. passe à la suivante). Nous a11ons distinguer le cas où l'élément à lire est le
é c r i re ( f , e ) dernier du fichier et Je cas où i1 ne ]'est pas.
{ f = X & <t> e t fdf(f) } <- _,
{f = X et
f < t > e t n on fdf (f) e t ±t t}
e +--- l i r e ( f )
À partir de ces deux opérations, nous pouvons donner le schéma de la création d'un fichier : <- _,
variables de lui fournir un descripteur d'un fichier déjà ouvert, ou un objet de type F I LE (une repré
nbélt type naturel sentation des noms des fichiers indépendante du système d'exploitation).
moyenne type réel
Parmi les méthodes proposées par les classes F i l e i nput S t r e am et
InitLecture ( f ) F i l eOutput S t r e am, il faut retenir plus particulièrement les méthodes :
moyenne +--- 0
- read de F i l e i nput St re am qui lit le prochain octet du fichier et le renvoie sous
nbél t +--- 0
tantque non fdf ( f ) faire
forme d'un entier ; cette fonction renvoie l'entier - 1 lorsque la fin du fichier est at
+- teinte ;
{ moyenne =
'\""l ongueu r( f )
L,i=l Ji et non fdf (f) J - write de F i leOutput S t ream qui écrit son paramètre (de type un byte) sur le
moyenne +--- moyenne + l i re ( f )
fichier ;
nbélt +--- nbélt + 1
- c l o s e qui ferme le fichier.
fintantque
si nbé l t = 0 alors moyenne +--- 0 La méthode suivante assure une copie de fichier sans se préoccuper de son contenu.
sinon moyenne +--- moyenn e / nb é l t
finsi I** Rôle : copie du fi chier source dans l e fichier des tina t i on *I
rendre moyenne public void copie ( St r ing s ource , S t r i n g destinat i o n )
throws IOExcept i on
{
F i le input S t r e arn i s = new F i l e i nput St rearn ( s ou r ce ) ;
F i leOutput St rearn o s = new F i leOutputSt rearn ( de s t i n at i on ) ;
1 5.4 LES FIC H I ERS D E JAVA int c ;
while ( ( c = i s . re ad ( ) ) != -1)
En JAVA , un flot (en anglais stream) est un support de communication de données entre une +-
+-
11 os = is
source émettrice et une destination réceptrice. Ces deux extrémités sont de toutes natures ; ce
o s . write ( (byt e ) c) ;
sont par exemple des fichiers, la mémoire centrale, ou encore un programme local ou distant.
Il fin de fi chier de i s
Les flots sont des objets définis par deux familles de classes. La première, représentée par Il fermer les fichiers i s e t os
les classes I nput St rearn et Ouput S t r e arn, sont des flots d'octets (8 bits) utilisés pour i s . close ( ) ;
l 'échange de données de forme quelconque. La seconde, représentée par les classes Reader os . close ( ) ;
et W r i t er, définit des flots de caractères Unicode (codés sur 16 bits) qui servent en particu
lier à la manipulation de texte.
Le paquetage j ava . i o contient toute la hiérarchie des classes de flots qui assurent toutes Notez que cette méthode signale la transmission possible d'une exception I OExcept ion
-0
0
sortes d'entrée-sortie. Leur description complète n'est pas du ressort de cet ouvrage. Nous -0
0 qui peut être déclenchée par les constructeurs en cas d'erreur d'ouverture des fichiers.
c: ne présenterons, dans cette section, que les classes qui permettent la manipulation séquen c:
=i =i
0
tielle de fichiers de données, élémentaires et structurées. Nous traiterons le cas particulier des 0 Avec la version 7 de Java, il n'est plus utile de fermer explicitement les :fichiers, s'ils ont
"<:t" "<:t"
,..-1
fichiers de texte à la fin du chapitre. ,..-1 été ouverts dans une clause try-wi th-re s sou rces. Cette nouvelle forme de try possède
0 0
N N une partie déclarative pour les ressources à fermer automatiquement. La fonction précédente
@ @
....... ....... peut se récrire comme suit :
1 5.4. 1 Fichiers d'octets ..c:
� O'l
·c ·c
> > public void copieO ( St ring s ou r c e , S t r ing de s t ) throws IOExcept ion
Cl. Cl.
0 On utilise les fichiers d'octets pour manipuler de l' information non structurée, ou du moins 0
u u {
dont la structure est sans importance pour le traitement à effectuer. La déclaration et l' ouver try ( F i l e i nputStrearn i s = new F i l e i nputStrearn ( s ou r ce ) ;
ture d'un fichier en lecture, respectivement en écriture, est faite avec la création d'un objet de F i leOutputSt rearn o s = new F i l eOutput St rearn ( de st ) )
type F i l e i nput S t re am, respectivement F i leOutput Stream :
int c;
F i l e i nputSt ream i s = new F i l e input S t r e am ( " e n t r é e " ) ;
while ( (c i s . re ad ( ) ) != -1) o s . wr i t e ( (byt e ) c) ;
F i l eOutputSt ream o s = new F i leOutput St ream ( " s o r t i e " ) ;
Ces classes offrent plusieurs constructeurs. Celui de l'exemple précédent admet comme
donnée une chaîne de caractères qui représente un nom de fichier. Il est également possible
15.4 Les fichiers de Java 1 69 1 70 Chapitre 1 5 • Les fichiers séquentiels
+-
1 5.4.2 Fichiers d'objets élémenta i res 11 moyenne 2=:::,,7ueur( f ) fi et non fdf (f)
moyenne += f . r e a d i nt ( ) ;
Les c1asses Dat a input S t ream ou Dat aOutput S t ream permettent de structurer, en nbé l t + + ;
lecture ou en écriture, des fichiers d'octets en fichiers de type élémentaire. Les construc
teurs de ces deux classes attendent donc des objets de type F i l e i nput S t ream ou
F i l eOutput St ream. finally
return nbélt==O ? 0 moyenne / ( double) nbélt ;
D at a i np u t S t ream i s
new D a t a i nput S t r e am ( new F i l e input S t r e am ( " e n t r é e " ) ) ;
}
Dat aOutput St ream o s = Il fin classe Fi chi er
new DataOutput S t r e am ( new Fi leOutput S t r e am ( " s o rt i e " ) ) ;
On désire maintenant fusionner deux suites croissantes d'entiers. Ces deux suites sont
Ces classes fournissent des méthodes spécifiques selon la nature des éléments à lire ou contenues dans deux fichiers, f et g. Le résultat de la fusion est une troisième suite elle
à écrire (entiers, réels, caractères, etc.). Le nom des méthodes est composé de read ou même croissante placée dans le fichjer h.
w r i t e suffixé par le nom du type élémentaire (e.g. readlnt, write lnt, readF l oat,
f -10 -2 0 5 89 100
w r i t e F l o a t , etc.).
g -50 0 1
Les opérations de lecture émettent l'exception EOFExcep t i o n lorsque la fin de fichier est h fusionner ( f , g ) = -50 -10 -2 0 0 1 5 89 100
atteinte. Le traitement de l'exception placée dans une clause catch consistera en général à
fermer le fichier à l'aide de la méthode c l o s e . On peut regretter l'utilisation par le langage du L'algorithme lit le premier élément de chacun des fichjers f et g. Il parcourt ensuite les
mécanisme d'exception pour traiter la fin de fichier. Est-ce vraiment une situation anormale deux fichiers simultanément jusqu'à atteindre la fin de fichjer de l'un ou de 1' autre. Les élé
que d'atteindre la fin d'un fichier lors de son parcours ? ments courants des deux fichiers sont chaque fois comparés. Le plus petit est écrit sur le
fichier h. Sur le fichier qui le contenait, on lit le prochain entier. Lorsque la fin de l'un des
Programmons les deux algorithmes de création d'un fichier d'entiers tirés au hasard, et du
deux fichiers est atteinte (remarquez qu'il n'est pas possible d'atteindre la fin des deux fi
calcul de leur moyenne. Nous définirons une classe Fichier qui contiendra un constructeur qui
chiers simultanément), les entiers restants sont écrits sur h, puisque supérieurs à tous ceux
fabrique le fichier d' entiers, et une méthode moyenne qui calcule la moyenne. Cette classe
qui précèdent. Cet algorithme s'exprime formellement :
possède un attribut privé, nomF ich, qui est le nom du fichier.
Algorithme f u s i onner
class Fichier { {An técéden t : f et g deux fi chi ers qui con t i ennent des
private S t r i n g nomF i ch ; s u i tes croissantes d ' en ti ers }
I * * Rôle : crée u n fi chi er de n e n t i ers t irés a u hasard *I { Conséquen t : h = sui t e croi ssant e d ' en t i ers rés u l t a t
F i c h i e r ( St r i n g nom, int n ) throws IOExcept i o n de l a fusi on des s u i t e s de f e t g }
{ données f , g type fichiers de ent i e r
nomF i ch = nom; résultat h type fichier de ent i e r
-0 -0
0 Il créer un généra teur de nombres aléat oires 0
c: c:
=i =i
0 Random rand = new Random ( } ; 0 In itLecture ( f ) I n itLecture ( g ) Init É criture ( h )
"<:t' try ( D a t aOutput St ream f = "<:t" s i non fdf ( f ) et non fdf ( g ) alors
,.-1 ,.-1
0 new Dat aOutputSt ream (new F i leOutputSt ream ( n omF i ch ) ) ) 0
N N { l es deux fichiers ne son t pas vi des }
@ @ x +--- l i re ( f )
....... .......
..c: for ( int i=O ; i<n; i++) f . wr i t e i nt ( ra n d . next i n t ( ) ) ; ..c: y +--- l i r e ( g )
O'l O'l
·c Il l e fi chi er f con t i en t n réels t i rés a u h asard ·c finsi
> >
Cl. Cl.
0 0 tantque non fdf ( f ) et non fdf ( g ) faire
u u
{me t t re dans h min (f, g) e t passer a u s u i vant }
I * * Rôle renvoie la moyenne des val eurs du fi chier courant *I si x � y alors
public double moyenne ( ) throws I OEx cept i o n écrire ( h , x )
{ x +--- l i r e ( f )
int nbé l t= O , moyenne=O ; sinon {x>y}
try ( D a t a input S t r e am f = écrire ( h , y )
new D at a i nputSt ream ( new F i le i np u t S t ream ( nomF i ch ) ) ) y +--- l i r e ( g )
finsi
while ( true ) fintantque
{ fdf (f) xou fdf (g) }
15.4 Les fichiers de Java 171 1 72 Chapitre 1 5 • Les fichiers séquentiels
h . wr i t e i n t ( x ) ;
try { x = f . readint ( ) ;
L'algorithme de la procédure de recopie ne présente aucune difficulté d'écriture : catch ( EOFExcept i o n e )
-+
{An t écédent : f non vide et ouvert en lect ure Il fdf (f) ::::} recopi er y et g sur h
g ouvert en écri t ure } h . wr i t e i nt ( y ) ;
{Rôle : recopie à l a fin de g les élémen t s de f} recopi e r ( g , h ) ;
procédure recopi e r ( donnée f fichier de ent i e r return;
résultat g : fichier de ent i e r
variable x type ent i e r
répéter el se Il x>y ::::} écrire y sur h
x +- l i re ( f ) h . w r i t e int ( y ) ;
écrire ( g , x ) try { y = g . r e a d i nt ( ) ;
jusqu ' à fdf ( f ) catch (EOFExcep t i o n e )
-+
finproc { recopier} 11 fdf (g) ::::} recopi er x e t f sur h
h . w r i t e i nt ( x ) ;
Programmons en JAVA cet algorithme avec l a méthode fusionner qui complétera la recopier ( f , h ) ;
classe F i c h i e r . Cette méthode fusionne les deux fichiers passés en paramètre. L'objet cou return;
rant contient le résultat de la fusion.
I* * Antécéden t fl et f2 deux noms de fi chiers qui con t i ennent des
* s u i t es croissantes d ' en t i ers }
* Conséquen t l e fi chi er courant nomFich est l a s u i t e croi ssan t e Il fin fusi onner
* d ' en t i ers rés u l t a t de l a fus i on des sui tes de fl et f2
Vous remarquerez que la recopie des derniers entiers, lorsque la fin d'un des fichiers
*I
public void f u s i onner ( St r i n g f l , S t r ing f2 )
est détectée, est faite dans la boucle, et non pas après, comme 1' algorithme le suggère. Si
-0 throws IOExcept i o n , EOFExcept i o n { -0
une clause catch avait été placée après l 'énoncé itératif, elle aurait attrapée l'exception
0 0
c: D a t a i nputStream f = c: EOFExcept ion, mais sans pouvoir en détermjner la provenance (i.e. fin de fichier de f ou
=i =i
0 new D a t a i np u t S t ream (new F i l e i nput St ream ( f l ) ) ; 0 de g).
"<:t" "<:t"
,.-1 D a t a i nputStream g = ,..-1
0 0 La méthode recopie est déclarée privée, dans la mesure où elle n'est utilisée que dans la
N new D a t a i npu t S t r e am (new F i l e i nput St ream ( f 2 ) ) ; N
@ @ classe.
Dat aOutput S t r e am h =
...... .......
..c: ..c: I * * Antécéden t : fi chi er i n on vide et ouvert en lect ure
O'l new Dat aOutput S t r e am ( new F i l eOutput St ream ( nomF i ch ) ) ; O'l
·c
> int x , y;
·c
> * fichier o ouvert en écri t ure
Cl. Cl.
0 * Rôle : recopie à la fin de o les élémen t s de i
8 Il l i re l e premier en t i er de cha cun des fi ch i ers u
*I
try { x = f . readint ( ) ; }
-+ private void recop i e r ( D at a i nputSt rearn i , Dat aOutputStrearn o )
catch ( EOFException e ) Il fdf (f) ::::} recopier g sur h
{
recop i e r ( g , h ) ;
try
return;
while (true) o . writ e i nt ( i . readint ( ) ) ;
try y = g . readint ( ) ;
-+ catch (EOFExcept i o n e ) { Il fdf ( i )
catch (EOFExcept i o n e ) { Il fdf (g) ::::} recopier X et f sur h i . close ( ) ; o . close ( ) ;
15.5 Les fichiers de texte 173 1 74 Chapitre 1 5 • Les fichiers séquentiels
1 5.4.3 Fichiers d'objets structurés introduisent une structuration en ligne. Un fichier de texte est donc vu comme une suite de
lignes, chaque ligne étant une suite de caractères quelconques terminée par un caractère de fin
La lecture et l'écriture de données structurées sont faites en reliant des ob de ligne. Certains langages comme le langage PASCAL définissent même un type spécifique
jets de type F i l e input S t r e am ou F i l eOutput S t r e am avec des objets de type pour les représenter. Pour d'autres langages, ils sont simplement des fichiers de caractères.
Ob j e ct input S t r e am ou Ob j e ctOutput S t r eam. Ces classes fournissent respective
ment les méthodes readOb j e ct et writ eOb j e ct pour lire et écrire un objet. Le frag Alors que les éléments de ces fichiers de texte sont des caractères, bien souvent les lan
ment de code suivant écrit dans le fichier F r e c t un objet de type Rectangle (défini au gages de programmation autorisent l'écriture et la lecture d'éléments de types différents, mais
chapitre 7), puis le relit. qui imposent une conversion implicite. Par exemple, il sera possible d'écrire ou de lire un en
tier sur ces fichiers. L'écriture de l'entier 125 provoque sa conversion implicite en la suite de
Rectangle r = new Re ctangle ( 3 , 4 ) ;
Ob j ectOutputSt ream o s =
trois caractères ' 1 ' , ' 2 ' et ' 5 ' successivement écrits dans le fichier de texte. Il est important de
new Ob j e ctOutput S t r e am ( new Fi leOutput S t r e am ( " F r e ct " ) ) ; bien comprendre que les fichiers de texte ne contiennent que des caractères, et que la lecture
Il écri t ure d ' un objet de type Rectangle sur l e fi chier os ou l'écriture d'objet de type différent entraîne une conversion de type depuis ou vers le type
o s . wr i t eüb j e ct ( r ) ; caractère.
o s . cl o s e ( ) ;
Ob j ect input S t r e am i s La plupart des langages de programmation définit des fichiers de texte liés implicitement au
new Ob j e ct input St ream ( new F i l e i nputStream ( " F r e c t " ) ) ; clavier et à l'écran de l'ordinateur. Ces fichiers sont appelés fichier d'entrée standard et fichier
Il lect ure d ' un objet de type Rectangle sur l e fi chier i s de sortie standard. Certains langages proposent un fichier de sortie d'erreur standard dont les
r = ( Rect angl e ) i s . readüb j e ct ( ) ; programmes se servent pour écrire leurs messages d'erreurs. Le fichier d'entrée standard
is . close ( ) ; ne peut évidemment être utilisé qu'en lecture, alors que ceux de sortie standard et de sortie
d'erreur standard ne peuvent l'être qu'en écriture. Ces fichiers sont toujours automatiquement
Notez que la méthode readOb j e ct renvoie des objets de type Ob j e ct qui doivent être ouverts au démarrage du programme.
explicitement convertis si la règle de compatibilité de type l'exige. Dans notre exemple, la
conversion de l'objet lu en Rectangle est imposée par le type de la variable r 1 . Si le type
de conversion n'est pas celui de l'objet lu, il se peut alors que l'erreur ne soit découverte qu' à
l'exécution. Par exemple, s i l'objet l u est de type Rect angle, le fragment de code suivant 1 5.6 LES FICHI ERS DE TEXTE EN JAVA
ne produit aucune erreur de compilation.
Integer z = ( I nt e g e r ) f . re adOb j e ct ( ) ; Les flots de texte sont construits à partir des sous-classes issues des classes Reade r, pour
S y s t em . out . p r i n t l n ( z ) ; les flots d'entrée, et Writer, pour ceux de sortie. Le type des éléments est le type char
(UNICODE). Les classes F i l eReader et F i l eW r i t e r permettent de définir les fichiers de
De plus, la méthode readOb j e ct émet l'exception EOFExcept ion si la fin de fichier est caractères, mais la structuration en ligne des fichiers de texte n'est pas explicitement définie.
atteinte. Enfin, les classes qui définissent des objets, peuvant être écrits et lus sur des fichiers, Toutefois, le caractère ' \n' permet de repérer la fin d'une ligne.
doivent spécifier dans leur en-tête qu'elles implémentent l'interface S e r i a l i z ab l e . L'en- -0
-g 0
c: Écrivons le programme qui compte le nombre de caractères, de mots et de lignes contenus
§ tête de la classe Rect angle s'écrit alors : =i
0 0
"<:t" dans un fichier de texte 2 . On considère les mots comme des suites de caractères séparés par
� public class Rectangle implements S e ri a l i z ab l e { ,..-1
0 0 des espaces, des tabulations ou des passages à la ligne.
N N
@ @ La seule petite difficulté de ce problème vient de ce qu' il faut reconnaître les mots dans le
...... .......
..c ..c:
·c
O'l O'l
·c texte. Il est résolu simplement à l' aide d'un petit automate à deux états, dansMot et horsMot.
> Si elles ne le font pas, les méthodes w r i t eOb j e ct et readOb j e ct émettront, respecti >
Cl. Cl. Si l 'état courant est dansMot et le caractère courant est un séparateur, l 'état devient horsMot
u
0 vement, les exceptions Not S e r i a l i z ab l eExcept ion et C l a s sNotFoundExcept i on. u
0
et on incrémente le compteur de mots puisqu'on vient d' achever la reconnaissance d'un mot.
Si l 'état courant est horsMot et le caractère courant n'est pas un séparateur, l'état devient
dansMot. Notez que l'incrémentation du compteur de mot aurait pu tout aussi bien se faire
1 5. 5 L E S FIC H I ERS D E TEXTE au moment de la reconnaissance du premier caractère d'un nouveau mot. Le tableau suivant
résume les changements d'états et les actions à effectuer.
Les fichiers de texte jouent un rôle fondamental dans la communication entre le pro
gramme et les utilisateurs humains. Ces fichiers ont des composants de type caractère et
l . Le compilateur signale une erreur si la conversion n'est pas explicitement faite. 2. À l'instar de la commande wc du système d'exploitation UNIX.
15.6 Les fichiers de texte en Java 175 1 76 Chapitre 1 5 • Les fichiers séquentiels
I�
import j a v a . i o . * ;
séparateur non séparateur public class Wc {
public static void main ( String [] args ) throws I OE xcepti o n
if ( a rgs . l ength ! = l ) {
état+--horsMot Il l e programme a t t end un seul nom de fi chier
dansMot S y s t em . e r r . p r i n t l n ( " Usa ge._. : ,_,j a v a ._.Wc,_, f i c h i e r " ) ;
nbmot+--nbmot+ 1
S y s t em . exit ( l ) ;
Le fragment de code suivant recopie un fichier source dans un fichier de s t i n a t i o n en des fichiers de texte sur huit bits, et ces classes permettront la conversion d'un octet en
numérotant les lignes 3 . La valeur entière du compteur de lignes est écrite en début de ligne un caractère UNICODE codé sur seize bits, et réciproquement. Notez que les instructions
grâce à la méthode pr int. suivantes :
F i l eReade r i s =new F i l eReader ( s ource ) ; new F i l eReade r ( f )
P r intWr i t e r o s =new P r intWrit e r ( new F i leWrit e r ( de s t i n a t i on ) ) ; new F i leWriter ( f )
int c , nbligne= l ;
o s . p rint ( l + " ww ") ; sont strictement équivalentes à :
while ( ( c=i s . read ( ) ) 1 = -1) {
o s . wr i t e ( c ) ; new Input S t r e amReade r ( new F i l e i nput S t r e am ( f ) )
if ( c== ' \ n ' ) new Output S t r e amWrit e r ( new F i leOutput St ream ( f ) )
Il début d ' une n ouvel l e l i gn e
o s . p rint ( ++nbligne + "w w " ) ;
i s . close ( ) ; 1 5. 7 EXERCICES
os . close ( ) ;
Il est étonnant que l'API ne propose pas de classe équivalente à Pri ntWri ter pour lire Exercice 15.1. Reprogrammez l' algorithme d' ÉRATOSTHÈNE de la page 1 03 en choisissant
des objets de type quelconque à partir de leur représentation sous forme de caractères. Aucune un fichier séquentiel d'entiers pour représenter le crible. Notez que vous aurez besoin d'un
classe standard ne définit, par exemple, une méthode readint qui lit une suite de chiffres sur second fichier auxiliaire.
un fichier de type F i l e Reader pour renvoyer la valeur entière qu'elle représente. Pourtant Écrivez un programme qui supprime tous les commentaires d'un fichier
Exercice 15.2.
une telle classe est très utile, et bien souvent, les programmeurs sont amenés à construire leur
contenant des classes JAVA. On rappelle qu'il existe trois formes de commentaires.
propre classe.
Le langage JAVA propose trois fichiers standard : l'entrée standard S ys tem . in, la sortie Exercice 15.3. Le fragment de code (donné à la page 177) qui recopie le contenu d'un
standard S y s t em . out et la sortie d'erreur standard S y s t em . e r r . Les méthodes offertes fichier en numérotant les lignes a un léger défaut. Il numérote systématiquement une dernière
par Syst em . i n sont celles de la classe I nput S t r eam, et ne permettent de lire que des ligne inexistante. Modifiez le code afin de corriger cette petite erreur.
caractères sur huit bits ; aucune n'offre la possibilité de lire des objets de type quelconque Écrivez un programme qui lit 1' entrée standard et qui affiche sur la sortie
Exercice 15.4.
après conversion. Les fichiers S y st em . out et S y s t em . e r r de type P r i n t S t ream uti
standard le nombre de mots, le nombre d'entiers et le nombre de caractères autres lus. Un
lisent également des flots d'octets, mais la méthode print (ou pri nt l n ) permet l'écriture
mot est une suite de lettres alphabétiques, un entier est une suite de chiffres (0 à 9), et un
de n'importe quel type d'objet.
caractère autre est ni une lettre alphabétique, ni un chiffre, ni un espace.
Depuis la version 6, le paquetage j ava . io propose la classe C o n s o l e qui permet de
gérer, s'il existe, le dispositif d'entrée/sortie interactif lié à la machine virtuelle JAVA . Une Exercice 15.5. Rédigez une classe F i chierTexte qui gère la notion de ligne des fichiers
-0 -0
0 0 de texte. Vous définirez toutes les méthodes du modèle algorithmique de fichier donné dans
c: instance (unique) de Console est renvoyée par la méthode S y s tem . cons o l e ( ) , ou null c:
=i =i
0 si elle n'existe pas. Les méthodes re ader et writ e r renvoient, respectivement, des ins 0 ce chapitre, complétées par les fonctions fdln, l i re l n et é c r i re l n . La fonction fdln
"<:t" "<:t" retourne un booléen qui indique si la fin d'une ligne est atteinte ou pas. La procédure l i re ln
,..-1
0
tances de type Reader et P rintWr i t e r , les flots associés à l'entrée et à la sortie de la ,..-1
0
N console. N fait passer à la ligne suivante, et la procédure é c r i r e l n écrit la marque de fin de ligne. Avec
@ @ ces nouvelles fonctions, l'algorithme de consultation d'un fichier de texte a la forme suivante :
....... Les méthodes de lecture de la classe C o n s o l e ne permettent pas la lecture d'objets de .......
..c: ..c:
O'l O'l
·c type élémentaire. Toutefois, ce type de lecture peut être assuré à l ' aide d'un petit analyseur ·c { i n i t i a l i s a t i on }
> >
Cl. de texte proposé par la classe S c anner du paquetage j ava . u t i l . Cl. I n i t Le c t u re ( f )
0 0
u u
Dans cet ouvrage, nous utilisons la classe S t d i nput pour lire sur l'entrée standard des tantque non fdf ( f ) faire
objets de type élémentaire. Ce n'est pas une classe standard de l' API. Elle a été développée { on est en début de l i gne, t ra i t er la l i gne couran t e }
tantque non fdln ( f ) faire
par l'auteur pour des besoins pédagogiques et est disponible sur son site.
c +--- l i r e ( f )
Pour conclure, signalons l' existence des classes I nput S t re amRe ader et traiter ( c ) { t ra i t er l e caractère couran t }
Output Stre amWr i t e r qui sont des passerelles entre les flots d'octets et les flots de fintantque
caractères UNICODE. La plupart du temps, les systèmes d'exploitation codent les caractères { on est en fin de l i gne, passer à l a l i gne s u i va n t e }
lireln ( f )
3. Voir également l'exercice correspondant page l 78 afin de corriger l 'erreur de fragment de code. fintantque
15.7 Exercices 1 79
Exercice 15.6. Complétez la classe F i ch i e r Texte afin de permettre des lectures d'objets
de type élémentaire, et des écritures d'objets de type quelconque.
Exercice 15.7. Le jeu Le mot le plus long de la célèbre émission télévisée « Des chiffres
et des lettres » consiste à obtenir, à partir de neuf lettres (consonnes ou voyelles tirées au
hasard), un mot du dictionnaire le plus long possible.
Écrivez un programme JAVA qui tire aléatoirement neuf lettres et qui écrit sur la sortie
standard le mot plus long qu'il est possible d'obtenir avec ce tirage. Vous pourrez placer le
dictionnaire de mots sur un ou plusieurs fichiers.
-0
0
c:
=i
0
"<:t'
,..-1
0
N
@
....... .µ
..c: ..c
O'l Ol
·c ï:::
> >-
Cl. 0.
0 0
u u
182 Chapitre 16 • Récursivité
1 6. 1 RÉCURSIVITÉ D E S ACTIONS
1 6. 1 . 1 Défi nition
Dans les langages de programmation, la récursivité des actions se définit par des fonctions
ou des procédures récursives. Une routine récursive contiendra au moins un énoncé d'appel,
direct ou non, à lui-même dans son corps.
Une procédure (ou une fonction) récursive P, définie selon le modèle ci-dessous, crée un
nombre infini d' incarnations de la procédure au moyen d'un nombre fini d'énoncés.
Chapitre 1 6
La définition précédente ne limite pas le nombre d' appels récursifs, et un programme écrit
selon ce modèle ne pourra jamais s' achever. Il est donc nécessaire de limiter le nombre des
appels récursifs. L'appel récursif d'une fonction ou d'une procédure devra toujours apparaître
dans un énoncé conditionnel et s'appliquer à un sous-ensemble du problème à résoudre.
Les fonctions récursives jouent un rôle très important en informatique. En 1 936, avant P= si B alors C(Ei , P) finsi
même l ' avènement des premiers ordinateurs électroniques, le mathématicien A. CHURCH 1 ou bien
avait émis la thèse 2 que toute fonction calculable, c'est-à-dire qui peut être résolue selon un
P = C(Ei , si B alors P finsi)
algorithme sur une machine, peut être décrite par une fonction récursive.
Dans la vie courante, il est possible de définir un oignon comme de la pelure d'oignon qui Toutefois, il faut être sûr que l'exécution des instructions conduira tôt ou tard à la branche
entoure un oignon. De même, une matriochka est une poupée russe dans laquelle est contenue de l'énoncé conditionnel qui ne contient pas d'appel récursif. Comme pour les énoncés ité
une matriochka. Ces deux définitions sont dites récursives. On parle de définition récursive ratifs, il est essentiel de prouver la finitude de la routine P. Pour cela, on lui associe un ou
lorsqu'un terme est décrit à partir de lui-même. En mathématique, certaines fonctions, comme plusieurs paramètres qui décrivent le domaine d'application de la routine. Les valeurs de
-0 -0 ces paramètres doivent évoluer pour restreindre le domaine d'application et tendre vers une
0
c: la fonction factorielle n! = n x ( n - 1 ) !, sont également définies selon ce modèle. On parle 0
c:
=i
alors de définition par récurrence. =i valeur particulière qui arrêtera les appels récursifs. Le modèle devient alors :
0 0
"<:t" "<:t"
,..-1 ,.-1
0
N Les précédentes définitions de l'oignon et de la matriochka sont infinies, en ce sens qu'on 0
N Px = si B alors C(Ei , Px' ) finsi
@ ne voit pas comment elles s'achèvent, un peu comme quand on se place entre deux miroirs @
....... ....... ou bien
..c:
O'l
qui se font face, et qu'on aperçoit son image reproduite à l'infini. Pour être calculables, les ..c:
O'l
·c
> définitions récursives ont besoin d'une condition d'arrêt. Un oignon possède toujours un ·c
> Px = C(Ei , si B alors Px' finsi)
Cl. Cl.
0 noyau qu'on ne peut pas peler, il existe toujours une toute petite matriochka qu'on ne peut 0
où x' est une partie de x . Bien sûr, x et x' ne peuvent être égaux, sinon, en dehors de tout
u u
ouvrir, et enfin O! = 1 .
effet de bord, les appels récursifs seraient tous identiques et la routine ne pourrait s' achever.
Dans ce chapitre, nous aborderons deux sortes de récursivité : l a récursivité des actions et
celle des objets.
1 6. 1 .3 Écriture récursive des routines
Pour beaucoup de néophytes, l'écriture récursive des routines apparaît être une réelle dif
l. Mathématicien américain ( 1 903-1995). ficulté, et bien souvent ceux-ci envisagent a priori une solution non récursive, c'est-à-dire
2. Non contredite jusqu'à aujourd'hui. basée sur un algorithme itératif. Pourtant, certains pensent que l'itération est humaine et la
16. 1 Récursivité des actions 1 83 184 Chapitre 1 6 • Récursivité
récursivité divine. Au-delà de cet aphorisme, l'écriture récursive est immédiate pour des al
'
/
gorithmes naturellement récursifs, comme les définitions mathématiques par récurrence, ou '
/
\
/
I
I
pour ceux qui manipulent des objets récursifs (voir 1 6.2). Elle est aussi plus concise, et sou
vent plus claire que son équivalent itératif, même si sa compréhension nécessite une cer
taine habitude. De plus, l' analyse de la complexité des algorithmes récursifs est souvent plus - -
,
1 1
;-
- - -
Les définitions par récurrence des fonctions mathématiques ont une transposition algorith - - --+-� - - ,
1
mique évidente. Par exemple, les fonctions factorielle et fibonacci 3 s'expriment par récur - -
: -�-
---+- - ,
1
1
rence comme suit :
A B c
fac(O) = 1
fac(n) = n x fac(n - 1 ) , Vn > 0
FIGURE 16.1 Les tours de Hanoï.
fib(l ) = 1
seconde, la fin du monde aura lieu lorsque la pile sera entièrement reconstituée ! Le nombre
fib(2) = 1
total de déplacements est exponentiel. Il est égal à 2n 1 . -
évidemment par l'intermédiaire de l'axe B selon les règles des tours de Hanoï, c'est-à-dire
{An t é cédent : n�O J
de façon récursive. Il suffit ensuite de déplacer les n 1 disques de l ' axe C vers l'axe B, -
3. Cette fonction fut proposée en 1202 par le mathématicien italien LEONARDO PISANO ( 1 175-1250), appelé
{An técédent : n�O J
FIBONACCI (une contraction de filius Bonacci), pour calculer le nombre annuel de couples de lapins que peut pro {Rôl e : écri t sur l a sort i e s tandard l a s u i t e de
duire un couple initial, en supposant que tous les mois chaque nouveau couple produit un nouveau couple de lapins, chi ffres qui forme l ' en tier n }
qui deviendra à son tour productif deux mois plus tard.
16. 1 Récursivité des actions 1 85 186 Chapitre 1 6 • Récursivité
retour de
procédure é c r i reChi f f r e s ( donnée n nature l )
si n�l O alors procédure
(
écrireChiffres(1 ) '1 '
écr ireChi f f re s ( n / 1 0 )
finsi
é c r i r e ( c onve r t i rEnCaractère ( n mod 1 0 ) )
écrireChiffres(1 2) ' 2'
finproc { écrireChi ffre s }
La finitude de cet algorithme est assurée pour tout entier positif. Les appels récursifs s' ap
pliquent à une suite d'entiers naturels qui tend vers un nombre inférieur à dix. Pour cette
valeur, la récursivité s'arrête. Sa programmation en JAVA ne pose pas de difficulté particu
( écrireChiffres(1 25) ' 5'
lière :
I** An técédent : n ? O ( écrireChiffres(1 25 7 ) '7'
(
* Rôle : écrit s u r l a sort i e s t andard l a sui t e de
* chi ffres qui forme l ' en t i er n
*I appels de contexte d' appel
public static void é c r i r eChi f fr e s ( int n ) procédure
{
as sert n>= O ; FIGURE 1 6.2 Exécution de écrireChiffres(1 257).
i f ( n >= l O ) é c r i reCh i f f re s ( n / 1 0 ) ;
Il écrire l a conversi on du chi ffre en cara ct ère parcours à l 'envers de la séquence de chiffres produite est obtenu automatiquement lorsqu'on
S y s t em . out . p r int ( ( char ) (n%10 + '0 ' ) ) ;
dépile la pile d'évaluation, en fin d'exécution de chaque appel récursif. La figure 1 6.2 montre
la pile d' évaluation pour l'appel de la fonction é c r i r e C h i f f r e s ( 1 2 5 7 ) . À gauche de la
pile, les flèches indiquent l'empilement des zones locales produites par les appels récursifs et
à sa droite est la valeur écrite par la procédure une fois l'exécution de chaque appel achevée.
1 6. 1 .4 La pile d'évalution
De nombreux algorithmes récursifs se servent de la pile d'évaluation pour mémoriser
L'écriture itérative de la procédure é c r i re C h i f f r e s nécessite de mémoriser les chiffres des données, en particulier les paramètres transmis par valeur à la routine, et pour récupé
(par exemple dans un tableau) parce que la décomposition par divisions successives produit rer leur valeur lors du retour de l'appel récursif. La fonction f ibonacci et la procédure
les chiffres dans 1 'ordre inverse de celui souhaité. Le calcul du résultat est obtenu ensuite ToursDeHanoï procèdent de la sorte. En exercice, vous pouvez essayer de dérouler à la
après un parcours à l'envers de la séquence de chiffres mémorisée. main la suite des appels récursifs de ces fonctions, mais d'une façon générale, la compréhen
public static void é c r i reChi f f r e s ( int n ) {
sion d'un algorithme récursif doit se faire de façon synthétique à partir du cas général.
assert n >= O ;
-0 -0
0 final int ma x c h i f f r e s= l O
Il 32 b i t s I
; 3 0
c:
=i § 1 6. 1 . 5 Quand ne pas utiliser la récursivité ?
0 int [ ] c h i f f r e s =new int [maxch i f f re s ] ; 0
"<:t" int i = O ;
,.-1
0
Une première réponse à cette question est quand l'écriture itérative est évidente. Typique
N Il décomposer l e n ombre par di vi s i ons s uccessives ment, c'est le cas pour les fonctions fact o r i e l l e et fibona c c i et on leur préférera les
@ Il mémori ser les chi ffres dan s le t ableau
...... ....... versions itératives suivantes :
.c: do ..c
O'l O'l
= x =
·c chi f f re s [ i ++ ] =n% 1 0 ; ·c {Rôle : cal cu l e n ! n n-1 ! , a vec O ! l}
> >
Cl. while ( ( n / =1 0 ) != 0) ; Cl.
0 0 fonction f a c t o r i e ll e ( donnée n : naturel ) : naturel
u Il parcourir l e t ableau des chi ffres en sens i n verse u
variables i , fact de type n a t u r e l
Il et écrire la conversion de chaque chi ffre en caract ère i +--- 0
while (i>O) fact +--- 1
S y s t em . out . print ( ( char) ( chi f f re s [ -- i ] + ( int ) '0 ' ) ) ; tantque i < n faire { fact i!}
i +--- i + l
fact +--- fact X i
Pourquoi la version récursive se passe-t-elle du tableau ? En fait, la séquence d'appels fintantque
récursifs mémorise les chiffres du nombre dans une pile « cachée la pile d'évaluation du »,
{ i =n e t fa ct=n ! }
programme dans laquelle s' empilent les zones locales des procédures et des fonctions. Le rendre fact
finfonc { fa c t ori e l l e }
16.1 Récursivité des actions 187 188 Chapitre 16 • Récursivité
Toutefois, il existe des situations où il est difficile de se prononcer parce que la solution
{Rôle : calcule fib (n) = fib(n-1) + fib (n-2) , pour n>2 itérative est loin d'être évidente. Regardons par exemple la fonction ackermann 4 donnée par
et avec fib (l) = 1 et fib (2) = l } la relation de récurrence suivante :
fonction fibonacc i ( donnée n : naturel) : naturel
variables i, pred, suce de type naturel
ackermann(O, n) =n+l
i t- 1
ackermann ( m, 0) = ackermann(m - 1 , 1 )
pred t- 1
suce t- 1
ackerman n ( m , n) = ackermann(m - 1 , ackermann ( m , n - 1 ) )
tantque i < n faire et dont l a programmation en JAVA est donnée par :
{pred=fib ( i ) et s ucc=fib ( i +l ) }
i t- i+l public static long ackermann ( long m, long n ) {
suce t- succ+pred if (m==O) return n + l ;
pred t- succ-pred el se
fintantque if {n==O) return ackerrnann ( rn- 1 , 1 ) ;
rendre pred el se
finfonc {fibonacci} return acke rmann (m-1 , a ckermann (m, n- 1 ) ) ;
sont équivalents à :
L'écriture itérative de la fonction fact o r i e l l e est une application directe de cette règle Les procédures précédentes étaient bâties sur le même modèle. L'appel récursif est placé
de transformation. directement dans le corps de la procédure :
Troisièmement, quand l 'efficacité en temps d'exécution des programmes est en jeu. La procédure P
récursivité a un coût, celui des appels récursifs des procédures ou des fonctions. Pour p ...
-0 -0
0 0 finproc {P}
c s'en convaincre, comparez les temps d'exécution des versions itérative et récursive de c
::J ::J
0 f ibonacci éc1ites en JAVA avec n = 50 et n = 55. Sur un Intel Core 2, le calcul itéra 0
Mais une routine P peut faire appel à une ou plusieurs autres routines qui, en dernier lieu,
V
T""l V
T""l
0 tif est immédiat, moins d'une milliseconde, alors que le calcul récursif nécessite plus de deux 0 font appel à la routine P initiale. On parle alors de récursivité indirecte. Lorsque seulement
N minutes pour n = 50, et plus de vingt-trois minutes pour n = 55. Au delà, ce sont des heures N
@ @ deux routines sont mises en jeu, on parle de récursivité croisée :
.....
..c
de calcul ! .....
..c
Ol Ol procédure Pl
ï::: Les temps d'exécution précédents ne sont pas une surpdse. Le calcul récursif de ï::: P2 ...
>
a. >
a.
0 fibonacci est paiticulièrement lourd : fibonacci ( 5 ) nécessite pas moins de 9 appels 0 finproc {Pl }
u u
récursifs, et f ibonacci ( 5 O ) plus de 25 milliards ! Le nombre théorique d' appels est égal
à (2/VS)l, 618n - 1 . La complexité de l'algorithmique récursif est exponentie11e. On lui procédure P2
additifs et multiplicatifs, et d'opérandes. La grammaire fixe la priorité des opérateurs : les Si l'on possède une fonction tracer qui trace un segment de droite dans le plan à partir
opérateurs additifs sont moins p1ioritaires que les opérateurs multipl icatifs . de la position courante jusqu'au point de coordonnées (x,y), la procédure A s'écrit :
<expre s s ion> <terme> <express ion> <op-add> <terme> {Rôle : tracer l a partie A de la courbe de Hilbert de n i veau i
<terme> <facteur> 1 <terme> <op-mult> <facteur> h longueur du segment qui relie les courbes de ni veau i - 1 }
<facteur> <variable> 1 <entier> 1 ' ( ' <expres s ion> ' ) ' procédure A ( donnée i : naturel)
si i>O alors
Chaque entité, <expression>, <terme> ou <facteur> se décrit par une procédure.
D (i-1) X � x-h tracer ( x , y )
La réc urs ivité indirecte a lieu dans <facteur> qui appelle récursivement <expre s s ion>
A (i-1) y � y-h trace r ( x , y )
pour analyser des expressions parenthésées. A (i-1) X � x+h tracer ( x , y )
La récursivité indirecte apparaît aussi dans les courbes de HILBERT 5 . Ces courbes sont B (i-1)
telles que toute courbe Hi s'exprime en fonction d'une courbe Hi-l· Plus précisément, finsi
chaque courbe Hi consi ste en l' utilisation de quatre parties (à l échelle 1/2) de Hi-l · Ces
'
finproc {A}
quatre paities A, B, C et D sont reliées entre elles par trois segments de droite de la façon
suivante : L'écriture des procédures B, c et D, bâties sur ce modèle, est immédiate.
'O -0
0 0 Conceptue11ement, une tel1e déclaration décrit une structure infinie, mais en généra] ]es
c c
::J ::J langages de programmation ne permettent pas cette forme d' auto-inclusion des objets. Con
0 0
V
T"l V
T""l trairement à la récursivité des actions, celle des objets ne crée pas « automatiquement » une
0 0 infinité d'incarnations d'objets. C'est au programmeur de créer explicitement chacune de ces
N N
@ @ incarnations et de les relier entre elles.
.....
..c .....
..c
Ol Ol La caractéristique fondamentale des objets récursifs est leur nature dynamique. Les objets
ï::: ï:::
>
a. >
a. que nous avons étudiés jusqu'à présent possédaient tous une taille fixe. La taille des objets
0 0
u u récursifs pouITa, quant à e11e, varier au cours de 1 'exécution du programme.
Pour mettre en œuvre la récmsivité des objets, les langages de programmation proposent
des outils qui permettent de créer dynamiquement un objet du type voulu et d' accéder à cet
objet.
FIGURE 16.3 Courbes de Hilbert de niveau 5. Des langages comme C ou PASCAL ne permettent pas une définition directement récursive
des types, mais ] 'autorisent au moyen de pointeurs. Ils mettent à la disposition du program
5. DAVID HILBERT, mathématicien allemand ( 1 862-1943), proposa cette courbe en 1890. Ce type de figure meur des fonctions d' allocation mémoire, comme rnalloc et new, pour créer dynamique
géométrique est plus connu aujourd'hui sous Je nom de fractale. ment les incarnations des objets auxquelles il accède par l'intermédiaire des pointeurs.
16.2 Récursivité des objets 191 192 Chapitre 16 • Récursivité
espace libre
\\
�---+-
\\
---
R \\\\
FIGURE 16.5 Organisation de la mémoi re .
la figure 16.4 par des flèches. Le symbole de la terre des électriciens indique la fin de la
FIGURE 16.4 La généalogie de Lo u ise . récursivité. En JAVA, il correspond à une référence égale à la constante null.
Pour de nombreuses raisons, mais en particulier pour des raisons de fiabilité de construc Les supports d'exécution des langages de programmation placent les objets dynamiques
tion des programmes, les langages de programmation modernes ont abandonné la notion de dans une zone spéciale, appelée tas. La figure 16.5 montre l'organisation classique de la mé
pointeur. C'est le cas de JAVA qui permet des définitions d'objet vraiment récursives. Ainsi, moire lors de l'exécution d'un programme. La zone globale est une zone de taille fixe qui
le type Arbre Généalogique sera déclaré en JAVA comme suit : contient des constantes et des données globales du programme. La pile d'évaluation, dont la
taille varie au cours de l'exécution du programme, contient l'empilement des zones locales
class ArbreGénéalogique des routines appelées avec leur pile d'évaluation des expressions. Enfin, le tas contient les
String prénom; objets alloués dynamiquement par le programme. Le tas croît en direction de la pile d'éva
Il défi n i t i on de l ' ascendance luation. S'ils se rencontrent, le programme s'arrête faute de place mémoire pour s'exécuter.
ArbreGénéalogique mère, père;
Il le constructeur Selon les langages, la suppression des objets dynamiques, c'est-à-dire la libération de la
ArbreGénéalogiqu e ( String s ) { place mémoire qu'ils occupent dans le tas, peut être à la charge du programmeur, ou laisser
prénom=s ; au support d'exécution. La suppression des objets dynamiques est une source de nombreuses
-0 'O
erreurs, et il est préférable que le langage automatise la destruction des objets qui ne servent
0 Il ArbreGénéalogique 0 plus. C'est le choix fait par JAVA.
c c
::J ::J
0 0 Dans les prochains chapitres, nous aurons souvent l'occasion de mettre en pratique des
V
T""l Cette définition est possible en JAVA car les attdbuts mère et père sont des références à V
T"l
0 0 définitions d'objet récursif. De nombreuses structures de données que nous aborderons se
N des objets de type ArbreGénéalog ique et non pas l ' objet lui-même. L'arbre généalogique N
@ @
ront définies de façon récursive et les algorithmes qui les manipuleront seront eux-mêmes
de Louise, donné par la figure 16.4, est produit par la séquence d'instructions suivante :
.....
..c .....
..c naturellement récursifs .
Ol Ol
ï::: ArbreGénéalogique ag; ï:::
>
a. >
a.
ag = new ArbreGénéalogique ( " Lo u i se " ) ;
0 0
u ag . mère = new ArbreGénéalogique ( " Mahaba " ) ; u
ag . père = new ArbreGénéalogique ( " Pa u l " ) ;
1 6.3 EXERCICES
ag . mère . mère new ArbreGénéalogique ( " Monique " ) ;
ag . père . mère = new ArbreGénéalogique ( " Maud" ) ; Exercice 16.1. Programmez en JAVA les algorithmes fibonacci et ToursdeHanoï don
ag . père . père = new ArbreGénéalogique ( " Dhakwan " ) ; nés à la page 183.
ag . père . père . père = new ArbreGénéalogique ( " J a c q u e s " ) ;
Exercice 16.2. Éctivez en JAVA et de façon récursive la fonction puissance qui élève un
Chacun des ascendants, lorsqu'il est connu, est créé grâce à l'opérateur new et est relié nombre réel à la puissance n (entière positive ou nulle). Note : lorsque n est pair, pensez à
à sa propre ascendance par l'intermédiaire des références. Celles-ci sont représentées sur l'élévation au carré.
16.3 Exercices 193
Cette déclaration indique le nom du type abstrait à définir, ainsi que certaines constantes
qui jouent un rôle particulier. La notation :
Structures de données
EntierNaturel. 0 E EntierNaturel
déclare le type abstrait EntierNaturel des entiers naturels, qui possède un élément particulier
dénoté O.
La déclaration ensembliste de ce11ains types abstraits nécessite de mentionner d'autres
types abstraits. Ces derniers sont introduits par le mot-clé utilise. Le type abstrait Ensemble
utilise deux autres types abstraits, booléen et E. Il possède la définition suivante :
Le terme structure de données désigne une composition de données unies par une même
sémantique. Mais, cette sémantique ne se réduit pas à celle des types (élémentaires ou struc Ensemble utilise E, booléen. 0 E Ensemble
turés) des langages de programmation utilisés pour programmer la structure de données. Dès
le début des années 1970, C.A.R. HOARE [Hoa72] mettait en avant l'idée qu'une donnée où E définit les éléments d'un ensemble, et 0 un ensemble vide. Remarquez que les types
représente avant tout une abstraction du monde réel définie en terme de structures abstraites, abstraits utilisés n'ont pas nécessairement besoin d'être au préalable entièrement définis. Pour
et qui n'est d'ailleurs pas nécessairement mise en œuvre à l'aide d'un langage de program la spécification du type abstrait Ensemble de ce chapitre, seule la connaissance des deux
mation patiiculier. D'une façon plus générale, un programme peut être lui-même modélisé en éléments vrai et faux de l'ensemble des booléens nous est utile.
-0 termes de données abstraites munies d'opérations abstraites. 'O
0 0
c c
::J ::J
0 0 Description fonctionnelle
V
T""l Ces réflexions ont conduit à définir une structure de données comme une donnée abstraite, V
T"l
0 dont le comportement est modélisé par des opérations abstraites. C'est à partir du milieu 0
N N La définition fonctionnelle présente les signatures des opérations du type abstrait. Pour
@ des années 1970 que la théorie des types abstraits algébriques est appai·ue pour déc1ire les @ chaque opération, sa signature indique son nom et ses ensembles de départ et d'arrivée. L'en
.....
..c .....
..c
Ol structures de données. Définis en termes de signature, les types abstraits doivent d'une part, Ol semble des entiers naturels peut être décrit à l'aide de l'opération suce qui, pour un entier
ï::: garantir leur indépendance vis-à-vis de toute mise en œuvre particulière, et d'autre part, offrir ï:::
>
a. >
a. naturel, fournit son successeur. Il est également possible de définir les opérations arithmé
0 un support de preuve de la validité de leurs opérations. 0 tiques + et x .
u u
Dans ce chapitre, nous verrons comment spécifier une structure de données à l'aide d'un
type abstrait, et comment l'implémenter dans un langage de programmation particulier suce EJntierNaturel -t EJntierNaturel
comme JAVA. Dans les chapitres suivants, nous présenterons plusieurs structures de données + EJntierNaturel x EJntierNaturel -t EJntierNaturel
fondamentales, que tout informaticien doit connaître. Il s'agit des structures linéaires, de la X EJntierNaturel x EJntierNaturel -t EJntierNaturel
structure de graphe, des structures arborescentes et des tables.
Si on munit le type abstrait Ensemble des opérations est-vide ?, E, ajouter et union, le type
abstrait contiendra les signatures suivantes :
17. 1 Définition d'un type abstrait 197 198 Chapitre 17 • Structures de données
Dans la mesure où le langage JAVA n' autorise pas la surcharge des opérateurs, les symboles public void a j outer ( E x ) {
+ et * n'ont pu être utilisés et les opérations d'addition et de multiplication ont été nommées.
La définition fonctionnelle du type abstrait Ensemble correspondra à la déclaration de
public void enlever (E x ) throws ExceptionÉ lémentAbsent
l'interface générique suivante :
public interface Ensemble<E>
public boolean estVide ( } ;
public boolean dans ( E x ) ; Il fin classe EnsembleListe
public void a j outer (E x ) ;
public void enleve r ( E x) throws ExceptionÉ lémentAbsent ; Dans les déclarations de l'interface Ensemble et de la classe EnsembleListe, le nom
public Ensemble<E> union (Ensemble<E> x ) ; E est une sorte de paramètre formel qui permet de paramètrer l'interface et la classe sur un
type donné. Le compilateur pourra ainsi contrôler que tous les éléments d'un ensemble sont
de même type.
Notez que la méthode enlever émet une exception si l'élément x à retirer n'est pas pré
Notez que dans la partie implémentation d'un type abstrait, le programmeur devra bien
sent dans l'ensemble. L'exception traduit l'axiome (5) du type abstrait. D'une façon générale,
prendre soin d'interdire l'accès aux. données concrètes, et de rendre publiques les opérations
les exceptions serviront à la définition des fonctions partielles des types abstraits.
du type abstrait.
La définition du type abstrait Ensemble n'impose aucune restriction sur la nature des élé
ments des ensembles. Ceux-ci peuvent être différents ou semblables. Les opérations d'ap
partenance ou d'union doivent s'appliquer aussi bien à des ensembles d'entiers qu'à des en
sembles de Rectangle, ou encore des ensembles d'ensembles de chaînes de caractères.
1 7.3 UTILISATION DU TYPE ABSTRAIT
L'implémentation du type abstrait doit être alors générique, c'est-à-dire qu'elle doit per Puisque la définition d'un type abstrait est indépendante de toute implémentation par
mettre de manipuler des éléments de n'importe quel type. Souvent, il est même souhaitable ticulière, l' utilisation du type abstrait devra se faire exclusivement par l'intermédiaire des
d' imposer que tous les éléments soient d'un type donné. De nombreux langages de program opérations qui lui sont associées et en aucun cas en tenant compte de son implémentation.
mation (ADA, C++, EIFFEL, etc.) incluent dans leur définition la notion de généricité et D' ailleurs, certains langages de progranunation peuvent vous l'imposer, mais ce n'est mal
proposent des mécanismes de construction de types génériques. heureusement pas le cas de tous les langages de programmation et c'est alors au programmeur
Depuis sa version 5.0, JAVA inclut la généricité. JAVA offre la définition de types géné de faire preuve de rigueur !
riques auxquels on passe en paramètre le type désiré. On ne va pas décrire ici tous les détails Les en-têtes des fonctions et des procédures du type abstrait et les affirmations qui dé
de cette notion du langage JAVA. Le lecteur intéressé pouITa se reporter à [GR l l ] . lei, la finissent leur rôle représentent l'interface entre l'utilisateur et le type abstrait. Ceci permet
déclaration de l'interface Ensemble est paramétrée sur le type des éléments de l'ensemble. évidemment de manipuler le type abstrait sans même que son implémentation soit définje,
La mise en œuvre des opérations dépendra des types de données choisis pour implémen mais aussi de rendre son utilisation indépendante vis-à-vis de tout changement d'implémen
'O ter le type abstrait. Pour un ensemble, il est possible de choisir un arbre ou une liste, eux -0 tation.
0 0
c mêmes implémentés à l'aide de tableau ou d'éléments chaînés. L'implémentation de l'inter c Des déclarations de vaiiables de type E n s emble pourront s'écrire en JAVA comme suit :
::J ::J
0 face Ensemble aura par exemple la forme suivante : 0
V
T""l V
T""l Ensemble<Integer> el = new EnsembleListe<Intege r > ( ) ;
0 public class EnsembleListe<E> implements Ensemble<E> 0
N N Ensemble<Rectangle> e 2 = new EnsembleListe<Rectangle> ( ) ;
@ @ Ensemble<Ensemble<String>> e3 =
.....
..c Liste<E> l ;
.....
..c new EnsembleListe<Ensemble<Str ing>> ( ) ;
Ol Ol
ï::: ï::: Ensemble e4 = new EnsembleListe ( ) ;
>
a. >
a.
0 public EnsembleL i s t e ( ) 0
u u
Il Le constructeur Dans ces déclarations, el est un ensemble d' Integer, e2 un ensemble de Rectangle,
et e3 un ensemble d'ensembles de String. En revanche, pour la dernière déclaration, il n'y
a pas de contrainte sur le type des éléments de l'ensemble e 4 et ces éléments pounont donc
public boolean estVide ( ) {
être de type quelconque.
L'utilisation du type abstrait se fera exclusivement par l'intermédiaire des méthodes défi
public boolean dans ( E x) { nies dans son interface. Par exemple, les ajouts suivants seront valides quelle que soit l'im
plémentation choisie pour le type En semble.
17.3 Utilisation du type abstrait 201
el . a jouter ( l 2 3 ) ;
e 2 . a jouter (new Rectangle ( 2 , 5 ) ) ;
e3 . a j outer ( new EnsembleLi ste<String> ( ) ) ;
e 4 . a jouter ( 1 2 3 . 4 5 ) ;
L'exemple suivant montre l'éc1iture d'une méthode générique qui permet de manipuler le
type générique des éléménts d'un Ensemble.
<E> void uneMéthode (Ensemble<E> e) {
E x;
if ( e . dans ( x ) )
Il x E e
Enfin pour conclure, on peut ajouter que si le type abstrait est juste et validé, il y a plus de
chances que son utilisation, exclusivement à l'aide de ses fonctions, soit elle aussi juste.
-0 -0
0 0
c c
::J ::J
0 0
V
T"'l «:!"
T""l
0 0
N N
@ @
.....
..c ......
..c
Ol Ol
ï::: ï::
>
a. >-
0..
0 0
u u
204 Chapitre 1 8 • Structures linéaires
est 2, et ainsi de suite. L'ajout et la suppression d'un élément peut se faire à n'importe quel
rang valide de la liste.
.Ciste est 1 'ensemble des listes linéaires non ordonnées dont les éléments appaitiennent
à un ensemble E quelconque. L'ensemble des entiers représente le rang des éléments. La
Cha pitre 1 8 constante listevide est la liste vide .
Les axiomes suivants déclivent les quatre opérations applicables sur les listes. La longueur
et la séquence vide : d'une liste vide est égale à zéro. L'ajout d'un élément dans la liste augmente sa longueur de
un, et sa suppression la réduit de un.
-0 s = <> 'O
0 0 Vl E .Ciste, et Ve E E
c c
::J ::J
0 0 (1) longueur(listevide) = 0
V Les opérations d'ajout et de suppression d'éléments sont les opérations de base des struc V
T""l T"l
0 tures linéaires. Selon la façon dont procèdent ces opérations, nous distinguerons différentes 0 (2) Vr, 1 ( r ( longueur(l), longueur(supprimer(l, r)) = longueur(l) - 1
N N
@ sortes de structures linéaires. Les listes autorisent des ajouts et des suppressions d'éléments @ (3) Vr, 1 ( r ( longueur(l) + 1, longueur(ajouter(l, r, e)) = longueur(l) + 1
.....
..c n'importe où dans la séquence, alors que les piles, les files et les dèques ne les permettent .....
..c
Ol Ol L'opération ième renvoie l'élément de rang r, et n'est définie que si le rang est valide.
ï::: qu'aux extrémités. On considère que les piles, les files et les dèques sont des fom1es particu ï:::
>
a. >
a.
0 lières de liste linéaire. Dans ce chapitre, nous commencerons par présenter la forme générale, 0
u
puis nous étudierons les trois formes particulières de liste. u (4) Vr, r < l et r > longueur(l), � e, e = ième(l , r)
L'opération supprimer retire un élément qui appartient à la liste, c'est-à-dire dont le rang
est compris entre un et la longueur de la liste. Les axiomes suivants indiquent que le rang des
1 8. 1 LES LISTES éléments à droite de l' élément supprimé est décrémenté de un.
La liste définit une forme générale de séquence. Une liste est une séquence finie d'éléments (5) Vr, 1 ( r ( longueur(l ) et l ( i < r ième(supprimer(l, r) , i) = ième(l,i)
repérés selon leur rang. S'il n'y a pas de relation d'ordre sur l'ensemble des éléments de la (6) Vr, 1 ( r ( longueur(l) et r ( i ( longueur(l) - 1,
séquence, il en existe une sur le rang. Le rang du premier élément est 1 , le rang du second ième(supprimer(l, r), i) = ième(l, i + 1)
18. 1 Les listes 205 206 Chapitre 1 8 • Structures linéaires
(7) Vr, r < 1 et r > longueur ( l), � l', l' = supprimer( l, r) Utilisation d'un tableau
La méthode qui vient en premier à l'esprit, lorsqu'on mémorise les éléments d'une liste
L'opération ajouter insère un élément à un rang compris entre un et la longueur de la liste dans un tableau, est de conserver systématiquement le premier élément à la première place du
plus un. Le rang des éléments à la droite du rang d'insertion est incrémenté de un. tableau, et de ne faire varier qu'un indice de fin de liste. La figure 1 8 . 1 montre la séquence de
cinq entiers < 5, -13, 23, 182, 100 > placée dans un tableau nommé élément s . L'attribut
(8) Vr, 1 :::;;r :::;;long ueur (l) + 1 et 1 :::;;i < r, lg, qui indique la longueur de la liste, donne également l'indice de fin de liste.
ième(ajouter(l, r, e ) , i) = ième(l, i)
2
81 32 1 1 00
(9) Vr, 1 :::;;r :::;;longu eur (l) + l et r = i, ième(ajouter(l, r, e), i ) = e élément s . length-1
1-131 23
0 4
(10) Vr, 1 :::;;r :::;;longu eur (l) + 1 et r < i :::;;lon gueur ( l ) + 1,
ième(ajouter(l, r, e ) , i) = ième(l, i 1) -
lg=S
5
1 1
FIGURE 18.1 Une liste dans un tableau.
18.1.2 L'implémentation en Java
L'algorithme de l'opération ième est très simple, puisque le tableau permet un accès direct
La description fonctionnelle du type abstrait .liste est traduite en JAVA par l'inte1face à l'élément de rang r. La complexité de cet algmithme est donc 0(1). Notez que pour accéder
générique suivante : à un élément de la liste l ' antécédent de l'opération doit être vérifié.
Algorithme ième ( r )
public interface Liste<E> {
{Antécédent : 1 � r � longueur (l ) J
public int l on gu eu r ( ) ;
rendre élément s [ r - 1 ] {les valeurs débutent à l ' indice 0)
public E ième ( int r ) throws RanginvalideException;
public void suppr ime r ( int r ) throws RanginvalideException;
public void a jou ter ( int r, E e) throws RanginvalideExcept i o n ; L'opération de suppression d'un élément de la liste provoque un décalage des éléments
qui se situent à droite du rang de suppression. Pour une liste de n éléments, la complexité de
cette opération est O(n), et l'algorithme qui la décrit est le suivant :
Les méthodes d'accès aux éléments de la liste peuvent lever une exception si elles tentent Algorithme supprimer ( r )
d'accéder à un rang invalide. Cette exception, RanginvalideException, est simplement {Antécédent : 1 � r � longueur (l) }
définie par la déclaration : pourtout i de r à lg faire
élément s [ i - 1 ] f-- élément s [ i ]
public class RanginvalideException extends RuntimeException {
-0 -0 finpour
0 public RanginvalideException ( ) 0
c c lg f-- lg-1
::J super ( ) ; ::J
0 0
V
T""l V
T""l
0 0 L'opération d'ajout d'un élément e au rang r consiste à décaler d'une position vers la
N N
@ @ droite tous les éléments à partir du rang r. Le nouvel élément est inséré au rang r. Dans la
.....
..c .....
..c
Ol Afin d'insister sur l'indépendance du type abstrait vis-à-vis de son implémentation, nous Ol plupart des langages de programmation, une déclaration de tableau fixe sa taille à la compi
ï::: présenterons successivement deux sortes d'implémentation. La première utilise des tableaux ï:::
>
a. >
a. lation. Quel que soit le constructeur choisi, un objet, instance de la classe L i steTableau,
0 et la seconde des structures chaînées. Les tableaux offrent une représentation contiguë des 0 possédera un nombre d'éléments fixe. Ceci contraint la méthode a j outer à vérifier si le ta
u u
éléments, et permettent un accès direct aux éléments qui les composent, mais ont comme bleau éléments dispose d'une place libre avant d'ajouter un nouvel élément. Comme pour
principal inconvénient de fixer la taille de Ja structure de données. Par exemple, si pour repré ] 'opération de suppression, la complexité de cet algorithme est O(n). L'algorithme est le
senter une liste, on déclare un tableau de cent composants alors quelle que soit la longueur ef suivant :
fective de la liste, l'encombrement mémoire utilisé sera celui des cent éléments. Au contraire, Algorithmea j outer ( r , e )
les structures chaînées sont des structures dynamiques qui permettent d'adapter la taille de la {Antécédent : 1 � r � longueur (l) +l
structure de données au nombre effectif d'éléments. L'espace mémoire nécessaire pour mé et longueur (l ) +l <élémen t s . length}
moriser un élément est plus important, et le temps d'accès aux éléments est en général plus pourtout i de lg-1 à r-1 faire
coûteux parce qu'il a lieu de façon indirecte. élément s [ i + l ) f-- é l ément s [ i )
finpour
18. 1 Les listes 207 208 Chapitre 1 8 • Structures linéaires
{r-1 est l ' indice d' insertion} Il r-1 est l ' indice de l ' élément à ajouter
élément s [ r - 1 ] +--- e élément s [ r-l ] =e ;
lg +--- lg+l lg++;
�
0 0 < 5 20 4 9 45 > dans le tableau éléments.
V
T""l public void supprime r (int r) throws RanginvalideExcept ion V
T""l
0 0
N if ( r< l 1 1 r>lg) N
�
@ throw new RanginvalideException ( ) @ t êt e queue
.....
..c .....
..c
Ol Il décaler les éléments vers l a gauche; Ol éléments
ï::: ï::: 5 20 4 9 45
> for ( int i=r; i<lg; i++) >
a. a.
0 élément s [ i - l ] =élément s [ i ] ; 0
u u
lg- -; queue tête
la méthode de gestion du tableau précédente. Ce décalage est lui-même circulaire et se fait é lément s [ tête+r- l ] =e ;
modulo la taille du tableau. L'indice d'un élément de rang r est égal à tête+r-1. queue = queue==élément s . length-1 ? 0 ++queue ;
99 9
for ( int i=tête+r; i<lg+tête; i++)
Il i > O
����.
----tl__:ïjm
élément s [ ( i - 1 ) % élément s . length]
élément s [ i % é l ément s . lengt h ] ;
/77/77 9
queue queue==O ? élément s . length-1 : --queue;
lg--;
Les nœuds sont représentés par la classe générique Noeud qui hérite de l a classe Lien L'ajout d'un élément e au rang r consiste à créer un nouveau nœud n initialisé à la valeur
et l'étend par l'ajout de l'attribut valeur qui désigne la valeur du nœud, i.e. la valeur d'un e, puis à relier le nœud de rang r-1 à n, et enfin à relier le nœud n au nœud de rang r. Si
élément de la séquence. l ' élément est ajouté en queue de liste, son suivant est la valeur null. Comme précédemment,
Cette classe possède deux constructeurs qui initialisent un nœud avec la valeur d'un élé si r = l , il faut modifier la tête de liste.
ment particulier, et avec celJe d'un lien sur un autre nœud. La méthode valeur renvoie
la valeur du nœud, alors que la méthode changerValeur change sa valeur. La méthode noeud de rang r
�
noeudSui vant renvoie ou modifie le nœud suivant.
=4---�..._____.__
public class Noeud<E> extends Lien<E>
protected E valeur;
public Noeud(E e) { valeur=e; } suivant
public Noeud ( E e , Noeud<E> s ) { valeur=e; suivant ( s ) ; n
Algorithme a j outer ( r , e)
{Antécédent : 1 � r � longueur ( l ) + l }
Avec cette structure chaînée, les opérations ième, supprimer, et a j outer nécessitent
n +-- créer Noeud ( e )
toutes un parcours séquentiel de la liste et possèdent donc une complexité égale à O(n). 2
noeudSuivant r - ( t ê t e ) . s uivant +--n
On atteint le nœud de rang r en appliquant r-1 fois l'opération noeudSuivant à partir n . suivant
l
+-- noeudSuivant r- ( t ê t e )
de la tête de liste. Nous noterons ce nœud noeudSui vantr - l (tête ) . L'algotithme de l g +-- lg+ 1
l'opération ième s'écrit :
Algorithme ième ( r )
Nous pouvons donner l'écriture complète de la classe ListeChaînée.
{Antécédent : 1 � r � longueur ( l ) }
l
rendre noeudSuivant r - ( t ê t e ) . valeur ( ) public class List eChaînée<E> implements List e <E > {
protected int lg;
protected Noeud<E> t ê t e ;
Comme le montre la figure 18.5, la suppression d'un élément de rang r consiste à affecter
public int l ongueur ( ) { return lg ; }
au lien qui le désigne la valeur de son lien suivant. La flèche en pointillé représente le lien public E ième ( int r ) throws RanginvalideException
�l�ivant
avant la suppression. Notez que si r est égal à un, il faut modifier la tête de liste. if ( r< l 1 1 r>lg)
noeud de rang r throw new RanginvalideException ( ) ;
-0 'O
0 0
c c Noeud<E> n=tê t e ;
::J ::J
0 0 for ( int i = l ; i < r ; i + + ) n=n . noeudSuivant ( ) ;
V
T""l V
T""l Il n désigne l e nœud de rang r
suivant
0 0 return n . valeur ( ) ;
N N
@ @
.....
..c .....
.c public void supprimer ( int r ) throws Ranginva l i deExcept ion
Ol Ol
ï::: ï::: if ( r< l 1 1 r>lg)
>
a. >
a.
0 0 throw new RanginvalideException ( ) ;
u FIGURE 18.S Suppression du nœud de rang r. u if ( r==l) Il s uppression en t ê t e de l i s t e
tête=tête . noeudSuivant ( ) ;
Algorithme supprimer ( r )
else { Il cas général , r>l
{Antécédent : 1 � r � longueur ( l ) } Noeud<E> p=null, q=t ête;
si r=l alors for ( int i = l ; i<r; i++) {
tête +-- noeudSuivant (têt e ) p=q ;
sinon q=q . noeudSuivant ( ) ;
2
noeudSuivant r - (têt e ) . suivant +--noeudSuivant r ( t êt e ) }
finsi Il q désigne l ' élément de rang r et p son prédécesseur
lg +-- lg-1
18. 1 Les listes 213 214 Chapitre 18 • Structures linéaires
pr é c é de n t
lg++;
Of\ �-
Lien en lui ajoutant un second lien, l'attribut précédent, dont la définition est semblable
-0 à l'attribut suivant. La déclaration de cette classe est la suivante : -0
0 0
c c
::J public class LienDouble<T> extends Lien<T> ::J '-+---'-
----+
suivant
0 0 précédent
V protected Lien<T> précé de nt ; V
T""l T""l
0 protected Lien<T> précédent ( ) { return précédent ; 0
N N
protected void précédent (Lien<T> s ) { précédent=s; }
@ @ n
.....
..c Il fin classe LienDouble .....
..c
Ol Ol
ï::: ï::: FIGURE 18.8 Ajout du nœud de rang r.
>
a. Chaque nœud d'une structure doublement chaînée est représenté par la classe générique >
a.
0 Noeud2 suivante qui étend la classe LienDouble. 0
u u
La classe suivante donne l'écriture complète de l'implémentation d'une liste avec une
public class Noeud2<E> extends LienDouble<E>
structure doublement chaînée.
protected E valeur;
public Noeud2 ( E e) { valeur=e; } public class List eChaînéeDouble<E> implements Liste<E> {
public Noeud2 (E e, Noeud2<E> p , Noeud2<E> s ) { protected int l g ;
valeur=e; précédent ( p ) ; suivant ( s ) ; protected Noeud2 < E > tête, queue;
public int longueu r ( ) { return l g ;
public E valeur ( ) { return valeur; } public E ième (int r ) throws Rang i nva lideExcept i on
public void changerValeur ( E e ) { valeur=e; if ( r<l 1 1 r> l g ) throw new Ranginval i deExcept i on ( ) ;
public Noeud2<E> noeudSuivant ( ) { return (Noeud2 <E> ) suivant ( ) ; }
18. 1 Les listes 215 216 Chapitre 1 8 • Structures linéaires
1 8. 1 .3 Énumération
else { Il cas général, r > 1 et r < l g
Noeud2 < E > q=tête, p=null ; Il anive fréquemment que l'on ait à parcourir une liste pour appliquer un traitement spé
for ( int i=l ; i<r; i++) {
cifique à chacun de ses éléments ; par exemple, pour afficher tous les éléments de la liste,
p=q;
ou encore élever au carré chaque entier d'une liste. L'algorithme de parcours de la liste sur
q=q. noeudSuivant ( ) ;
laquelle on [Link] une action représentée par la routine traiter s'écrit de la façon suivante :
}
Il q désigne l ' élément de rang r Algorithme parcours ( 1 , traiter)
q . noeudSuivant ( ) . noeudPrécédent ( p ) ; pourtout i de 1 à longueur ( l ) faire
p . noeudSuivant ( q . noeudSuivant ( ) ) ; trait e r ( i ème ( l , i ) )
finpour
lg--;
public void ajouter ( int r , E e) throws RanginvalideException Il est évident que la complexité de cet algorithme doit être 0 (n). C'est le cas, si la liste est
if (r<l 1 1 r>lg + l ) implémentée avec un tableau. Mais, elle est malheureusement égale à O(n2) si la liste utilise
throw new RanginvalideExcept i on ( ) ; une structure chaînée, puisque l'opération ième repai1 chaque fois du début de la liste.
-0 Noeud2<E> n=new Noeud2<E > ( e ) ; -0
0 0 Une des propriétés fondamentales des structures linéaires est que chaque élément, hom1is
c Il l i s t e vide =? r==l c
::J if (lg==O) ::J le dernier, possède un successeur. Il est alors possible d'énumérer tous les éléments d'une
0 t ê t e=queue=n;
0
V
T""l V
.-l liste grâce à une fonction suce dont la signature est définie comme suit :
0 el se 0
N if {r==l) { Il insertion en tête de l i s t e N
@ @ suce E -t E
.....
..c
têt e . noeudPrécédent ( n ) ; .....
..c
Ol n . noeudSuivant (tête ) ; Ol
ï::: ï::: Pour une liste l, cette fonction est définie par l'axiome suivant :
>
a. tête=n; >
a.
0 0
u u Vr E [1, longu eur ( l ) [, succ(ième(l, r)) = ième ( l , r + 1)
el se
if (r==lg+l )
Notez que la liste n'est pas le seul type abstrait dont on peut énumérer les composants.
Il ajout du dernier élément de l a l i s t e
Nous veITons que nous pomTons énumérer les éléments des types abstraits que nous étudie
queue . n oeudSuivant ( n ) ;
rons par la suite en fonction de Jeurs particularités.
n . noeudPrécédent (queue ) ;
queue=n;
L'implémentation en JAVA
else { Il cas général , r>l e t r � l g
Nous représenterons une énumération par l'interface JAVA Iterator du paquetage
java . ut i l . Cette interface possède des méthodes de manipulation de l 'énumération.
18. 1 Les listes 217 218 Chapitre 1 8 • Structures linéaires
E n particulier, l a méthode next qui renvoie l'élément suivant de l'énumération (ou l'ex nbÉ num++;
ception NoSuchElementException si cet élément n'existe pas) et la méthode hasNext return e ;
indique si la fin de l'énumération a été atteinte ou pas. L'inte1face définit aussi la méthode }
remove, mais nous ne la traiterons pas. Il pas de suivant
throw new NoSuchElementExcept i on ( ) ;
Une liste définira la méthode i terator qui renvoie l'énumération de ses éléments. Cette }
méthode s'écrit de la façon suivante : Il ListeÉnumération
public Iterator<E> iterator ( )
L'attribut nbÉnum est nécessaire pour identifier la fin de la liste, car si les indices de tête
return new Liste É numé ration ( } ;
et de queue sont égaux, la liste peut être aussi bien vide que pleine.
Pour les classes qui implémentent les listes à l'aide de structures chaînées, il suffit de
La programmation de la classe Li steÉnumération dépend de l'implémentation de la conserver une référence sur l'élément courant. L'accès au successeur se fait à l'aide de la
liste. Chaque classe qui implémente le type abstrait Liste définira une classe privée lo méthode noeudSui vant. La première fois, sa valeur est égale à la tête de liste.
cale ListeÉnumération qui donne une implantation particulière de l'énumération. Par private class ListeÉ numération implements Iterator<E> {
exemple dans la classe ListeTableau, on définira : private Noeud<E> courant ;
private class ListeÉ numération implements Iterator<E> private Liste É numérat ion ( )
courant = tête;
private int courant;
private Liste É numérat ion ( )
courant=O ; public boolean hasNext ( )
return courant ! =null;
public boolean hasNext ( )
return courant ! =l g ; public E next ( ) throws NoSuchEl ementExcept ion
if ( hasNext ( ) ) {
public E next ( ) throws NoSuchElementException E e = courant . valeur ( ) ;
if ( hasNext ( ) ) courant = courant . noeudSuivant ( ) ;
return élément s [ courant ++ ] ; return e ;
Il pas de suivant }
throw new NoSuchElementExcept i on ( ) ; Il pas de s u i vant
throw new NoSuchElementExcept i on ( ) ;
}
Il List eÉnuméra t i on
Il ListeÉnumération
L'attribut courant désigne l'indice du prochain élément de l'énumération dans le tableau.
-0 -0 L'algorithme de parcours donné plus haut s'écrit en JAVA de la façon suivante. On crée
0 Sa valeur initiale est égale à zéro. 0
c c d'abord l'énumération, puis on traite les éléments un à un grâce à la fonction next.
::J ::J
0 Pour la classe Li steTableauCirculaire, le calcul de l'élément suivant se fait modulo 0
V
T""l V
T""l public void <E> parcours (Liste<E> 1 )
0 la longueur du tableau. 0
N N Iterator<E> énum=l . iterator ( ) ;
@ private class ListeÉ numération implements Iterator<E> { @ while (énum. hasNext ( ) )
.....
..c private int courant, nbÉ nurn;
.....
..c t r a i ter ( énum . next ( ) ) ;
Ol Ol
ï::: private Liste É numération ( ) ï:::
>
a. >
a.
0 courant = tête; 0
u nbÉ num = O ;
u La manipulation d'une énumération s'applique en général à tous ses éléments. Il peut être
en particulier très risqué de modifier la structure de données (e.g. la liste) au cours d'un
public boolean hasNext ( ) parcours dans la mesure où cela peut corrompre l 'énumération.
return nbÉ num ! = l g ;
Une seconde façon de traiter tous les éléments d'une liste est d'utiliser un énoncé adapté,
s'il existe dans le langage de programmation. JAVA propose l'énoncéjoreach avec lequel la
public E next ( ) throws NoSuchElementException
méthode de parcours précédent se récrit simplement :
if ( hasNext ( ) ) {
E e = élérnent s [ courant ] ; public void <E> parcours (Liste<E> 1 ) {
courant = courant == élément s . length - 1 ? 0 ++courant ; for (E x : 1) traiter ( x ) ;
18.2 Les piles 219 220 Chapitre 1 8 • Structures linéaires
Le rôle de l'opération empiler est d'ajouter un élément en sommet de pile, celui de dépiler
-0
0 1 8.2 LES PILES -0
0 de supprimer le sommet de pile et celui de sommet de renvoyer l'élément en sommet de pile.
c c
::J ::J Enfin, 1 ' opération est-vide ? indique si une pile est vide ou pas.
0 0
V Une pile est une séquence d'éléments accessibles par une seule extrémité appelée V
T""l T""l
0
N
sommet. Toutes les opérations définies sur les piles s'appliquent à cette extrémité. L'élé 0
N Description axiomatique
@ ment situé au sommet s'appelle le sommet de pile. La séquence formée de quatre entiers @
.....
..c < 5, -13, 23, 100 > est représentée sous forme de pile par la figure 18.9 . .....
..c
Ol Ol
La sémantique des fonctions précédentes est définie formellement par les axiomes sui
ï::: ï::: vants :
>
a. >
a.
0 L'ajout et la suppression d'éléments en sommet de pile suivent le modèle dernier entré - 0
u u
premier sorti (LIFO 2). Les piles sont des structures fondamentales, et leur emploi dans les Vp E Pile, Ve E E
programmes informatiques est très fréquent. Nous avons déjà vu que le mécanisme d'appel (1) est-vide?(pilevide) = vrai
de routines suit ce modèle de pile. Les logiciels qui proposent une fonction « undo » servent (2) est-vide?(ernpiler(p, e)) = faux
également d'une pile pour défaire, en ordre inverse, les dernières actions effectuées par l'uti (3) d épiler( empiler(p, e )) = p
lisateur. Les piles sont également nécessaires pour évaluer des expressions postfixées.
(4) sornrnet(empiler(p, e)) = e
1 . cf le chapitre 13 page 143. (5) � p, p = dépiler ( pilevide)
2. Last-ln First-Out. (6) � e, e = sommet(pilevide)
18.2 Les piles 221 222 Chapitre 18 • Structures linéaires
1-1 3 1 23 1 00t 1 1 1 1
tête queue éléments . l ength-1
Notez que ce sont les axiomes (3) et (4) qui définissent le comportement LIFO de la pile.
Les opérations dépiler et sommet sont des fonctions partielles, et les axiomes (5) et (6) pré é l éments
cisent leur domaine de définition ; ces deux opérations ne sont pas définies sur une pile vide. 5
sommet de pile
La figure 18.10 montre la séquence < 5, -13, 23, 100 > mémorisée dans un tableau. Pour protected boolean estPleine ( ) {
repérer à tout moment le sommet de pile, il suffit d'un seul indice de queue. return lg==élément s . length;
18.3 Les files 223 224 Chapitre 1 8 • Structures linéaires
Utilisation d'une structure chaînée bureaux de poste, ou à une caisse de supermarché la veille d'un week-end. À tout moment,
seul le premier client de la file accède au guichet ou à la caisse.
L'implémentation d'une pile à l'aide d'une structure chaînée utilise la classe L i steChaînée
qui ne nécessite qu'une référence sur la tête de liste. La figure 1 8 . 1 1 montre la séquence
< 5, -13, 23, 100 >. entrée file >---�:.. sortie
tête --1 1 1 ·l 23 1 1 ·l -1 3 I �
rno
1 ·1� null
FIGURE 18.12 Une file.
Les files définissent le modèle premier entré - premier sorti (FIFO 3). Les éléments sont
Vf E File, Ve E E
insérés dans la séquence par une des extrémités et en sont extraits par l'autre. Ce modèle ( 1) est-vide? ( filevide) = vrai
con-espond à la file d'attente que 1 ' on rencontre bien souvent face à un guichet dans les (2) est-vide?(enfiler(f, e)) = faux
(3) est-vide?(f) � premier (en filer(f, e)) = e
3. First-ln First-Out. (4) non est-vide?(f) � premier(enfiler(!, e)) = premier(!)
(5) est-vide?(!) � défiler(enfiler(f, e)) = filevide
18.3 Les files 225 226 Chapitre 1 8 • Structures linéaires
Comme pour celle de P ile, l'implémentation de l'interface File s'appuie sur les opé Dè que est l'ensemble des dèques dont les éléments appartiennent à un ensemble E quel
rations proposées par le type Liste. Quelle que soit l'organisation des données choisie, conque. La constante dèquevide représente une dèque vide. L'ensemble Sens {gauche, =
tableau ou structure chaînée, les algorithmes des opérations de Fi le sont les mêmes. L' opé droite} est défini pour désigner l'extrémité utilisée par les différentes opérations de manipu
ration premier retourne l'élément de tête, défiler le supprime, alors que l'opération enfiler lation de dèque.
ajoute un élément en queue. Pour que ces opérations gardent une complexité égale à 0(1),
les classes FileTableau et F i l eChaînée devront utiliser, respectivement, les classes Vè que utilise E, Sens et booléen
L i steTableauCircul a i re et Li steChaînéeDouble. dèquevide E Dè que
public class Fileîableau<E> extends L i s t eTableauCirculaire<E>
implements File<E>
Description fonctionnelle
{ Quatre opérations sont définies sur le type Vè que :
public FileTableau ( ) super ( ) ; }
public File îableau ( int n ) { super ( n ) ; endéquer Vèque x E x Sens ---+ Dèque
dédéquer Dèque x Sens ---+ Dèque
public boolean estVide ( ) { extrémité Dèque x Sens ---+ [,
-0 return longueur ( ) == O ; -0
0
c
0
c est-vide? Dèque ---+ booléen
::J ::J
0 public E premier ( } throws F i leVideException 0
V
T"l V
T""l Description axiomatique
0 if (estVide ( ) ) 0
N throw new FileVideException ( ) ; N
@ @ Les axiomes qui décrivent le type Vèque sont l'union des axiomes des types Pile et File :
..... return élémentDeTê te ( ) ; .....
..c ..c
O'I
ï:::
Ol
ï::: Vd E Vèqu,e, Vs, sl, s2 E Sens et Ve E E
> public void enfile r ( E e ) { >
a.
0
a.
0
(1) est-vide?(dèquevide) = vrai
u if (estPleine ( ) ) u
throw new FileP leineException ( } ;
(2) est-vide?(endéquer(d, e, s)) = faux
a j out erEnQueue ( e ) ; (3) extrémité( endéquer( d, e, s), s) = e
(4) est-vide?(d) � extrémité(endéquer(d, e, sl), s2) = e
public void défiler ( ) throws Fi leVideExcept ion (5) non est-vide?(d) � extrémité( endéquer(d, e, sl), s2) = extrémité(d, s2)
if (estVide ( ) )
throw new Fi leVideException ( ) ;
(6) dédéquer(endéquer(d, e, s), s) = e
supprimerEnTê te ( ) ; (7) est-vide?(d) � dédéquer(endéquer(d, e, s), s) = d
if ( e stVide ( ) ) throw new DèqueVideException ( ) ; inversée l'opérateur suit ses opérandes. Par exemple, l'expression infixe suivante :
-0 if ( s == Sens . gauche} supprimerEnQueue ( } ; -0 (7 + 2 ) * (5 3)
0 0 -
@ { @
.....
..c .....
..c L'évaluation d'une expression postfixée se fait très simplement à l'aide d'une pile. L'ex
Ol if ( s == Sens . gauche) ajouterEnQueue ( e ) ; Ol
ï::: else Il s=Sens . droi t e ï::: pression est lue de gauche à droite. Chaque opérande lu est empilé et chaque opérateur trouve
>
a. >
a.
0 a j outerEnTêt e ( e } ; 0 ses deux opérandes en sommet de pile qu'il remplace par le résultat de son opération. Lorsque
u u
l'expression est entièrement lue, sans erreur, la pile ne contient qu'une seule valeur, le résultat
public E extrémi t é (Sens s ) throws DèqueVideExcept ion de l'évaluation. Éc1ivez l'algorithme d'évaluation d'une expression postfixée.
{ Programmez en JAVA cet algorithme, en utilisant une classe d'implémentation du type
if ( e stVide ( ) ) throw new DèqueVideException ( } ;
Pile. Les expressions sont lues sur l'entrée standard System . in et les résultats sont écrits
return s == Sens . gauche ? élémentDeQueue ( }
sur la sortie standard System . out. Vous traiterez un opérateur supplémentaire, dénoté par le
: élémentDeTête ( ) ;
symbole =, qui affiche le résultat. Les opérateurs et les opérandes sont séparés par des blancs,
Il fin classe DèqueChaînée<E> des tabulations ou des passages à la ligne. Vous pouITez utiliser la classe St rearnTokeni zer
définie dans le paquetage java . io. Un objet de cette classe prendra en entrée un flot de
18.5 Exercices 229 230 Chapitre 1 8 • Structures linéaires
caractères et rendra en sortie un flot d'unités syntaxiques qui représente les différents compo dans toutes les classes d'implémentation de l'interface Liste<E>. L'en-tête de la cette mé
sants (opérandes, opérateurs, séparateurs) d'une expression. thode est le suivant :
Exercice 18.6. En utilisant une pile, éc1ivez un programme qui vérifie si un texte lu sur public L i s t e <E> filtrer ( P rédicat<E> f ) ;
Elle énonce qu'une liste est soit vide, soit formée d'un élément suivi d'une liste. On définit
les opérations suivantes :
L'opération tête retourne le premier élément de la liste, et fin la liste amputée du premier
élément. L'opération cons construit une liste à partir d'un élément (à placer en tête) et d'une
liste.
Les algorithmes de manipulation de cette forme de liste sont naturellement récursifs. Par
exemple, parcourir une liste pour appliquer un traitement sur chacun des éléments s'écrit :
Algorithme appliquer ( 1 , traiter)
si non est-vide ? ( l ) alors
traite r (tête ( l ) )
'O appliquer ( f in ( l ) ) 'O
0 finsi 0
c c
::J ::J
0 0
V
T""l V
T""l
0 Écrivez les axiomes qui définissent la sémantique des opérations précédentes. Proposez 0
N N
@ une implémentation en JAVA de ce modèle de .Ciste à l'aide d'une structure chaînée. @
.....
..c .....
..c
Ol Exercice 18.8. Une matrice creuse est une matrice dont la majorité des éléments sont égaux Ol
ï::: ï:::
>
a. >
a.
0 à zéro. Proposez une représentation d'une mat1ice creuse à l'aide d'une structure chaînée qui 0
u u
ne mémorise que les valeurs différentes de zéro.
Exercice 18.9. Ajoutez à l'interface générique L i ste<E> les méthodes map et apply
telles que nous les avons définies au chapitre 13 page 143. Programmez ces méthodes dans
toutes les classes d'implémentation de l'interface Liste<E>.
Exercice 18.10. Ajoutez à l'interface générique Liste<E> la méthode filtrer qui ren
voie une liste formée des éléments qui sastisfont à un prédicat booléen. Le prédicat booléen
est une lambda transmise en paramètre à la méthode filtrer. Programmez cette méthode
232 Chapitre 1 9 • Graphes
Les graphes servent par exemple en sociologie à modéliser les relations entre des indi
vidus ou en sciences économiques. Naturellement, ce sont les outils de prédilection pour la
représentation des réseaux (routiers, de télécommunication, d'interconnexion de réseaux, de
processeurs, etc.). Par exemple, le réseau routier entre les grandes villes d'un pays peut être
assimilé à un graphe non orienté. Un sommet représentera une ville, et une arête une route
entre deux vi11es.
En informatique, les objets alloués dynamiquement dans la zone de tas de la mémoire
d'un ordinateur s'organisent en graphe, et sont gérés automatiquement par les récupérateurs
de mémoire 1 de certains langages de programmation, comme c'est le cas en JAVA. La phase
Cha pitre 1 9 d'optimisation globale des compilateurs construit un graphe deflot de contrôle du programme
à partir duquel elle améliorera le code cible à produire selon des techniques classiques de
substitution d'appels de procédures, de réduction de puissance, etc. [ASU89].
Graphes Il est possible d'ajouter des informations sur les sommets pour les identifier, ou sur les arcs
pour les pondérer. Les noms des villes seront associés aux sommets du graphe qui modélise
le réseau routier, et on placera sur chaque arête la distance qui sépare deux villes. Avec ces
informations, il sera possible, par exemple, de calculer le trajet le plus court pour se rendre
d'une ville à une autre. Un graphe dont les arcs (ou les arêtes) po11ent des valuations est
appelé graphe valué.
Il est impossible en quelques lignes introductives de présenter tous les domaines d'appli
cations des graphes. Nous convions le lecteur intéressé à se reporter aux ouvrages suivants
Un graphe est un ensemble de sommets reliés par des arcs. La figure 1 9. 1 montre un
[Ber70, MB86, Gon95].
graphe particulier à neuf sommets et dix arcs. Par définition, un graphe est orienté, c'est-à
dire que les relations établies entre les sommets ne sont pas symétriques. Toutefois, certains Dans ce chapitre, après avoir introduit quelques termes spécifiques aux graphes, nous pré
problèmes, qui ne tiennent pas compte de l'orientation, pourront les considérer comme symé senterons un type abstrait Çraphe et ses mises en œuvre possibles. Quelques algorithmes
triques. On parle alors de graphe non orienté et les arcs qui relient les sommets sont nommés classiques sur les graphes seront donnés au chapitre 24.
arêtes.
1 9. 1 TERMI NOLOGI E
Un graphe G = (X, U) est formé d'un ensemble de sommets X et d'un ensemble
'O -0
0
c
0
c d' arcs U. L'ordre d'un graphe est son nombre de sommets. Les graphes creux ont peu d'arcs
::J ::J et ceux qui en possèdent beaucoup sont dits denses.
0 0
V V
T"l
0
T""l
0
Un arc u = ( x, y) E U possède une extrémité initiale x et une extrémité finale y. x est
N N le prédécesseur de y et y est le successeur de x. Si x = y, l'arc est appelé une boucle. Une
@ @
..... ..... arête entre deux sommets est notée e = [x, y]. L'ensemble des voisins d'un arc est l'union de
..c ..c
Ol Ol l'ensemble de ses prédécesseurs et de ses successeurs.
ï::: ï:::
>
a. >
a.
0 0 Un multigraphe est un graphe qui possède des boucles ou des arcs multiples (plusieurs
u u
arcs qui possèdent la même extrémité initiale et la même extrémité finale). Un graphe simple
est un graphe sans boucle, ni arc multiple. Par la suite, nous ne considérerons que les graphes
simples. Le nombre d'arcs d'un graphe simple à n sommets est compris entre 0 et n (n 1), -
FIGURE 19.1 U n graphe à neuf sommets et deux composantes connexes. et � n (n 1 ) si on ne considère pas lorientation des arcs.
-
Deux arcs sont dits adjacents s'ils ont au moins une extrémité commune. Un arc u est
Les graphes interviennent dans des domaines variés tant théoriques (e.g. mathématiques incident à un sommet x vers l 'extérieur si x est l'extrémité initiale de u. Le demi-degré
discrètes, combinatoire) que pratiques (e.g. applications informatiques), et sont très utilisés
dès qu'il s'agit de simuler des relations complexes entre des éléments d'un ensemble. 1 . En anglais garbage-coLlector.
19.2 Définition abstraite d'un graphe 233 234 Chapitre 1 9 • Graphes
extérieur, noté d+ (x), est le nombre d'arcs incidents vers l'extérieur à un sommet x. De Lorsqu'on considère un graphe non orienté, on ajoute au type abstrait les opérations sui
même, un arc u est incident à un sommet y vers l'intérieur si y est l'extrémité finale de 11,. Le vantes :
demi-degré intérieur, noté d-(x), est le nombre d'arcs incidents vers l'inté1ieur à un sommet
x. Le degré d'un sommet x est égal à d-(x) + d+(x). arête Sommet x Sommet ---+ booléen
ajouterArête Ç}raphe x Sommet x Sommet --7 Qraphe
Un graphe est complet s'il existe un arc (x, y) pour tout x et y de X. Un sous-graphe supprimerArête Qraphe x Sommet x Sommet ---+ Qraphe
G' = (A, U) d'un graphe G = (X, U) est un graphe dont les arcs de U ont leurs extrémités
dans A. Un graphe partiel G' = ( X, V) d'un graphe G = ( X, U) est un graphe dont les Enfin, pour les graphes valués, les opérations ajouterArc et ajouterArête possèdent les
sommets de X sont les extrémités des arcs de V. signatures suivantes :
Un chemin est une liste de sommets dans lequel deux sommets successifs quelconques
ajouterArc Qraphe x Sommet x Sommet x E --7 Qraphe
sont reliés par un arc. La longueur d'un chemin est égale au nombre d'arcs. Un chemin qui
ajouterArête Qraphe x Sommet x Sommet x E ---+ Ç}raphe
ne rencontre pas deux fois le même sommet est dit élémentaire. Un cycle est un chemin dont
le premier et le dernier sommet sont identiques. avec E désignant un ensemble de valeurs quelconques. La valeur d'un arc ou d'une arête est
Un graphe est dit connexe s'il existe un chemin reliant toute paire de sommets. Il est obtenue grâce aux opérations suivantes :
fortement connexe si un tel chemin existe de x vers y et de y vers x. Un graphe non connexe
peut être formé de composantes (fortement) connexes. valeurArc Ç}raphe x Sommet x Sommet ---+ E
valeurArête Qraphe x Sommet x Sommet ---+ E
La racine d'un graphe est un sommet r tel que pour tout sommet y, il existe un chemin
entre r et y.
Description axiomatique
§j
s7 faux faux faux faux faux faux faux faux
public void a j outerSommet ( S s ) throws SommetException;
public void enleverSommet ( S s ) throws SommetExcept i o n ; s8 faux faux faux faux faux faux faux 1 faux
public void a j outerArc ( S s l , S s2) throws ArcExcep t i o n ; s9 faux faux faux faux faux faux faux faux faux
public void supprimerArc ( S s l , S s 2 ) throws ArcExcept ion; Une classe GrapheMatrice qui implémente l'interface Graphe peut utiliser les décla-
public void a j outerArêt e ( S s l , S s2) throws ArêteExcep t i o n ; rations suivantes :
public void supprimerArêt e ( S s l , S s2) throws ArêteException;
public Iterable<S> sommet s Adjacent s ( S s ) ; protected int nbSornme t s ; Il ordre du graphe
protected boolean [ ] [ l mat I ;
2. Pour répérer facilement les arcs, les valeurs vrai sont encadrées.
19.3 L'implémentation en Java 237 238 Chapitre 1 9 • Graphes
public S ièmeSucc ( S s, int i ) { Le parcours peut passer plusieurs fois par un même sommet (à cause d'un cycle par
return listeI [ numéro ( s ) ] . i ème ( i ) ; exemple), et le traitement du sommet ne devra pas être renouvelé. Pour vérifier si un som
met a déjà été traité ou pas, on le marque après chaque traitement. L'algorithme s'exprime
récursivement en deux parties comme suit :
Le calcul du demi-degré intérieur d'un sommet nécessite quant à lui le parcours de l'en
Algorithme Parcours-en-Profondeur ( G )
semble des listes d'adjacence, c'est-à-dire le nombre total d'arcs du graphe. La complexité
{Parcours en profondeur du graphe G}
est O(max(n,p)). pourtout s de G faire
Pour représenter un graphe valué, il suffit d'ajouter les valuations des arcs (x, y) à l'élé si non marqué ( s ) alors
ment y dans la liste d'adjacence du sommet x. La figure 19.3 montre cette forme de repré Pprofondeu r ( G , s)
sentation. finsi
finpour
s1 s2 s3
Algorithme Pprofondeur ( G , s)
� Si le traitement du sommet a lieu avant le parcours des successeurs, le parcours est dit pré
fixe. Cela correspond à l'exécution d'une action sur le sommet courant juste avant la pose de la
marque sur le sommet. En revanche, si le traitement est fait après le parcours des successeurs,
FIGURE 19.3 Représentation d'un graphe valué par des listes d'adjacence.
le parcours est postfixe. L'action est faite sur le sommet courant après l'énoncé itératif. Notez
que deux traitements, préfixe et postfixe, peuvent être appliqués lors d'un même parcours.
L'énumération des successeurs d'un sommet s consiste simplement à énumérer les som
mets de sa liste d'adjacence. En partant du sommet s 1, les parcours en profondeur préfixe et postfixe du graphe de la
figure 19.1 traitent les sommets selon les ordres suivants :
V V
T""l
Cj
N
Il existe deux types classiques de parcours de graphe : le parcours en profondeur et le 0
N
La complexité du parcours en profondeur dépend de la représentation du graphe. Pour un
© parcours en largeur. Ces deux types de parcours font un parcours complet du graphe, et @ graphe de n sommets et p arcs, la complexité de l'algorithme est O(n2 ) avec une matrice
.:c visitent chacun des sommets, une seule fois, pour lui appliquer un traitement. .....
..c d' adjacence, et O(max(n,p)) avec des listes d'adjacence .
Ol
ï:::
O'I
ï:::
>-
a. >
a.
Parcours en profondeur 0 Parcours en largeur
8 1 9.4.1 u 1 9.4.2
Le parcours en profondeur, à pa1tir d'un sommet s, passe d'abord par ce sommet, puis On appelle « distance » la longueur du chemin entre deux sommets d'un graphe. Le par
consiste à parcourir en profondeur chacun de ses successeurs. Ce parcours correspond donc cours en largeur d'un graphe à partir d'un sommet origine s consiste d'abord à visiter ce
à celui d'une composante connexe du graphe. Si le graphe en possède plusieurs, tous les sommet, puis à traiter les sommets de distance avec s égale à un, puis ceux de distance égale
sommets n'auront pas été traités, et l'algorithme devra se poursuivre avec un parcours en à deux, etc. L'algorithme s' éc1it de façon itérative et utilise une file d'attente qui conserve
profondeur d'un sommet non encore traité. les sommets déjà traités, par ordre de distance croissante, dont les successeurs sont à visiter.
19.4 Parcours d'un graphe 241 242 Chapitre 1 9 • Graphes
Comme pour le parcours en profondeur, les nœuds parcourus sont marqués afin de ne pas les @Functionalinterface
traiter plusieurs fois. Notez qu'un parcours en largeur parcomt une composante connexe du public interface Opération<T>
graphe. Pour un parcours complet d'un graphe à plusieurs composantes connexes, on décrit public void exécut er ( T e ) ;
l'algorithme en deux étapes comme précédemment :
Algorithme Parcour s-en-Largeur ( G )
Lors d'un parcours effectif d'un graphe, on transmet à la méthode de parcours une
{Parcours en largeur des composantes connexes du graphe GJ
pourtout s de G faire
fonction anonyme qui donne une implémentation particulière de l ' interface fonctionnelle
si non marqué ( s ) alors Opération. Par exemple, pour afficher tous les sommets d'un graphe sur la sortie standard,
Plargeu r ( G , s) on utlisera la lambda :
finsi
x - > { System . out . print ( x ) ;
finpour
Le traitement effectué sur chacun des sommets est défini par 1'inte1face fonctionnelle ge Il fin parcoursLargeur
nérique 3 Opération.
Notez que l'utilisation de ces méthodes de parcours n'a de sens que si l'ordre de parcours
3. lf le chapitre 13 page 143. des sommets est important. S'il s'agit d'appliquer un traitement sur chacun des sommets du
19.5 Exercices 243
graphe dans un ordre quelconque, il conviendra d' utiliser la méthode forEach à qui on passe
la fonction anonyme à exécuter.
1 9.5 EXERCICES
Exercice 19.1. Détemlinez le nombre maximum d'arêtes d'un graphe simple G à n som
mets et p composantes connexes.
Exercice 19.2. Soit un graphe à sept sommets donné par les listes d'adjacence suivantes :
sl -t s2 s3 s5
s3 -t s4 s6
s4 -t s7
s5 -t s2
s6 -t s4
Dessinez le graphe que ces listes représentent, puis donnez la matrice d' adjacence asso
ciée. Pour ce graphe particulier, quelle représentation vaut-il mjeux choisir pour économiser
de la place mémoire ?
Exercice 19.3. En partant du sommet sl du graphe précédent, donnez l'ordre de parcours
des sommets pour les trois types de parcours :
- profondeur préfixe ;
- profondeur postfixe ;
- largeur.
Exercice 19.4. Rédigez entièrement les deux classes d'implémentation de l'interface
Graphe (donnée à la page 235) à l'aide d'une matiice d'adjacence et des listes d'adjacence.
Exercice 19.5. Implémentez la notion de graphe valué avec les deux formes de représenta
tion précédentes.
-0 -0
0 0
c Exercice 19.6. Complétez les classes précédentes avec les méthodes de parcours en largeur c
::J ::J
0 et en profondeur. 0
V
T""l «:!"
T""l
0 Exercice 19.7. Écrivez une fonction qui, à partir de la représentation matricielle d'un gra 0
N N
@
.....
phe, renvoie sa représentation sous forme de listes d'adjacence. Écrivez sa réciproque. @
......
..c ..c
Ol Ol
ï::: Exercice 19.8. On définit l'opération union qui renvoie l'union de deux graphes. Sa signa ï::
>
a. >-
0..
0 ture est la suivante : 0
u u
umon Çraphe x Çraphe -t Çraphe
Donnez les axiomes qui déc1ivent la sémantique de cette opération et programmez une
méthode union pour les différentes représentations de graphe.
Exercice 19.9. On appelle puits d'une composante connexe, un sommet s tel que pour tout
sommet x =/= s, il existe un arc (x, s) mais pas l'arc ( s, :r) . Montrez qu'un graphe ne peut avoir
au maximum qu'un seul puits et écrivez la fonction chercherPuits qui recherche l'existence
d'un puits dans un graphe.
246 Chapitre 20 • Structures arborescentes
20.1 TERMINOLOGI E
Un arbre est formé d'un ensemble de sommets, appelés nœuds, reliés par des arcs et orga
nisés de façon hiérarchique. C'est en fait un graphe connexe sans cycle (voir le chapitre 19). Il
existe un nœud particulier appelé racine qui est à l'origine de l'arborescence. Contrairement
aux arbres biologiques, les arbres informatiques « poussent » vers le bas, et leur racine est
située au sommet de la hiérarchie. Chaque nœud possède zéro ou plusieurs .fils, reliés avec lui
de façon univoque. Chaque nœud, à l'exception de la racine, possède un père unique. Les fils
d'un même père sont évidemment des frères.
Chapitre 20 Tout nœud n d'un arbre est accessible par un chemin unique qui part de la racine et passe
par un ensemble de nœuds appelés ascendants de n. La racine d'un arbre est donc un ascen
dant de tous les nœuds de l'arbre. Réciproquement, tous les nœuds accessibles par un chemin
à partir d'un nœud n sont des descendants de ce nœud.
Structures arborescentes La figure 20.2 arbre qui possède quatorze nœuds, nommés n1 jusqu'à n14. Il est impor
tant de comprendre que les nœuds sont distincts et portent chacun un nom unique. Dans
l'exemple, l'ordre de nomination doit être considéré comme quelconque. La racine s'appelle
n1, le nœud n10 est un descendant des nœuds n 1 et n8, alors que n2 est l'ascendant des
nœuds n4 et n5.
hauteur
�;
n2
sente par des arbres. L'objet de ce chapitre est l'étude de la structure d'arbre qui est une des
2 0
structures de données les plus impo11antes en informatique.
Q
l0
n• n7 n9
énoncé conditionnel si
n3 n 14
-0 -0
0 0
c c ns
::J ::J
0 0
V V 3
T""l T""l
0 0
N N
@ @ FIGURE 20.2 Un arbre formé de quatorze nœuds .
.....
..c .....
..c
O'I Ol
ï::: ï:::
> >
0
a. a. Au chapitre 16, nous avons vu comment définir des objets récursivement, et les arbres se
0 0
u u prêtent bien à de telles définitions. Un arbre est ainsi formé d'un nœud racine et d'une suite,
éventuellement vide, d' arbres appelés sous-arbres. L'arbre de la figure 20.2 est formé de la
FIGURE 20.1 Un arbre syntaxique de l'énoncé si x=O alors y�O finsi. racine n1, et de trois sous-arbres. Son premier sous-arbre a pour racine le nœud n2 et deux
sous-arbres, son deuxième sous-arbre a pour racine le nœud n6 et un unique sous-arbre, etc.
Les nœuds qui ne possèdent pas de sous-arbres, qui n'ont donc pas de fils, sont appelés
Après avoir introduit la terminologie relative aux arbres, nous présenterons d'abord les nœuds externes ou feuilles. Ceux qui possèdent au moins un sous-arbre s' appellent des nœuds
arbres sous leur forme générale, puis nous en étudierons un forme particulière, les arbres internes. L'arbre de la figure 20.2 possèdent six nœuds internes et huit feuilles. Les nœuds n5
binaires. et n14 sont des feuilles, alors que ns et n10 sont des nœuds internes.
20.2 Les arbres 247 248 Chapitre 20 • Structures arborescentes
Une branche d ' un arbre est un chenùn entre la racine et une feuille. Le nombre de branches 20.2.1 Définition abstraite
d'un arbre est égal à son nombre de feuilles. La longueur d'un chenùn est définie entre deux
nœuds appartenant à une même branche, et est égale au nombre d'arcs qui les séparent. La Arbre est l'ensemble des arbres, Nœud l'ensemble des nœuds d'un arbre, et Forêt l'en
longueur du chemin entre les nœuds n8 et n11 est égale à deux, celle entre les points n2 et n4 semble des suites d'arbres. Une suite vide d'arbres est désignée par la constanteforêtvide.
est égale à un. Notez que la longueur d'un chemin d'un nœud unique est zéro.
Ensembles
La hauteur ou le niveau d'un nœud est la longueur du chemin entre la racine et lui. La hau
teur ou la profondeur d'un arbre est égale au maximum des hauteurs de ses nœuds. La hauteur Arbre utilise Nœud, Forêt
du nœud n10 est égale à deux, et la hauteur de l'arbre est trois. La profondeur moyenne d'un Forêt utilise Arbre, naturel, entier
arbre est égale à la somme des hauteurs de chacun de ses nœuds divisée par le nombre de forêtvide E Forêt
nœuds.
Pour les arbres étiquetés, nous ajouterons l'ensemble E des valeurs qui peuvent être asso
Un arbre étiqueté est un arbre dont les nœuds possèdent une valeur. Dans certains arbres, ciées à un nœud.
seules les feuilles sont étiquetées. Le nom du nœud et sa valeur sont deux notions distinctes,
et plusieurs nœuds peuvent posséder des valeurs identiques. La figure 20.3 montre 1' arbre
Description fonctionnelle
précédent étiqueté avec des caractères alphabétiques.
Les opérations données ci-dessous permettent la construction d'arbre. D'autres les com
pléteront par la suite.
"'/
forêt Arbre ---+ Forêt
G) G)� 1 � n1 0 @
L'opération cons construit un arbre à partir d'un nœud et d'une forêt. L'opération racine
renvoie le nœud de racine d'un arbre, etforêt retourne ses fils. Si le nœud est étiqueté, le type
n7 n9
abstrait est complété par les opérations suivantes :
7\
" 1•
@ (B
cons Nœ'ud x [, ---+ Nœ'ud
n1 1 n12 valeur Nœud -7 [,
nulle, un, deux, trois, etc. et que l' on note : (2) forêt( cons( n, f))
= f
(5) Vk, 1 ( k ( longueur(.{), La méthode forêt renvoie la forêt d' arbres de l'arbre courant. Enfin, l'opération cons du
longueur(supprimerArbre(f, k)) = longueur(!) 1 - type abstrait sera définie par le constructeur de la classe qui implémentera cette interface.
(6) Vk, 1 ( k ( longueur(!) + 1, L'interface générique suivante définit le type Forêt. Notez qu'elle implémente aussi l'in
longueur( ajouterArbre(f, k, a)) = longueur(f) + 1 terface Iterable et offrira l'énumération des sous-arbres de la forêt courante avec la mé
(7) Vk, 1 ( k ( longueur(f) et 1 ( i < k, thode iterator.
ièmeArbre(supprimerArbre(f, k), i) = ièmeArbre(f, i) public interface Forêt<A> extends Iterable<A> {
(8) Vk, 1 ( k ( longueur(!) et k ( i ( longueur(!) 1, - public int longueur ( ) ;
ièmeArbre(supprimerArbre(f, k), i ) = ièmeArbre(f, i + 1) public A ièmeArbre ( int r ) throws RanginvalideExcept ion;
public void a j out erArbre ( int r , A a ) throws RanginvalideExcept ion;
(9) Vk, 1 ( k ( longueur(!) + 1 et 1 ( 'i < k,
public void supprimerArbre ( int r) throws RanginvalideExcept i o n ;
ièmeArbre(ajouterArbre(.f, k, a), i ) = ièmeArbre(.f, i )
(10) Vk, 1 ( k ( longueur(!) + 1 et k = i,
ièmeArbre(ajouterArbre(f, k, a) , i) = a Dans ce qui suit, nous déc1ivons deux organisations de la structure d' arbre. La première
(11) Vk, 1 ( k ( longueur(.f) + 1 et k < i ( longueur(f) + 1 , utilise une structure chaînée, la seconde des listes d'adjacence.
ièmeArbre(ajouterArbre(.f, k, a ) , i ) = ièmeArbre(f, i 1)
-
(12) Vr, r < l et r > longueur(!), � f', f' = supprimerArbre(f,r) Utilisation d'une structure chaînée
( 1 3) \fr, r < l et r > longueur(!) + 1, � f', f' = ajouterArbre(f, r, a) Une première mise en œuvre possible est une représentation chaînée des arbres. Chaque
arbre est formé d'une part de sa racine et d'autre part de sa forêt. La classe générique
Enfin, si l'arbre est étiqueté, on ajoute l'axiome : ArbreChaîné qui implémente l'interface Arbre est la suivante :
11 , 11 4 11 5
113 tl5
112
114
n6
n3
�6
FIGURE 20.5 Arbre représenté par des listes d'adjacence.
public void supprirne rArbre ( int r ) throws Ranginval ideExcept ion { Lors du parcours de 1 ' arbre, si le traitement est appliqué sur la racine avant 1 'énoncé itéra
super . supprirne r ( r ) ; tif, le parcours est préfixe. Au contraire, s'il a lieu après, le parcours est postfixe.
Nous donnons ci-dessous la programmation en JAVA de la méthode de parcours préfixe
de la classe Arbre. Le traitement de la racine est assuré par une opération exécuter du
-0 -0
0 0 paramètre op de type Opération (voir la section 1 9.4.3, page 242). Par ailleurs, notez que
c c
::J ::J
0 Utilisation des listes d'adjacence 0 l'utilisation de l'énoncéfo reach est possible car Forêt implémente Iterable.
V
.-l V
T""l
0 0 public void parcoursPréfixe ( Opération<E> op) {
N Une autre façon de représenter les arbres est d'utiliser des listes d'adjacence semblables N
op . exécuter ( racine ( ) ) ;
@ à celles employées pour implémenter des graphes. On construit une suite linéaire de tous les @
.....
..c .....
..c for (Arbre<E> a : forêt ( ) )
Ol nœuds et on associe à chacun des nœuds la liste de ses fils. La figure 20.5 donne l'arbre de la Ol a . parcoursPréfixe ( op ) ;
ï::: ï:::
>
a. figure 20.2 selon cette organisation. >
a.
0 0
u Si la suite est implémentée par un tableau, cette représentation offre un accès en 0(1) à u
chaque nœud de façon indépendante de sa position dans la hiérarchie, mais la gestion dyna
mique des ajouts et des suppressions des nœuds dans l'arbre est plus difficile.
20.3 ARBRE BINAIRE
20.2.3 Algorithmes de parcours d'un arbre Un arbre binaire est un arbre qui possède au plus deux fils, un sous-arbre gauche et un
sous-arbre droit. Les arbres binaires sont utilisés dans de nombreuses circonstances. Ils
Comme pour un graphe, le parcours d'un arbre passe par tous les nœuds pour appliquer un servent, par exemple, à représenter des généalogies ou des expressions arithmétiques (voir
traitement, toujours le même, sur chacun d'entre eux. Deux types de parcours sont possibles : la figure 20.6).
en profondeur et en largeur.
20.3 Arbre binaire 253 254 Chapitre 20 • Structures arborescentes
A
Un arbre binaire de n nceuds possède une profondeur p minimale lorsqu'il est parfait et
maximale lorsqu'il est dégénéré. La profondeur p et le nombre de nceuds n d'un arbre sont
tels que llog2 nJ ( p ( n 1 , où lJ désigne la partie entière inférieure. Cette relation est
A 0
-
très importante car elle détermine la complexité de la plupart des algorithmes sur les arbres
binaires. Cette complexité est comprise entre O(log2 n ) et 0(n) .
0 8
Une autre relation intéressante lie le nombre de feuilles et le nombre de nceuds des arbres
binaires complets. Leur nombre de feuilles est égal à leur nombre de nceuds plus 1.
0
Nœv,d x Arbreb x Arbreb
Elles signifient qu'un arbre binaire est soit vide, soit formé d'un nceud et de deux arbres
binaires, appelés respectivement sous-arbre gauche et sous-arbre droit. Notez que la notion
d' arbre binaire vide, étrangère aux arbres, a été introduite pour distinguer les deux arbres
binaires de la figure 20.7 page 253.
FIGURE 20.7 Deux arbres binaires distincts.
Ensembles
L'arbre binaire (a) de la figure 20.8 possède une forme quelconque, mais certains arbres
Arbreb est l'ensemble des arbres binaires et possède l'élément particulier arbrevide qui
ont des formes caractéristiques. On appelle arbre binaire dégénéré (b ), un arbre dont chaque
cotTespond à un arbre binaire vide. Les nceuds de l'arbre appartiennent à l'ensemble Nœud.
niveau possède un seul nœud - les nœuds appartiennent à une seule et même branche. Un
arbre binaire complet (c) est un arbre dont les nœuds, qui ne sont pas des feuilles, possèdent
toujours deux fi1s. Enfin, un arbre binaire parfait (d) est un arbre dont toutes les feuilles sont
Arbreb utilise Nœud et booléen
situées sur, au plus, deux niveaux ; les feuilles du dernier niveau sont placées le plus à gauche.
arbrevide E Arbreb
-0 -0
0 0
c Description fonctionnelle
::J §
0 0
V
T"'l � Les opérations suivantes sont définies sur le type Arbreb
0 0
:
N N
@ @
.....
..c .....
..c
cons Nœud x Arbreb x Arbreb -+ Arbreb
Ol Ol racme Arbreb -+ N(E7Ld
ï::: ï:::
>
a. >
a. sag Arbreb -+ Arbreb
0 0
u u sad Arbreb -+ Arbreb
est-vide? Arbreb -+ booléen
Comme pour les arbres généraux, les opérations suivantes sont définies sur les nceuds
étiquetés :
(a) (b) (c) (d)
cons JVœud x E -+ JVœud
FIGURE 20.8 Arbre (a) quelconque (b) dégénéré (c) complet (d) parfait.
valeur Nœud -+ E
20.3 Arbre binaire 255 256 Chapitre 20 • Structures arborescentes
Description axiomatique jamais tester s'il est vide dans la mesure oli l'objet doit exister pour que la méthode puisse
être exécutée ; il sera donc toujours différent de null. Le coût supplémentaire en espace
Les axiomes suivants déclivent la sémantique des opérations du type abstrait Arbreb. Les
mémoire est celui d'une seule constante arbreVide pour tout arbre binaire.
deux premiers axiomes spécifient un arbre vide, le troisième, la façon de construire un arbre
binaire, et les derniers l'accès et les conditions d'accès aux composants d'un arbre binaire. public class ArbreBinaireChaîné<E> implements ArbreBinaire<E>
public static final ArbreBinaire arbreVide
'tin E Nœud, 'tJ a, g, d E Arbreb = new ArbreBinaireChaîné ( null ) ;
(1) est-vide?(arbrevide) = vrai protected E laRacine;
(2) est-vide?( cons(n, g, d)) = faux protected ArbreBinaire<E> sag, sad;
public ArbreBinaireChaîné ( E r , ArbreBinaire<E> g , ArbreBinaire<E> d ) {
(3) con s( racine(a), sag(a), sad( a)) = a
laRacine = r;
(4) racine(cons(n, g, d)) = n sag g;
(5) sag(cons(n, g, d)) = g sad = d;
(6) sad(cons(n, g, d)) = d
public ArbreBinaireChaîné ( E r ) {
(7) � n E Nœud, n = racine(arbrevide)
this ( r , ArbreBinaireChaî né . arbreVide , ArbreBinaireChaîné . arbreVide ) ;
(8) � a E Arbreb, a = sag (arbrevid e)
(9) � a E Arbreb, a = sad(arbrevide) public boolean estVide ( ) {
return this == arbreVide ;
L'axiome suivant est défini pour un arbre binaire étiqueté :
public E racine ( ) throws ArbreVideExcept ion
'tin E Nœud et 'tJe E E if (es tVide ( ) )
(10) valeur(cons(n, e ) ) = e throw new ArbreVideExcept i on ( ) ;
return laRacine;
La programmation en JAVA du parcours infixe d'un arbre binaire est donnée ci-dessous.
Cette méthode complète l a classe ArbreBinaire. Le traitement à appliquer à chaque racine
est donné par le paramètre fonctionnel op de type Opération (voir à la page 242).
n2
nk
20.5 EXERCICES
-0 -0 Exercice 20.1. Donnez l' alg01ithme du parcours en largeur d'un arbre général.
0 0
c c
::J ::J
0 0
'
'
' Exercice 20.2. Montrez par récurrence qu'un arbre binaire de profondeur p possède au plus
V
T"'l V
T""l
0 0 2P nœuds.
N N
@ @ Exercice 20.3. Donnez les alg01ithmes qui calculent la hauteur d'un arbre quelconque et
.....
..c .....
..c
Ol Ol d'un arbre binaire, puis ajoutez la méthode hauteur aux classes Arbre et ArbreBinaire .
ï::: FIGURE 20.11 Transformation d'un arbre général en arbre binaire. ï:::
> >
a.
0
a.
0 Exercice 20.4. Le nombre de STRAHLER 1 est une sorte de mesure de la complexité d'un
{
u u arbre binaire complet. Il est défini par la fonction S suivante :
L'alg01ithme qui transforme un arbre général en un arbre binaire s'exprime récursivement.
Les transformés binaires des sous-arbres d'une forêt sont liés par leur sous-arbre droit en
partant du dernier sous-arbre de la forêt, puis en remontant jusqu'au premier sous-arbre. 0 si a est une feuille
Le sous-arbre gauche du nœud courant est lié au premier sous-arbre transformé sous forme S(a) = S(sag(a)) + 1 si S(sag(a)) = S(sad(a))
binaire. max(S(sag(a)), S(sad(a))) smon
Algorithme Transformé-Binaire ( a , b)
l . Utilisé à l'origine en hydrographie pour décrire la structure d'un réseau de rivières, ce nombre est utilisé en
{Rôle : transforme l ' a rbre a en arbre binaire b } informatique par les compilateurs dans la gestion de lallocation des registres, ou encore par des outils graphiques
racine ( b ) f- racine ( a ) de visualisation de graphe.
20.5 Exercices 261 262 Chapitre 20 • Structures arborescentes
Rédigez l'algorithme qui calcule ce nombre et ajoutez la méthode strahler à la classe plus fréquentes. Notez que pour un ensemble d'une centaine de caractères, il faut au plus
ArbreBinaire. f1og2 1001 = 7 bits pour coder un caractère.
Écrivez en JAVA une classe Huffman q<ui fournit deux méthodes qui, respectivement,
Exercice 20.5. Deux arbres binaires a et b sont miroirs s'ils possèdent la même racine, si
code et décode un fichier de texte. Au préalable, vous constituerez une table des fréquences
le sous-arbre gauche de a est le miroir du sous-arbre droit de b et si le sous-arbre droit de a
des caractères à partir de fichiers de texte dont vous disposez.
est le miroir du sous-arbre gauche de b. Écrivez l'algorithme qui permet de vé1ifier si deux
arbres sont miroirs et ajoutez la méthode miroir à la classe ArbreBinaire. Exercice 20.10. Un arbre étiqueté est représenté sur un fichier de texte sous la fom1e sui
vante : chaque nœud est représenté par son étiquette (une suite de lettres), suivie d'une vir
Exercice 20.6. Écrivez la version itérative de l'algo1ithme de parcours en profondeur d'un
gule, suivie du nombre de fils du nœud (un entier naturel), suivi de la représentation de ses
arbre binaire. Vous aurez besoin d'une pile, dont la hauteur est au plus égale à la profondeur
fils, séparés par des virgules. Dessinez l'arbre défini par la suite de caractères :
de l'arbre.
pierre , 3 , paul , O l , marie , O , claude , 2 , maud, O O , l é a , 1 , l é o , O , charles , O ,
Exercice 20.7. Il est possible de dénoter de façon univoque les nœuds d'un arbre binaire Écrivez l 'algorithme qui construit un arbre à partir de sa représentation textuelle lue sur un
par des mots formés d'une suite de 0 et de 1 obtenus en parcourant le chemin qui mène de la
fichier. Programmez cet algorithme en JAVA.
racine à ce nœud. Par définition, la racine est le mot vide 0, et si un nœud est dénoté par le
mot m, son fils gauche est mO et son fils droit ml. Par exemple, les nœuds de l'arbre donné
par la figure 20.6 de la page 253 sont tels que + = 0, x = 0, a = OO, b = 01 et 3 = 1.
À l'aide de cette notation, donnez :
- la représentation des nœuds des bords gauche et droit d'un arbre binaire ;
- la représentation du père d'un nœud ;
- la hauteur d'un arbre binaire.
Exercice 20.8. Soit un ensemble E d'éléments { x1, x2, . . . , Xn} munies de probabilités
pi , P2, . . . , Pn, avec 'L�= l Pk = 1. On associe à l ' ensemble E un arbre binaire construit de
la façon suivante : à partir des n feuilles de l'arbre constituées par les éléments xk> on choisit
les deux éléments Xi et Xj qui possèdent les probabilités les plus petites et on construit un
nouveau nœud x' ayant Xi et Xj pour fils et dont la probabilité est Pi +PJ . On placera 1 ' élément
de probabilité la plus petite à gauche. Dans l'ensemble E, on remplace Xi et x.1 par x'. Le
nouvel ensemble E n'a plus que n - 1 éléments. On recommence l'opération jusqu'à obtenir
un ensemble réduit à un élément.
Dessinez l'arbre associé à l'ensemble E = {b, e, m, x, z } muni des probabilités p(b) =
-0 0 , 2 1 , p(e) = 0 , 35 , p(m) =
0, 26, p(x ) = 0 , 08 et p(z) = 0, 1. 'O
0 0
c Pour u n ensemble E de n éléments, combien de nœuds internes et de feuilles possédera c
::J ::J
0 0
V 1' arbre ? V
T""l T""l
0 Rédigez l'algorithme qui construit l'arbre associé à un ensemble E selon la méthode pré 0
N N
@ cédente. @
.....
..c .....
..c
Ol Ol
ï::: Exercice 20.9. L'arbre de l'exercice précédent s'appelle un arbre de HUFFMAN. Associé à ï:::
>
a. >
a.
0 la notation présentée à l'exercice n° 19.7, cet arbre permet de définir un code binaire unique 0
u pour les éléments d'un [Link] E. Pour l 'ensemble E précédent, on obtient le code : b = 11,
u
e = 01, m = OO, x = 100 et z = 101. Avec u n tel code, l a suite 00011101101 se décode sans
ambiguïté : mebez (on décode en partant de la racine de l'arbre de HUFFMAN, et en suivant le
chemin indiqué jusqu'à une feuille ; on écrit la lettre cotTespondante et on repart de la racine
pour décoder le reste).
Le codage de HUFFMAN est utilisé pour comprimer des fichiers de caractères. Les taux
de compression peuvent varier de 30% à 60% selon les fichiers. L'idée est de permettre un
codage de longueur variable des caractères, avec le codage le plus court pour les lettres les
264 Chapitre 21 • Tables
21.1.1 Ensembles
Table définit l 'ensemble des tables qui mémorisent des éléments de l 'ensemble E, chacun
des éléments étant muni d'une clé p1ise dans Clé .
Chapitre 2 1
21.1.2 Description fonctionnelle
Les signatures des trois opérations de base sur les tables sont données par :
De plus, l'opération qui permet d'obtenir la clé d'un élément à partir de sa valeur est
définie par :
La conservation de l'information sous des formes diverses, que ce soit en mémoire centrale 21.1.3 Description axiomatique
ou en mémoire secondaire, et la recherche d'infom1ations à partir de critères spécifiques est
une activité très courante en informatique. Nous appellerons table 1 , la structure qui permet de Pour cette desc1iption axiomatique, nous complétons le type abstrait par l'opération oc
conserver des éléments de nature quelconque, munie des opérations d'ajout, de suppression currences qui renvoie le nombre d'occurrences d'une clé dans une table.
et de recherche.
occurrences : Table x Clé ---+ naturel
L'accès à un élément se fait à partir d'une clé qui l'identifie. Par exemple, si une table
(1) occurrences(tablevide, c) = 0
conserve des informations sur des personnes, on poun:a choisir comme clé le numéro INSEE
-0 de chaque individu. Notez toutefois, que l'unicité de la clé n'est pas une nécessité, et que -0
(2) clé(e) = c ::::> occurrences(ajouter(t, e), c) = occurrences(t, c) + 1
0 0
c
::J plusieurs éléments distincts peuvent posséder la même clé. c
::J
(3) clé( e ) =f c ::::> occurrences( ajouter( t, e), c) = occurrences(t, c)
0 0 (4) occurrences(t, c) = 0 ::::> � t, t = supprimer(t, c)
V
T""l V
T""l
0 La façon de représenter une table aura une grande incidence sur la complexité des al 0 (5) occurrences(t, c) ;;::: 1 =>
N gorithmes de manipulation de table. Ces algorithmes s'appuient essentiellement sur des re N
@ @ occurrences(supprimer(t, c) , c) = occurrences(t, c) - 1
..... cherches basées sur des comparaisons entre clés, et nous distinguerons par la suite les re .....
..c
O'I
..c
Ol (6) c =!= c' => occurrences(supprimer(t, c) , c') = occurrences(t, c')
ï::: cherches positives lorsque la clé recherchée est présente daris la table et les recherches néga ï:::
>
a. tives lorsqu'elle est absente. >
a. (7) occurrences(t, c) = 0 ::::> � e, e = rechercher(t, c) E t
0 0
u u (8) occurrences(t, c) ;;::: 1 => clé(rechercher(t, c)) = c
Les tables peuvent être représentées de nombreuses façons. Dans ce chapitre, nous traite
rons uniquement des tables placées en mémoire centrale, et représentées par des listes et des
arbres, ainsi que des tables d'adressage dispersé. Mais, tout d'abord, décrivons formellement
la notion de table par son type abstrait Table. 2 1 .2 REPRÉSENTATION DES ÉLÉM ENTS EN JAVA
Pour toutes les représentations des tables de ce chapitre, les éléments sont formés d'une
valeur et d'une clé de type quelconque. Pour les définir, nous utiliserons la classe générique
1 . Le terme dictionnaire est également employé pour désigner cette strucntre. Élément suivante :
21.2 Représentation des éléments en Java 265 266 Chapitre 21 • Tables
public class É l ément<V , C> Toutes les autres méthodes de l' inte1face fonctionnelle sont définies par défaut. La mé
protected V valeur; thode comparable vérifie si deux clés peuvent être comparées, c'est-à-dire si elles sont de
protected C c l é ; même nature. Par défaut, elle renvoie toujours vrai, et la lambda précédente de comparaison
public É lément ( V v , C c) { de deux chaînes de caractères ne fera aucune vérification.
valeur = v ;
clé = c ; Si l'on souhaite vérifier que les clés sont comparables, on pourra par exemple représenter
un comparateur de chaînes de caractères par la classe suivante :
public C c l é ( ) { return c l é ; }
public V valeur ( ) { return valeur; public class ComparateurDeCléAlpha implements Comparateur<String>
public void changerClé ( C c ) { clé = c ; public int compare r (String x, String y ) {
public void changerValeur ( V v ) { valeur = v ; } return x . compareTo ( y ) ;
Les clés sont des objets quelconques, mais doivent posséder des opérateurs relationnels public boolean comparable ( Object x ) {
return == null) ? false :
nécessaires aux opérations du type abstrait Ta,ble. À chaque table, nous associerons les opé
(x
Stirng . class . isAss ignableFrom ( x . getClass ( ) ) ;
rations de comparaison propres à l 'ensemble des clés utilisées 2 .
Les signatures de ces opérations sont données par l'interface générique fonctionnelle sui Il ComparateurDeCléEn tière
vante :
@Functionalinterface Les axiomes 4 et 7 du type abstrait Table montrent que les opérations rechercher ou sup
public interface Comparateur<T> primer échouent si la clé n'est pas présente dans la table. Il est possible de traiter cette si
tuation de plusieurs façons, soit en signalant une erreur, soit en renvoyant un élément spé
public int compare r ( T x, T y) ; cial, ou encore en ajoutant aux opérations un booléen qui indique l'échec de l'opération.
Par la suite, nous retiendrons la première solution, et les opérations émettront l'exception
public default boolean comparable ( T x) { CléNonTrouvéeExcept ion.
return true;
Les représentations de table que nous allons étudier maintenant, c'est-à-dire à l'aide de
public default boolean égal ( T x , T y) listes, d'arbres et de fonctions d' adressage dispersé, implémenteront toutes l ' interface géné
return this . compare r ( x , y ) O; rique T ab 1 e suivante :
public default boolean inférieur ( T x , T y ) { public interface Table<V, C > extends Iterable<É lément <V , C> >
return this . c ompare r ( x , y ) < 0; public void a j outer ( É lément <V, C> e ) ;
public void supprime r ( C c l é ) ;
-0 -0 public É lément <V, C > recherche r ( C c l é ) throws CléNonTrouvéeExcept ion;
0 public default boolean inférieurOu É gal ( T x, T y) { 0
c c
::J return this . c ompare r ( x , y ) <= O ; ::J
0 0
V
T""l V
T""l
0 public default boolean supérieur ( T x , T y) { 0
N N
@ return this . compare r ( x , y ) > O; @
.....
..c
Ol
ï::: public default boolean supérieurOuÉ gal ( T x , T y) {
�
ï:::
2 1 .3 REPRÉSENTATION PAR UNE LISTE
>
a. >
a.
return this . compare r ( x , y ) >= O ;
0 21 .3.1 Liste non ordonnée
u 8
La représentation d'une table par une liste non ordonnée est la méthode la plus simple et
La comparaison de deux valeurs est assurée par l a méthode comparer qui renvoie un la plus naïve. L'ajout des éléments peut se faire n'importe où, et en particulier en tête ou en
entier égal à 0 si x = y, négatif si x < y et positif si x > y. Ainsi, nous pourrons définir queue de liste, selon la représentation choisie de la liste, afin de garder une complexité en
un comparateur de chaînes de caractères par la lambda ( x , y ) -> x . compareTo ( y ) . 0(1). L'opération de suppression nécessite une recherche de l'élément à supprimer, suivie
ou non d'un décalage d'éléments si la table est représentée par un tableau. L'algoiithme de
2. Notez qu'il aurait été aussi possible de munir chaque clé de ses opérations de comparaison. C'est le choix de recherche consiste à comparer les éléments un à un jusqu'à ce que l'on ait trouvé l'élément,
l'API JAVA. ou alors atteint la fin de la liste.
21.3 Représentation par une liste 267 268 Chapitre 21 • Tables
Une liste linéaire l peut être définie récursivement (cf. exercice 17.6) comme étant, soit de type Comparateur. Cette classe implémente l'interface Table. Son constructeur mé
la liste vide, soit la concaténation d'un élément de tête e avec une liste l' que nous noterons morise les opérations de comparaison du type de clé utilisé.
< e, l' > . La définition axiomatique de rechercher s'exprime alors comme suit :
public class ListeNonOrdonnéeChaînée<V , C>
extends Li steChaînée<É lément<V , C >> implements Table<V , C>
(1) � e, e = rechercher(listevide, c)
(2) clé(e) = c � recher ch er ( < e , l > , c) = e protected Comparateur<C> comp;
(3) clé(e) =/= c � recher ch er ( < e, l >, c) = rechercher(l, c) public Li steNonOrdonnéeChaînée ( Comparat eur<C> cmp ) {
comp=cmp;
L'algorithme de recherche ci-dessous parcourt la liste de façon itérative et compare la clé
recherchée à celle de chacun des éléments. La recherche s'arrête lorsque la clé recherchée est
trouvée ou lorsque la liste a été entièrement parcourue. Dans ce dernier cas, la recherche est
négative.
Algorithme rechercher ( t , c) En suivant l'algorithme initial (sans sentinelle), la méthode rechercher s'écrit :
{Rôle : rechercher dans l a table t l ' élément de clé c } public É lément<V , C> rechercher ( C c l é ) throws CléNonTrouvéeException
{Conséquent renvoie l ' él ément e de clé c if ( ! comp . comparable ( c lé ) )
Vk, l�k�longueur (t ) , clé (ième (t, k) )#c} throw new CléincomparableExcept i on ( ) ;
i t- 1 for ( É lément <V, C> x : this)
tantque i#longueu r ( t ) faire if ( comp . égal ( x . clé ( ) , clé) ) return x;
{ i <longueur (t) et Vk, l�k<i, clé (ième ( t , k ) )#c} Il Vk, l�k� longueur (this) , clé (ième (this, k) ) #c
x t- ième ( t , i ) throw new CléNonTrouvéeExcept i on ( ) ;
si c l é ( x ) =c alors
{clé trouvée}
rendre x S'il n'y a pas équiprobabilité dans la recherche des éléments, il est possible d'ordonner
sinon i t- i + l les éléments de façon à placer en tête de liste les éléments les plus recherchés. Toutefois,
finsi on n'a pas toujours connaissance des éléments les plus recherchés. Dans ce cas, il faut faire
fintantque
évoluer l a liste pour que les éléments les plus recherchés soient situés en tête. C'est ce que
{Vk, l�k�longueur (t ) , clé (ième ( t , k ) )#c}
l'on appelle la recherche auto-adaptative. Citons deux algorithmes peu coûteux :
- après chaque recherche, on place l'élément recherché en tête de liste ; cette méthode
S'il y a équiprobabilité dans la recherche des éléments, le coût moyen d'une recherche est bien adaptée si la liste est représentée sous forme chaînée ;
positive, exprimé en nombre de comparaisons, est � (n+ 1), et n pour une recherche négative. - après chaque recherche, on fait progresser l'élément recherché d'une place vers la tête
Cet algorithme peut être légèrement amélioré grâce à une sentinelle. On supp1ime le test de la liste.
-0 de fin de liste en ajoutant systématiquement la clé recherchée à l'extrémité de la liste : -0
0 0
c c
::J ::J
0 Algorithme rechercher ( t , c) o 21 .3.2 Liste ordonnée
V
T""l i t- 1 V
.-l
0 0 Nous considérerons que les éléments d'une liste ordonnée l sont en ordre croissant de telle
N a j outer un élément bidon de c l é c en fin de l i s t e N
@ tantque c#ième ( t , i ) faire @ façon que :
.....
..c {i�longueur (t) +l e t Vk, l�k<i, clé (ième ( t , k ) )#c}
.....
..c
Ol Ol
ï::: i t- i+l ï:::
>
a. >
a. \:li, j E [ l , longueur(l)], i < j * clé(ième(l, i)) � clé(ième(l, j ) )
0 fintantque 0
u {clé (ième (t, i ) ) =c} u
si i�longueur ( t ) alors rendre ième ( t , i ) finsi
Les axiomes qui définissent les opérations d u type abstrait Table sont exprimés à l ' aide de
la définition récursive des listes.
{Vk, l� k�longueur (t ) , clé (ième ( t , k ) )#c}
(1) � e , e = rechercher(listevide, c)
La classe ListeNonOrdonnéeChaînée représente une liste dont les éléments sont or (2) clé( e ) = c * rechercher(< e , l >, c) = e
donnés par des clés dont les opérateurs de comparaisons sont contenus dans l'attribut comp
21.3 Représentation par une liste 269 270 Chapitre 21 • Tables
(3) clé(e) < c ::::?rechercher(< e, l > , c) = rechercher(l, c) if ( comp . égal ( x . clé ( ) , clé) )
(4) clé(e) > c ::::?� e' e' = rechercher(< e, l >, c) Il clé trouvée
return x ;
(5) ajouter(listevide, e) =< e, listevide > el se
(6) clé(e) < clé(e') ::::?ajouter(< e, l >, e') =< e, ajouter(l, e') > Il clé (élt couran t ) > clé
(7) clé(e) ;;::: clé(e') ::::?ajouter(< e, l >, e') =< e', < e, l >> throw new C l éNonTrouvéeExcept i on ( ) ;
(8) clé(e) = c ::::? supprimer(< e, l > , c) = l 11 'ïlk, l�k�longueur (this) , clé ( ième (this, k) ) =f. c
throw new CléNonTrouvéeExcept i on ( ) ;
(9) clé(e) < c ::::? supprimer(< e, l > , c) =< e, supprimer(Z, c) >
(10) clé(e) > c ::::? � l', l' = supprimer(< e, l >, c)
La complexité des méthodes de recherche séquentielle dans des listes ordonnées ou non
Les opérations parcourent la liste tant que la clé de l' élément à rechercher, à supprimer ou
n'est pas très bonne puisqu'elle est de l 'ordre de n . Toutefois, e11es mettent en jeu des al
à ajouter est supérieure à celle de l'élément courant.
gorithmes très simples, qui peuvent être raisonnablement utilisés pour des tables de petite
Avec une liste ordonnée, les trois opérations de base sont en éJ(n), avec une [Link]é taille.
moyenne égale à � (n + 1).
L a position d'un nouvel élément à ajouter suit celle de l'élément qui lui est immédiatement
inférieur. Avec une représentation chaînée de la liste, l 'opération ajouter recherche la position
21 .3.3 Recherche dichotomique
d'insertion, puis modifie le chaînage, selon la même technique que pour la liste non ordonnée.
Le principe de l 'algorithme est de diviser respace de recherche de l'élément en deux es
Si cet élément est le plus petit, l'inse1tion est en tête de liste. Si la liste est représentée par paces de même taille. L'élément recherché est dans l'un des deux espaces. La recherche se
un tableau, les opérations supprimer et ajouter sont plus coûteuses dans la mesure où elles pow-suit dans l 'espace qui contient l'élément recherché selon la même méthode. Cette mé
doivent décaler une partie des éléments du tableau.
thode de résolution par partition est une méthode classique qui consiste à diviser un problème
L'algorithme de recherche parcourt la liste de façon séquentielle jusqu'à ce que l'on ait en sous-problèmes de même nature mais de taille inférieure.
trouvé un élément de clé supérieure ou égale à celle recherchée. En cas d'égalité, l'élément
La recherche dichotomique nécessite une table ordonnée et, pour être efficace, un accès
recherché est trouvé.
direct à chaque élément de la table. La représentation habituelle de la table est le tableau. Au
Algorithme rechercher ( t , c) début, 1 'espace de recherche est la liste entière, depuis un rang gauche égal à 1 jusqu'à un rang
i f-- 1 droit égal à la longueur de la table. Le rang du milieu (gauche + droit) /2 divise la table en
t r ouvé f-- faux
deux. Si la clé recherchée est égale à celle de l'élément du nùlieu alors la recherche s'achève
tantque non trouvé et i#longueur ( t ) faire
avec succès. Si elle lui est inférieure, la recherche se poursuit dans l'espace de gauche. Si elle
{i#longueur (t} et Vk, l'k<i, clé (iéme (t , k) } <c}
x f-- ième ( t , i )
lui est supérieure, la recherche se poursuit lieu dans l'espace de droite. La recherche échoue
si c l é ( x ) �c alors
lorsque l'espace de recherche devient vide, c'est-à-dire lorsque les rangs gauche et droit se
-0 trouvé f-- vrai -0 sont croisés. Une écriture évidente de cette méthode est :
0 0
c f--
c
::J sinon i i+l ::J Algorithme rechercher ( t , c)
0 finsi
0
V V gauche +--- 1
T""l fintantque T""l
0 0 droit +--- longueur ( t )
N si clé ( x ) =c alors rendre x finsi
N
@ @ répéter
..... {Vk, l,k,longueur (t ) , clé (iéme (t, k) )#c} ..... {gauche�droi t e t
..c ..c
Ol Ol 'ïlk, l�k<gauche, clé (ième (t, k) ) <c e t
ï::: ï:::
>
a. >
a. 'ï!k, droit <k�longueu r (t ) , clé (ième (t , k ) ) >c}
0 S'il y a équiprobabilité dans la recherche des éléments, le coût moyen d'une recherche 0
u u milieu +--- (gauche+droit ) / 2 <
positive et négative est � ( n + 1). La complexité est éJ( n) comme pour la liste non ordonnée. x +--- ième ( t , mi l i e u )
La programmation en JAVA de cette méthode est donnée ci-dessous : si c=clé ( x ) alors rendre x
Une autre version de cet algorithme est donnée ci-dessous. Si l 'élément est trouvé, les Dans une table à n éléments, le nombre de comparaisons pour une recherche négative ou
deux bornes gauche et droit sont modifiées de telle façon que la boucle s'achève et milieu positive (dans le pire des cas) est égal à llog2 nJ + 1. Cela correspond au parcours entier
indique alors le rang de 1 'élément recherché. d'une branche d'un arbre binaire fortement équilibré.
Algorithme rechercher ( t , c) Le nombre moyen de comparaisons est d'environ log2 n - 1 pour une recherche positive,
gauche +-- 1 et log2 n pour une recherche négative. D'une façon générale, les algo1ithmes de recherche
droit +-- longueu r ( t ) dichotomique ont une complexité égale à O(log2 n).
{ l ' espace de recherche est a u départ toute l a table}
Les ajouts et les suppressions d'éléments en table par la méthode dichotomique ne sont
répéter
pas efficaces dans la mesure où ils nécessitent des décalages traités de façon séquentielle
{ga uche�dro i t et
Vk, l�k<gauche, clé (ième (t, k ) ) <c et pour maintenir ]'ordre de la table. La méthode dichotomique est donc adaptée à des tables
Vk, droit <k�longueur (t ) , clé (ième (t , k ) ) >c } qui n'évoluent pas ou très peu, comme la table des mots réservés d'un langage dans un compi
milieu +-- (gauche+droi t ) / 2 lateur. Lorsque les tables doivent évoluer, nous préférerons une représentation avec des arbres
x +-- ième ( t , mi l i e u ) binaires.
s i c�clé ( x ) alors gauche +-- milieu+l finsi
si c�cl é ( x ) alors droit +-- milieu-1 finsi
jusqu' à gauche>droit
{clé (ième ( t , mi l i eu) ) =c ou
2 1 .4 REPRÉSENTATION PAR UN ARBRE ORDONNÉ
Vk, l�k�longueur (t ) , clé (ième ( t , k ) )#c}
L a table est représentée par u n arbre binaire ordonné, c'est-à-dire un arbre binaire étiqueté
si c l é ( x ) =c alors rendre x finsi
{Vk, l�k�longueur (t ) , clé (ième ( t , k ) )#c} dont les valeurs sont rangées suivant une relation d'ordre. Par la suite, nous considérerons la
relation suivante entre les nœuds :
L'écriture de cet algorithme peut être améliorée en regroupant les deux tests d'égalité en
'\/a E Arbreb0, '\/e E sag(a), Ve' E sad(a)
un seul : clé(e) :::;clé(valeur(racine(a))) < clé(e')
Algorithme rechercher ( t , c) Toutes les clés du sous-arbre gauche sont inférieures ou égales à la clé du nœud, elle-même
gauche +-- 1 strictement inférieure à toutes les clés du sous-arbre droit.
droit +-- longueur ( t )
Dans le chapitre précédent, nous avons déjà évoqué les arbres binaires et les algorithmes
répéter
associés. Nous avons vu que pour tout arbre binaire ayant n nœuds et une profondeur p, on a
{gauche<droit et
Vk, l�k<gauche, clé (ième (t, k ) ) <c et la double inégalité suivante :
Vk, droit�k�longueur ( t ) , clé (ième (t , k) ) � c }
mili e u +-- (gauche+droi t ) / 2
-0 +--
-0
0 x ième ( t , mi l i e u ) 0
c c La pire des recherches dans un arbre dégénéré est alors semblable à celle dans une
::J si c�clé ( x ) alors droit +-- milieu ::J
0 0
sinon {c>clé (x) } gauche +-- milieu+l liste linéaire ordonnée. Alors que pour un arbre parfaitement équilibré, elle sera égale
V
T""l V
T""l
0 jusqu ' à gauche�droit 0 à llog2 nJ + 1 . La complexité peut donc varier de O(n) à O(log2 n).
N N
{clé (ième ( t , gauche) ) =c ou
@ @ La profondeur moyenne d'un arbre est de l'ordre de fa. Le nombre de comparaisons
.....
..c Vk, l�k�longueur (t ) , clé (ième ( t , k) )#c} .....
..c
Ol Ol moyen pour la recherche, l'ajout et la suppression d'un élément dans un arbre binaire de
ï::: x +-- ième ( t ab , gauche) ï:::
> si c l é ( x ) =c alors rendre finsi > recherche quelconque est environ égal à 2 log2 n.
a. x a.
0 0
u {Vk, l�k�longueur (t ) , clé (ième ( t , k) )#c} u Dans la programmation en JAVA des méthodes de la table, un arbre binaire ordonné sera
représenté par une structure chaînée dont la définition a été donnée à la section 20.3.2.
public class ArbreOrdannéChaîné<V , C> implements Table<V , C > {
Vous remarquerez que cet algorithme ne s' arrête pas lorsque l'élément recherché est
protected ArbreBinaire< É lément <V, C>> r ; Il l a racine de l ' arbre
trouvé ! Au contraire, il se compo11e de la même façon que s'il recherchait un élément n'ap
protected Camparateur<C> camp;
pa11enant pas à la table. Si l'on sait que la plupart des recherches sont négatives, cette écriture
public ArbreOrdannéCha îné ( Camparateur<C> cmp)
est très efficace dans la mesure où l' alg01ithme ne fait plus qu'une seule comparaison de clé r = ArbreBinaireChaîné . arbreVide;
à chaque itération. D'autre pai1, si un même élément apparaît plusieurs fois dans la table, camp = cmp;
cet algorithme permet de rechercher sa première occurrence, c'est-à-dire l'élément le plus à
gauche dans la table.
21.4 Représentation par un arbre ordonné 273 274 Chapitre 21 • Tables
if ( a . estVide ( ) )
throw new CléNonTrouvéeException ( ) ; Malheureusement, l'ajout en feuille a l'inconvénient majeur de construire des arbres dont
la forme dépend des séquences d'ajouts. Dans le pire des cas, si la suite d'éléments est crois
-0 C c l éDuNoeud = a . racine ( } . clé ( } ; -0 sante (ou décroissante), l'arbre engendré est dégénéré.
0 0
c if ( comp . égal ( cléDuNoeud, clé} } c
::J ::J
0 Il l ' élément recherché est trouvé 0
V V
T"'l
0
return a . racine ( ) ; T""l
0 21 .4.3 Suppression d'un élément
N Il poursuivre l a recherche N
@ return comp . supérieur ( c l éDuNoeud, clé) ? @ L'opération qui consiste à supprimer un élément de l'arbre est légèrement plus complexe .
.....
.c .....
..c
Ol rechercher ( a . sag ( } , clé) : rechercher ( a . s ad ( } , clé} ; Ol Si l'élément à retirer est un nœud qui possède au plus un sous-arbre, cela ne pose pas de
ï::: ï:::
>
a. >
a. difficulté. En revanche, s'il possède deux sous-arbres, il y a un problème : il faut lier ses deux
0 0 sous-arbres à un seul point ! La solution pour contourner cette difficulté est de remplacer
u u
Cette programmation avec deux méthodes est nécessaire pour éviter Je test de compatibilité l'élément à enlever par le plus grand élément du sous-arbre gauche (ou le plus petit élément
de clé à chaque appel récursif. du sous-arbre droit) et de supprimer ce dernier. Dans les deux cas, la relation d'ordre est
conservée.
21 .4.2 Ajout d'un élément
't:/a E Arbreb0, 't:/e E E, et 't:/c E Clé
Il existe plusieurs façons d'ajouter un élément dans un arbre binaire ordonné, mais toutes (8) � a, a = supprimer( arbrevide, c)
doivent maintenir la relation d'ordre. La plus simple consiste à placer l'élément à l'extrémité (9) c < clé(valeur(n)) =? supprimer(< n, g, d > , c) =< n, supprimer(g, c), d >
d'une des branches de l'arbre. Ce nouvel élément est donc une feuille. Les axiomes suivants
définissent l'opération ajouter en feuille.
21.4 Représentation par un arbre ordonné 275 276 Chapitre 21 • Tables
( 1 0) c > clé(valeur(n) ::::}supprimer(< n, g, d > , c) =< n,g, supprimer(d, c) > if ( ! a . sad ( ) . est Vide ( ) ) {
max Arbrebo -t E
Les opérations d'ajout et de suppression que nous venons de présenter ne donnent aucune
( 1 4) max(< n, g, arbrevide >) = valeur(n) garantie sur la forme des arbres qu'elles retournent, et peuvent en particulier, conduire à
( 15) d f. arbrevide, max(< n, g, d >) = max(d) des performances en temps linéaires, semblables à celles des listes lorsque les arbres sont
dégénérés. Pour garantir systématiquement une complexité logarithmique, il faut adapter les
La programmation de l'opération supprimer suit la définition axiomatique précédente. méthodes d'ajout et de suppression d'éléments afin qu'elles conservent l'arbre équilibré.
public void supprimer ( C c l é ) throws CléNonTrouvéeException
if ( ! comp . comparable ( c l é ) )
throw new C l é i ncomparableExcep t i on ( ) ;
21.5 LES ARBRES AVL
r = supprime r ( r , clé) ;
Un arbre AVL 3 est un arbre binaire équilibré tel que pour n'importe quel de ses sous
private ArbreBinaire< É lément <V , C> >
arbres, appelons-le a, la différence de hauteur de ses sous-arbres gauche et droit n'excède pas
supprime r ( ArbreBinaire< É lément<V , C>> a , C clé)
un. Cette propriété est exp1imée par l'inégalité suivante :
if ( a . e stVide ( ) ) Il arbre vide => non trouvée
throw new C l éNonTrouvéeException ( ) ; 1 hauteur(sag(a)) - hauteur(sad(a)) I � 1.
C cléDuNoeud = a . racine ( ) . clé ( ) ;
if ( comp . inférieur ( cl é , c l éDuNoeud) ) La figure 2 1 . l montre un exemple d'arbre à sept nœuds qui possède la propriété AVL :
a . changerSag ( supprimer ( a . s ag ( ) , c lé ) ) ;
el se
if (comp . supérieur ( c l é , cléDuNoeud) )
a . changerSad ( s upprime r ( a . sad ( ) , clé) ) ;
else Il cléDuNoeud clé ::::} es t trouvé
=
21 .5.1 Rotations
Les arbres AVL servent à représenter les arbres binaires ordonnés et garantissent une com
plexité de recherche de l'ordre de O(log2 n). Les opérations d'ajout et de suppression de la
section 2 1 .4 peuvent créer un déséquilibre qui remet en cause la propriété des arbres AVL.
La différence de hauteur des sous-arbres qui violent la propriété est alors égale à 2. Pour
-
& --
conserver la propriété AVL, ces opérations doivent rééquilibrer l'arbre au moyen de deux
----:_ ----------_
types de rotation, les rotations simples et les rotations doubles. Ces rotations doivent bien sûr
conserver la relation d'ordre sur les clés.
Considérons l'arbre (a) de la figure 21.2. Le nœud dont la clé a pour valeur 1 provoque
un déséquilibre à gauche et réclame une restructuration de 1' arbre, appelée rotation simple à
gauche. Cette figure montre l'arbre (b) obtenu après cette rotation.
?
(a) (b)
Les deux rotations simples précédentes ne peuvent résoudre toutes les formes de déséqui
libre. Prenons le cas de l'arbre (a) de la figure 21.5. L'élément de clé 2 viole la propriété AVL
et provoque un déséquilibre dans le sous-arbre droit du sous-arbre gauche de l'arbre. La ré
organisation de l' arbre (b) est effectuée à l'aide d'une rotation double à gauche. Une rotation
double à gauche consiste à appliquer successivement deux rotations simples : une rotation
(a) (b) simple à droite du sous-arbre gauche, suivie d'une rotation simple à gauche de l'arbre.
~
FIGURE 2 1 . 2 Une rotation simple à gauche.
La figure 21.3 montre le cas général d'un déséquilibre dû au sous-arbre le plus à gauche (a),
0
et la rotation simple à gauche qui permet de rééquilibrer l'arbre afin de retrouver la propriété
Œ\@)
AVL (b).
-0 -0
0 0
c c
::J ::J
0 0
V V (a) (b)
T""l T""l
0 0
N N
@ @ FIGURE 21.S Une rotation double à gauche.
.....
..c .....
..c
Ol Ol
ï::: ï::: La figure 21.6 montre la forme générale d'un arbre auquel il faut appliquer une rotation
>
a. >
a.
0 0 double à gauche pour le rééquilibrer.
u u
Comme pour la rotation simple à gauche, il existe une rotation double à droite, symétrique
de la gauche comme le montre la figure 21 .7. Celle-ci consiste en une rotation simple à gauche
(a) (b) du sous-arbre droit, suivie d'une rotation simple à droite de l'arbre.
Les opérations d'insertion et de suppression n'ont besoin que de ces quatre rotations pour
FIGURE 21.3 Rotation simple à gauche.
maintenir la propriété AVL sur un arbre binaire ordonné.
De façon symétrique, un déséquilibre se produit lorsque le sous-arbre le plus à droite est Après l'ajout en feuille d'un nouvel élément, la vérification de la propriété AVL se fait
trop grand. Le rééquilibrage de l'arbre est assuré par une rotation simple à droite, strictement en remontant la branche à partir de la nouvelle feuille insérée jusqu'à la racine de l' arbre.
symétrique à la précédente comme le montre la figure 2 1 .4.
21.5 Les arbres AVL 279 280 Chapitre 21 • Tables
(a) (b)
La mise en œuvre des arbres AVL s'effectue plus commodément à l'aide de structures
FIGURE 21.6 Rotation double à gauche. chaînées. L'implémentation de l'interface ArbreRestructurable se fait par héritage de
la classe ArbreBinaireChaîné. Les quatre méthodes de rotation modifient l 'arbre courant
selon les règles données dans la section précédente.
La mise en œuvre d'une table à l'aide d'un arbre AVL ordonné est décrite par la classe gé private ArbreBinaire< É lément<V, C > >
nétique ArbreAVLChaîné. Puisqu'un arbre AVL ordonné est un arbre ordonné particulier, rééquilibre rG ( ArbreBinaire< É léme n t < V , C > > a )
cette classe hélite naturellement de la classe ArbreOrdonnéChaîné. Ceci nous permettra
en particulier de ne pas avoir à récrire la méthode rechercher. if (hauteur ( a . sag ( ) ) - hauteur ( a . s ad ( ) ) == 2)
Il l ' arbre a n ' es t plus équilibré,
public class ArbreAVLChaîné<V , C > extends ArbreOrdonnéChaîné<V, C> Il le sous-arbre gauche est trop grand
implements Table<V , C> a = hauteur ( a . s ag ( ) . s ag ( ) ) >= hauteur ( a . s ag ( ) . sad ( ) )
? ( ( ArbreRestructurable< É lément<V, C > > ) a ) . rotat ionS impleGauche ( )
public ArbreAVLChaîné ( Cornparateur<C> crnp) { super ( cmp ) ; } sinon double rotation gauche
Il
( (Arbre Re structurable< É lément <V, C > > ) a) . rotat i onDoubleGauche ( ) ;
Il fin classe ArbreAVLChaîné Il calculer les hauteurs des nouveaux
Il sous-arbres gauche et droit de a
La propriété AVL requie11 la connaissance de la hauteur des arbres manipulés. Pour des calculerHauteur ( a . s ag ( ) ) ;
raisons d'efficacité, il n'est pas question de recalculer, par un parcours de l'arbre, cette hau calcule rHauteur ( a . s ad ( ) ) ;
teur chaque fois que cela est nécessaire. Au contraire, nous alJons associer à chaque nœud }
un attribut qui mémorisera la hauteur de l'arbre. Cet attribut est simplement défini dans une Il calculer la nouvelle hauteur de a
classe ÉlémentAVL, héritière de la classe Élément. c a l culerHauteur ( a ) ;
return a ;
public class É lémentAVL <V, C > extends É léme nt<V, C >
{
private Arbr e Binaire< É léme nt<V, C>>
protected int hauteur = O;
rééquilibre rD ( ArbreBinaire< É lément <V, C > > a )
Enfin, un nœud-4 possède quatre sous-arbres, un sous-arbre pour les clés inférieures à sa clé, Si on autorise l'ajout de plusieurs éléments de même clé, ceux-ci ne seront pas nécessai
un autre pour les clés supé1ieures, et deux autres pour les deux intervalles définis par ses rement placés côte à côte dans l'arbre. Une procédure de recherche qui renveITait l'ensemble
trois clés. La figure 2 1 .8 montre un exemple d'arbre 2-3-4, formé de neuf nœud-2, de quatre des éléments de même clé devra alors poursuivre la recherche dans plusieurs sous-arbres à
nœud-3 et de trois nœud-4, dont les clés sont ordonnées selon ces règles. partir du nœud qui contient le premier élément avec la clé recherchée.
I
/
I
I
/
I
/
I
#'
)•
GfJ
7i
8 10 15 20 30 8
1 4
~
ou un nœud-3, l'élément est simplement inséré à sa place, ce qui transforme la feuille en
un nœud-3 ou un nœud-4. En revanche, un problème se pose lorsque la feui11e où ajouter le
R�\
nouvel élément est un nœud-4. Il n'est en effet pas possible de placer un nouveau nœud-2
30
sous ce nœud-4 puisque cela remettrait en cause la forme équilibrée de l'ai·bre.
-0 Une première solution, dite ascendante, consiste à scinder la feuiUe en deux nœud-2 dont -0
0 0 / I
I
c les valeurs sont celles de l 'élément de gauche et de droite du nœud-4, et d'insérer ensuite c /
::J ::J I
0 0
l'élément du milieu du nœud-4 dans son père. L'opération d'ajout peut alors placer le nouvel
!
1
V
T""l V
T""l ;; \
\
0 élément dans ] ' un des deux nouveaux nœud-2 créés en feuille. Le nœud père a été transformé 0 #-/
\
N N )•
en nœud-3 ou nœud-4, selon qu'il était au préalable un nœud-2 ou nœud-3. Si le père était \
@ @ 1
•
.....
..c lui-même un nœud-4, cette opération devra être renouvelée avec son propre père, et ainsi de .....
..c r
Ol Ol 7 13 - \
7 10
ï::: suite jusqu'à la racine si nécessaire. ï:::
( >�:J
> >
'
1
' '
a. a. � •
0 La seconde solution, dite descendante, scinde de façon similaire les nœud-4, mais cette 0 \
I
u u
fois-ci lors de la recherche de la feuille où insérer l'élément. Puisque les nœud-4 sont transfor
8
més en descendant la branche, le père de la feuille nœud-4 dans lequel on insère son élément
central ne peut pas être un nœud-4. La figure 2 1 .9 montre comment le nœud-4 ( 1 5,20,30) est
FIGURE 21.10 Suppression de la clé 45 provoquant une permutation.
scindé lorsque l'algorithme d'ajout le traverse. Lorsque la racine de l' arbre est un nœud-4, sa
scission a pour effet de créer un nouveau nœud-2 et la hauteur de l' arbre est alors augmentée
Si le frère gauche est un nœud-2, la permutation précédente n'est plus possible, et on
de un. Dans le pire des cas, si tous les nœuds de la branche sont des nœud-4, il faut procéder
remplace ] 'élément de l a feuille nœud-2 par l ' élément le plus à droite de son père, puis on
à llog2 nJ + 1 scissions. Notez que la scission d'un nœud-4 est une opération relativement
coûteuse, mais que de façon expérimentale, nous avons mesuré que les ajouts d'éléments dont 5. On peut tout aussi bien choisir l'élément de clé immédiatement supérieure ou égale.
les clés sont tirées de façon aléatoire provoquent en moyenne une scission pour deux ajouts.
21.6 Arbres 2-3-4 et bicolores 287 288 Chapitre 21 • Tables
procède à la fusion de la feuille nœud-2 et de son frère gauche. Le nombre d'éléments du 21 .6.2 Mise en œuvre en Java
nœud père est diminué de un. La figure 2 1 . 1 1 montre un exemple de fusion. Notez que si
le père est lui-même un nœud-2, il faut recommencer cette opération de fusion au niveau du Il est possible de représenter les arbres 2-3-4 par des arbres généraux tels que nous les
père. De proche en proche, elle peut se propager jusqu'à la racine de l'arbre 2-3-4, et dans ce avons définis au chapitre 20. La mise en œuvre de l'interface Table à l'aide d'un arbre 2-3-4
cas la hauteur de l'arbre est diminuée de un. Dans le pire des cas, le nombre de fusions est est décrite par la classe générique Arbre 2 3 4 .
éga1 à la hauteur de l'arbre 2-3-4 plus un. public class Arbre234<V, C > implements Table<V , C > {
protected Arbre<Valeu r 2 3 4 <V, C>> r ; // racine de l ' arbre 2-3-4
protected Comparateur<C> camp;
public Arbre 2 3 4 ( Comparateur<C> cmp)
r=null;
comp=cmp;
!
p.,/'
)• Les éléments et leurs clés, contenus dans chaque nœud de l'arbre, sont rangés dans une
liste ordonnée mise en œuvre à l'aide d'un tableau à trois composants. On les représente
par une classe Valeu r 2 3 4 qui hérite de L i steOrdonnéeTableau. On munit cette classe
7 13 - \
\
d'une méthode particulière de recherche linéaire qui renvoie le rang dans la liste de l'élément
I
I recherché si celui-ci est présent, ou celui de l'élément qui lui est immédiatement supérieur
� ...:- - - - - ' 10 1 3
dans le cas contraire.
V fusion , class Valeur 2 3 4 <V, C> extends Lis teOrdonnéeTableau<V, C>
Valeur 2 3 4 ( É lément <V, C > e , Comparateur<C> cmp ) {
FIGURE 21.11 Suppression de la clé 45 provoquant une fusion.
super ( 3 , cmp) ;
super . a jouter ( l , e ) ;
La propagation de l a fusion souffre une exception dans Je cas où Je nœud à fusionner est
un nœud-4. Par exemple dans l 'arbre de la figure 2 1 . 1 2, la suppression de la clé 8 provoque int recherche r ( Comparateur<C> cmp, C clé) {
la fusion des clés 5 et l 0, et se propage sur le nœud père. La clé 1 3 remplace la clé 8, et doit int i = 1 ;
être fusionnée avec son voisin (i.e. 20 21 35). Mais, ce nœud est un nœud-4 et il n'est pas while ( i <= longueu r ( ) & & cmp . s upérieur ( c l é , ième ( i ) . c lé { ) ) )
possible, par définition, de créer un nœud-5 (i.e. 1 3 20 3 1 45). La solution consiste alors à i++;
transférer le premier élément du nœud-3 dans son père. Si le père était lui-même un nœud-2, return i;
-o on poursuivrait par une fusion avec son voisin (dans l'exemple, 1 3 avec 3 1 et 45). -0
0 0
c c
::J ::J
0 0
V
T"'l V
.-l La méthode rechercher dans u n arbre 2-3-4 est donnée ci-dessous. Dans chaque nœud,
0 )• 0 on effectue la recherche linéaire de la clé. Si cette recherche échoue, r donne le rang du ième
N N
@ @ sous-arbre dans lequel doit se poursuivre récursivement la recherche ; mais si le nœud courant
.....
..c .....
..c
Ol ' Ol est une feuille, la recherche dans l 'arbre s'achève et échoue.
ï::: ' ï:::
>
a.
\ >
a. public É lément<V , C> rechercher ( C c l é ) throws CléNonîrouvéeException
0 0
u 20 31 45 u if (! comp . comparable ( c l é ) )
'
'
throw new CléincomparableExcep t i on ( ) ;
if ( e s tVide ( ) ) // l a table est vide
throw new CléNonTrouvéeExcept i on ( ) ;
return rechercher ( r , c lé ) ;
FIGURE 21.12 Suppression de l a clé 8.
Les méthodes a j outer et supprimer sont plus délicates à programmer, et plus parti 4 6 8
culièrement la seconde. Elles constituent d'ailleurs un excellent exercice de programmation
laissé au lecteur.
800
-0 -0
0 0 7 8
c bicolores avec ceux des arbres 2-3-4. Les équivalents binaires de chaque type de nœud d'un c
::J ::J
0 arbre 2-3-4 sont donnés par la figure 21.13. Les frères dans un nœud-3 ou un nœud-4 sont 0
V
T""l V
T""l
0 coloriés en rouge (liens gras et cercles doubles dans la figure), alors que l'aîné est colorié en 0
N noir (liens maigres et cercles simples). La figure 21.14 montre les représentations 2-3-4 et N
@ @
..... bicolore d'un arbre dans lequel on a inséré les clés de 1 à 8 en ordre croissant. .....
..c ..c
Ol Ol
ï:::
>
À partir de ces règles de correspondance entre nœuds, il est très facile de déduire que la ï:::
>
a. racine d'un arbre bicolore est noire, qu'un nœud rouge est nécessairement le fils d'un nœud a.
0 0
u u
noir, et que toutes les branches de l'arbre possèdent le même nombre de nœuds noirs. Ce
dernier point est dû au fait que toutes les branches d'un arbre 2-3-4 ont la même longueur,
et qu'il ne peut y avoir dans un arbre bicolore qu'un seul nœud noir par nœud 2-3-4. Enfin, FIGURE 21.14 Le même arbre sous forme 2-3-4 et bicolore.
on en déduit que la hauteur d'un arbre bicolore à n éléments est au plus égale au double de
l' arbre 2-3-4 correspondant, c'est-à-dire qu'elle est O(log2 n) . L'ajout d'un élément dans un arbre bicolore se fait en feuille et pose le même problème
La méthode de recherche dans un arbre bicolore est identique à celle dans un arbre binaire que dans un arbre 2-3-4. IJ s'agit de scinder les équivalents binaires des nœud-4, soit lors
standard, et la forme équilibrée de l' arbre garantit une complexité de recherche de l'ordre de de la recherche de la feuille où insérer l'élément (algorithme descendant), soit après l'inser
O(log 2 n ) pour un arbre à n éléments. tion dans une feuille nœud-4 (algorithme ascendant). Pour un arbre bicolore, l'opération de
21.6 Arbres 2-3-4 et bicolores 291 292 Chapitre 21 • Tables
scission consiste simplement à colorier en noir les deux frères, et en rouge l' aîné puisque ce rotation simple gauche
dernier est inséré dans son père. La figure 2 1 . 1 5 montre cette opération de changement de
couleur.
Notez que si l'aîné est la racine de l'arbre, ce nœud devra être colorié à nouveau en noir,
puisque le changement de couleur correspond à la scission d'un nœud-4 à la racine d'un arbre
2-3-4. Souvenez-vous que c'est l'unique cas où la hauteur d'un arbre 2-3-4 est augmentée de
un.
Nous avons vu qu'un arbre bicolore ne peut posséder deux nœuds rouges consécutifs.
L'opération de changement de couleur précédente peut violer cette règle si le père du nœud rotation double gauche
qui a été colorié en rouge est lui-même un nœud rouge. On élimine ce problème par les
rotations simples ou doubles identiques à celles présentées dans la section 2 1 .5 . 1 . La figure
2 1 . 1 6 montre les quatre situations possibles et les rotations à faire. Notez que le grand-père
est par définition nécessairement un nœud noir. Après chaque rotation, le père est colorié en
noir, et le grand-père en rouge.
Pour qu'on puisse supprimer un élément, celui-ci doit être présent dans 1'arbre bicolore.
Comme pour un simple arbre binaire ordonné, il ne sera possible de supprimer qu'un nœud
ayant au plus un sous-arbre. Si le nœud qui le contient possède deux sous-arbres, on rempla
cera l ' élément à supprimer par l'élément de clé immédiatement infé1ieure ou égale 6 situé le
plus à droite dans le sous-arbre gauche. C'est ce nœud avec au plus un sous-arbre qui sera rotation double droite
-0 -0
0 supprimé. Sa suppression consiste simplement à lier son père avec son unique fils gauche. 0
c c
::J ::J
0 0
V Si le nœud suppiimé est rouge, ou si son fils unique est rouge (le nœud supptimé était donc V
T""l T""l
0 noir), on colorie en noir le fils et la suppression est terminée. Notez que cela c01Tespond à 0
N N
@ la suppression d'un élément dans un nœud-3 ou un nœud-4. Dans le cas contraire, le nœud @
.....
..c supp1imé et son fils unique sont noirs. Cela correspond à la suppression d'un nœud-2 dans un .....
..c
Ol Ol
ï::: arbre 2-3-4, que nous avions traitée par une permutation ou une fusion selon la nature de son ï:::
>
a. >
a.
0 frère. Pour un arbre bicolore, nous envisagerons trois cas de figure. 0
u u
Premier cas, le frère du nœud supprimé est noir et possède un fils rouge. Le frère est un
FIGURE 21.16 Suppression de 2 nœu ds rouges consécutifs.
équivalent nœud-3 ou nœud-4, et la suppression était assurée par une permutation. Pour un
arbre bicolore, il faut produire une rotation simple ou double, du fils rouge, du frère et du père
du nœud supprimé. Après la rotation, le nouveau père prend la couleur de l'ancien père, ses un exemple de permutation, et sa c01Tespondance avec un arbre 2-3-4 ; la suppression de
l'élément 30 provoque une rotation double à gauche.
fils gauche et droit sont coloriés en noir, tout comme le fils unique. La figure 2 1 . 1 7 montre
Deuxième cas, le frère du nœud suppdmé est noir et ses deux fils sont noirs, c'est-à-dire
6. Ou, au choix, l'élément de clé inunédiatement supérieure ou égale situé le plus à gauche dans le sous-arbre que le frère est l'équivalent d'un nœud-2. On colorie en noir le fils unique et en rouge le
droit. frère. Si le père du nœud supprimé est rouge, il suffit de colorier ce père en noir, pour obtenir
l'effet d'une fusion dans l' arbre équivalent 2-3-4. La figure 2 1 . 1 8 met en évidence ce cas
21.6 Arbres 2-3-4 et bicolores 293 294 Chapitre 21 • Tables
A
rotation double gauche
A
0 '<'-e, \
0 ��
\
0 0\
):� ,
frère G\ �i frère
•
/ ,
,
:
'
1
'
1
1
rouge@
v fils unique
fils unique
fils
fusion
permutation
Cs)A"
lorsqu'on supprime l'élément 13. En revanche, si le père est noir et qu'il n'est pas la racine
/ ®
de l'arbre, on considère à nouveau les trois cas de figure à son niveau. Ceci consiste à faire
une propagation au niveau du parent. Le nombre de propagations possibles est la hauteur de
l' arbre, O(log2 n ) . frère
-0
0
c
::J
Troisième cas, le frère du nœud supprimé est rouge. Il s'agit de changer sa couleur en
noir pour être en mesure d' appliquer ensuite l'un des deux cas précédents. Pour cela, on
transforme l 'arbre à l ' aide d'une rotation simple. La rotation simple fait intervenir le père du
-0
0
c
::J
:1 0; \@
fils uni -
fils droit
0 0
V
T""l nœud supprimé, le frère, et son fils gauche (si le frère est un fils gauche) ou son fils droit V
.-l
0 dans le cas contraire. Après la rotation, le frère devient le nouveau père, il est colorié en noir, 0
N N FIGURE 21.19 Suppression de 5 dans l'arbre bicolore.
@ et l'ancien père est colo1ié en rouge. Notez qu'après cette transformation, l'application du @
.....
..c .....
..c
Ol deuxième cas n'entraînera pas de propagation puisque le père est rouge. Dans la figure 2 1 . 1 9, Ol 21 .6.4 Mise en œuvre en Java
ï::: ï:::
>
a. le frère droit de l'élément 5 supprimé est rouge. La rotation simple à droite réorganise l'arbre >
a.
0 de telle façon que le nouveau frère 13 est un nœud noir. Le deuxième cas peut maintenant 0 Comme les arbres AVL, les arbres bicolores sont représentés par des arbres restruc
u u
être appliqué. turables chaînés sur lesquels les rotations, simples et doubles, sont possibles. L'im
plantation d'une table à l'aide d'un arbre bicolore est déclite par la classe générique
La complexité de la suppression est O(log2 n ) . Pour chaque suppression, il y a O(log2 n ) ArbreBicoloreChaîné qui hérite de la classe ArbreOrdonnéChaîné et redéfinit
propagations, et au plus une réorganisation et une pennutation, soit une rotation simple, plus ses deux méthodes a j outer et supprimer, la méthode rechercher étant obtenue par
une rotation simple ou double. hélitage.
Les mesures expérimentales d'ajout de clés dans un ordre aléatoire donnent environ deux public class ArbreBicoloreChaîné<V, C> extends ArbreOrdonnéChaîné<V, C >
rotations, une simple et une double, pour cinq insertions (ce qui est un peu mieux que pour implements Table<V , C >
un arbre AVL). Pour les suppressions, le nombre de rotations est semblable à celui des arbres {
AVL, c'est-à-dire une rotation pour quatre suppressions.
21.6 Arbres 2-3-4 et bicolores 295 296 Chapitre 21 • Tables
scinderNoeud ( courant ) ;
Le type de rotation à effectuer est fonction de la structure de l'arbre. La méthode Il OU clé non trouvée
restructurer suivante applique les rotations simples ou doubles comme l'indique la fi if ( ! t rouvée) throw new C l éNonTrouvéeExcept i on ( ) ;
gure 21.16 à la page 292. Il rechercher le maximum du sous-arbre gauche de courant
ArbreBinaire< É lérnent<V , C> > noeudSupp =courant,
private ArbreBinaire< É lément <V, C > > restructurer ( suivant=noeudSupp . s ag ( ) ;
ArbreBinaire< É lément<V, C>> a, ArbreBinaire< É lément<V, C>> p , while ( ! suivant . e stVide ( ) ) {
ArbreBinaire< É lément <V, C>> gp, ArbreBinaire< É lément<V, C>> agp) lesPères . ernpiler ( noeudSupp ) ;
noeudSupp=suivant ; suivant=suivant . s ad ( ) ;
}
if ( p . s ag ( ) == a ) Il noeudSupp désigne l e nœud à supprimer contenant la clé
p = gp . s a g ( ) == p ? Il immédiatement inférieure ou égale à celle de courant
( (ArbreRe structurablee< É lément <V , C> > ) gp) . rotat ionS impleGauche ( ) if ( r == noeudSupp )
( ( ArbreRestructurablee< É lément<V, C > > ) gp) . ro t a t i onDoubleDroit e ( ) ; Il on supprime l a racine, son sous -arbre gauche
else Il (p . sa d ( ) == a) Il est nécessairement vide
p = gp . s ad ( ) == p ? r=noeudSupp . s ad ( ) ; enNo i r ( r ) ;
( ( ArbreRe s t ructurablee< É lément<V , C > > ) gp) . rotat ionSimpleDroit e ( ) return;
( ( ArbreRestruct urablee< É lérnent<V , C > > ) gp) . rotat i onDoubleGauche ( ) ;
Bicolore couleurEltSupp=couleur ( noeudSupp ) ,
Il accrocher l ' arbre rééquilibré à l ' arrière-grand-père s ' i l existe ancienneCouleurCourant=couleur ( courant ) ;
Il sinon, le père est la nouvelle racine de l ' arbre Il mettre l a valeur de noeudSupp dans courant
if (gp == r ) r=p; if ( courant ! =noeudSupp) {
el se courant . racine ( ) . changerValeu r ( noeudSupp . racine ( ) . valeur ( ) ) ;
if ( a gp . s ad ( ) == gp) agp . changerSad ( p ) ; changerCouleur ( courant, ancienneCouleurCourant ) ;
else agp . changerSag ( p ) ;
}
return p ; Il prendre l e fils unique du nœud supprimé,
Il son père et son grand-père
ArbreBinaire< É lérnent<V, C>> f i l sNoeudSupp =
La méthode supprimer est donnée ci-dessous dans sa totalité. La propagation nécessite ( courant == noeudSupp) ? noeudSupp . s ad ( ) noeudSupp . s ag ( ) ;
la connaissance des ascendants du nœud supp1imé. Ceux-ci sont mémo1isés dans une pile père=parent ( l esPères ) ;
lors du parcours de la branche qui conduit au nœud à supptimer. La fonction parent renvoie grandPère=parent ( l esPères ) ;
le père du nœud courant, situé en sommet de pile. Il suppression de noeudSupp =?
Il à quel sous-arbre du père accrocher son fi l s ?
public void supprirner (Object c l é ) throws C léNonTrouvéeException
if (père . s ad ( ) == noeudSupp) père . changerSad ( f i l sNoeudSupp ) ;
if (! cornp . c omparable ( cl é ) )
-0 -0 else père . changerS ag ( fi l sNoeudSupp ) ;
0 throw new CléincomparableExcept i on ( ) ; 0
c c if ( e s t Rouge ( fi l sNoeudSupp) 1 l couleurElt Supp==Bicolore . rouge)
::J ::J
0 if ( e s tVide ( ) ) Il arbre vide ::::} clé non trouvée 0 enNoi r ( fi l sNoeudSupp ) ;
V
T"'l throw new CléNonTrouvéeExcept ion ( ) ; V
T""l
0 0 else Il le nœud supprimé est noir et son fils aussi
N Il l ' arbre n ' est pas vide N do {
@ P i le<ArbreBinaire< É lément<V, C>>> lesPères @
..... ..... ArbreBinaire<É lérnent<V, C>> frère =
..c new P i leChaînée<ArbreBinaire< É lément<V, C>>> ( ) ; ..c
Ol Ol quelFrère ( f i l sNoeudSupp, père ) ;
ï::: ArbreBinaire< É lément <V, C > > courant=r; boolean t r ouvée=false; ï:::
>
a. >
a. if ( e s t Rouge ( frère ) )
0 do 0
u u Il l e frère est rouge =? réorganisation
if ( cornp . égal ( c l é ( courant ) , clé) ) trouvée=true;
ArbreBinaire<É lérnent<V , C>> filsFrè reRouge=
else {
frère == père . s ad ( ) ? frère . s ad ( ) frère . s ag ( ) ;
lesPères . empile r ( courant ) ;
grandPère =
courant = ( comp . supérieurOuÉ gal ( clé ( courant ) , cl é ) ) ?
restructurer ( fi l sFrèreRouge, frère, père, grandPère ) ;
courant . s ag ( ) Il clé du nœud courant � clé
enNo i r ( grandPère ) ; enRouge (père ) ;
courant . s ad ( ) ; Il clé du nœud courant < clé
else { Il l e frère est noir
if ( e s t Rouge ( frère . sag ( ) ) 1 1 e s t Rouge ( frère . sad ( ) ) )
while ( ! t rouvée && ! courant . estVide ( ) ) ;
Il un de ses fils est rouge =? permuta t i on
Il courant désigne l e nœud contenant la clé à supprimer
21.7 Tables d'adressage dispersé 299 300 Chapitre 21 • Tables
ArbreBinaire<É lément<V , C>> filsRouge = Jacques et Monique. L'évaluation d'une fonction d'adressage idéale pour ces clés renvoie,
est Rouge ( frère . sag ( ) ) ? frère . sag ( ) : frère . sad ( ) , par exemple, les sept indices suivants 6, 2, 8, 3, 1, 7 et 10. La figure 21.20 page montre la
a=restructurer ( filsRouge, frère, père, grandPère ) ; table avec les éléments à leur place (pour simplifier, seule la clé est affichée).
changerCouleur ( a , couleur (père ) ) ;
enNoir ( a . sag ( ) ) ; enNoir ( a . s ad ( ) ) ;
enNo i r ( fi lsNoeudSupp ) ;
0
return;
}
Pierre
Il ses deux fils sont noirs
enNoi r ( f i l sNoeudSupp ) ; enRouge ( frère ) ;
Germaine
2
if ( e s tRouge (père ) ) {
Il fusion Maud
3
enNo i r (père ) ;
return;
4
else {
Il propagation si l e père n ' es t pas l a racine
5
if (père == r) return;
f i l sNoeudSupp=père;
6 Louise
père=grandPère;
grandPère=parent ( l e s P è re s ) ; Jacques
7
Paul
8
while (true ) ;
} Il fin supprimer
9
Monique
10
Les collisions sont en général inévitables 8 , et nous verrons à la section 21.7.3 la façon de fait modulo m, ce qui rend de fai t cette fonction plus coûteuse à calculer. Notez qu'en JAVA,
les résoudre. [FGS90] montre en particulier, sous ce1iaines hypothèses probabilistiques, que on peut remplacer avantageusement le produit par 32 par un décalage vers la gauche de cinq
la fonction h disperse unifotmément sur l'ensemble des places de la table, et que la probabilité bits :
que la fonction h renvoie la même place pour cinq clés différentes avoisine zéro. Il en résulte
h = (h<<S) + s . charAt ( i ) ;
que d'une façon générale, la recherche d'un élément dans une table d'adressage dispersé
réclame, au plus, cinq accès quelle que soit la taille de la table. Des résultats statistiques
Il n'existe pas de fonction d'adressage universelle. En fait, elle dépend de l'ensemble des
établis sur divers ensembles de clés et plusieurs fonctions d'adressage semblent confümer de
clés utilisé. Une fonction efficace pour des noms ou des prénoms ne le sera pas nécessaire
façon expérimentale cette probabilité théorique [ASU89].
ment pour les mots réservés d'un langage de programmation, ou pour des codes de sécmité
Le choix de la fonction h est donc primordial. Il doit être tel qu'il minimise le nombre de sociale. Si l'ensemble des clés est connu, il est possible de chercher une fonction sans colli
collisions. Ce choix peut être difficile à faire, surtout si l' on n'a pas de connaissance a priori sion. Il existe d'ailleurs des générateurs de fonctions d'adressage qui, à partir d'un ensemble
sur les valeurs des clés. S'il est mauvais, il peut rendre catastrophique une méthode efficace. de clés connu, fournissent des fonctions sans (autant que faire se peut) collision [Sch90]. En
revanche, si la nature des clés n'est pas connue, on choisira une fonction simple à calculer et
21 .7.2 Choix de la fonction d'adressage dont on essaiera de prévoir le comportement à partir d'un échantillon de clés.
Le choix de la valeur m est également à prendre en considération. La méthode d'adressage
Une bonne fonction d'adressage doit à la fois répartir les clés le plus uniformément sur dispersé est très efficace, mais ce gain de temps se fait au prix d'une certaine perte de place
l'ensemble des places de la table et être simple et rapide à calculer. mémoire par rapport aux méthodes qui ne requièrent que l'espace mémoire strictement né
Toute fonction d'adressage doit d'abord passer à une représentation entière de la clé (sauf cessaire pour mémoriser les éléments. En général, la taille de l a table est fixée à l ' avance, et il
si la clé est déjà sous cette forme) et finir par rendre un indice calculé modulo m pour revenir faudra réserver un nombre de places supérieur au nombre d'éléments prévus. S i les éléments
sur l'intervalle [0, m-1]. En général, la clé est une chaîne de caractères et il est important d'en occupent un espace mémoire important, il ne faudra pas les conserver tels quels dans la table
extraire seulement les caractères significatifs. En particulier, il est inutile de faire intervenir (en JAVA, par exemple, les éléments de la table seront des références).
les caractères blancs lorsqu'ils tem1inent les chaînes. La représentation entière de la chaîne Le taux de remplissage de la table, c'est-à-dire le rapport n/m, avec n le nombre effectif
est obtenue avec des sommes arithmétiques ou, si l'opérateur est disponible dans le langage, d'éléments présents dans la table, a une influence sur la qualité de la fonction d'adressage.
des unions exclusives des caractères qui la composent. Voici quelques méthodes couramment Plus ce taux sera faible, plus le lisque de collisions sera réduit, mais encore une fois au plix
utilisées : d ' une perte de place mémoire.
- prendre quatre caractères au centre de la chaîne ;
Enfin, et ce n'est pas de moindre importance, pour éviter une accumulation de collisions
- prendre les trois premiers et les trois derniers caractères de la chaîne ;
pour les clés dont la représentation entière serait un multiple de m,, la taille de la table doit
- additionner les entiers obtenus en regroupant les caractères par blocs de quatre carac-
être un nombre premier.
tères ;
- calculer des suites de la forme ho, hi = ahi -1 + ci.
-0 Pour cette dernière façon de procéder, la suite est très simple à calculer et à programmer. -0 21 .7.3 Résolution des collisions
0 0
c D' ailleurs, c'est avec une fonction d'adressage de ce type que la table de la figure 21.20 a été c
::J ::J
0 0 Il n'est malheureusement pas toujours possible d'éviter les collisions, et l'opération ajou
V produite. Nous en donnons la programmation en JAVA. V
T""l T""l ter(t, e) devra quand même insérer dans la table l'élément e, même si sa clé entre en collision
0 0
N int fctAdressage ( String s ) { N avec celle d'un autre élément déjà présent dans la table. Il existe deux grandes techniques
@ int h=O; @ pour résoudre les collisions. La première utilise des fonctions secondaires, la seconde chaîne
.....
..c .....
..c
for ( int i=O ; i < s . length ( ) ; i++)
Ol Ol les collisions dans la table ou hors de la table.
ï::: h = 32*h+ s . charAt ( i ) ; ï:::
>
a. >
a.
0 return Math. abs ( h ) % m ; 0
u u Utilisation de fonctions secondaires (adressage ouvert)
Cette méthode consiste à appliquer, en cas de collision, une seconde fonction d'adressage
JAVA ne vérifie pas les dépassements de capacité des opérations arithmétiques. La valeur
pour obtenir une nouvelle place dans la table. Si cette nouvelle place provoque à nouveau
de la variable h à la fin de boucle peut donc être négative, voilà pourquoi il faut prendre sa
une collision, on applique une troisième fonction d' adressage, et ainsi de suite jusqu'à ce
valeur absolue. Pour les langages qui font la vérification, le calcul dans la boucle devra être
qu'il n'y ait plus de collision ou que le nombre de fonctions secondaires ait été épuisé. Plus
8. Le célèbre paradoxe du jour anniversaire affirme que si plus de vingt-trois personnes sont réunies dans une formellement, cette méthode dispose d'un ensemble de fonctions hi tel que :
même salle, il y a près d'une chance sur deux que deux d'entre elles soient nées le même jour.
hi(c) = (h(c) + f(i)) mod m, avec f(O) = 0
21.7 Tables d'adressage dispersé 303 304 Chapitre 21 • Tables
hi(c) (h(c) + i) mod m Notez que l'algorithme s'arrête lorsque i 2 � m,. Dans l'hypothèse où m est premier, alors
seulement rn/2 places auront été testées. Toutefois, ceci est assez rare en pratique et n' arrive
Cette méthode est particulièrement simple, puisqu'elle consiste à consulter h( c) + 1, h( c) + que lorsque le taux de remplissage de la table est élevé.
2, h(c) + 3, . . ., c'est-à-dire les places de distance i par rapport à h(c). L'algorithme échoue Une troisième méthode, appelée adressage double, consiste à disposer d' une seconde fonc
et s' arrête lorsque i = m puisque toutes les places de la table auront été consultées en vain. tion d'adressage h'. La fonction f est telle que f(i) = i h'(c), c'est-à-dire que les fonctions
Cette méthode n'est toutefois pas très bonne car les séquences de places calculées pour deux secondaires sont de la forme :
clés différentes sont identiques, et elle provoque des accumulations d'éléments autour des
clés qui ont été placées du premier coup, sans collision. [Knu73] donne une approximation hi(c) (h(c) + i h'(c)) mod m
du taux de collision pour cette méthode. Pour une recherche positive, ce taux est environ égal
à ( 1 - d/2)/(1 - d), où d est le taux de remplissage de la table. Cette méthode évite les accumulations et, contrairement aux deux techniques précédentes,
Une variante de cette première méthode, appelée quadratique, doit son nom au fait que la produit pour deux clés différentes en collision des suites de places distinctes. Pour un adres
fonction .f a la forme d'une équation du second degré. Les fonctions secondaires sont telles sage le plus uniforme possible sur toute la table, h' (c) doit être différent de 0 et premier avec
que : m pour tout c. KNUTH [Knu73] suggère de choisir h'(c) = 1 + (c mod (m - 2)), avec m
et m - 2 premiers. Il indique également que le taux de collision pour une recherche positive,
hi(c) (h(c) + a i2 + bi) mod m si les deux fonctions sont indépendantes et uniformes, est environ -d- 1 log(l - d).
Cette méthode évite le problème d'accumulation de la méthode linéaire, mais les sé
quences de places produites pour deux clés distinctes en co11ision restent identiques. La Chaînage externe des collisions
-0 -0
0 qualité de cette méthode dépend du choix de a et de b. Le meilleur choix pour éviter de 0
c c Avec cette méthode, et contrairement à la technique précédente, on ne cherche pas dans
::J ::J
0 chevaucher, autant que faire se peut, les suites de place renvoyées par d'autres clés, est de 0
V V la table une nouvelle place pour les éléments dont les clés entrent en collision. Ceux-ci sont
T""l prendre a et b premier avec m. Notez qu'il sera toujours possible d'ajouter un élément si la T""l
0
N
0
N
rangés dans des structures dynamiques en dehors de la table. Ces structures peuvent être des
table est au plus à moitié remplie. La plupart du temps, on choisit a = 1 et b = 0, car les
@ @ listes ou des arbres. Le coût de cette méthode est celui de la fonction d'adressage primaire,
..... fonctions secondaires sont alors très simples à calculer avec une relation de récurrence qui .....
..c ..c plus celui de la recherche, de 1'ajout ou de la suppression de 1'élément dans la structure de
Ol évite l'élévation au carré. Nous donnons la programmation en JAVA de la méthode rechercher. Ol
ï::: ï::: données choisie. En général, le nombre de collisions étant faible (pas plus de cinq pour une
>
a. >
a.
0 / * * Rôle : recherche dans une table d' adressage dispersé avec 0 fonction primaire uniforme), une simple liste linéaire est tout à fait acceptable, et conserve
u u
* résol u t i on des collision s par fonctions secondaires à la méthode d' adressage dispersé toute son efficacité. Notez que pour une place donnée, le
* méthode quadrat i que : i. 2 nombre moyen d'éléments chaînés est n/m.
*/
public É lément<V, C> rechercher ( C c l é ) throws CléNonTrouvéeException Cette méthode a l'avantage d'être très simple à mettre en œuvre, et pe1met une gestion plus
if (! comp . comparabl e ( c l é ) ) efficace de la mémoire. En effet, si la place mémoire est limitée 9, il est possible de réduire la
throw new Cl é incomp a r abl e Except i o n ( ) ; taille de la table sans que cela pénalise trop le taux de collision.
int d=l,
h=fctPrimaire ( c lé) ; 9. La taille des mémoires des ordinateurs actuels ne cessent d'augmenter, mais la taille des données manipulées
aussi. 1.
21.7 Tables d'adressage dispersé 305 306 Chapitre 2 1 • Tables
Néanmoins, si pour une raison ou une autre, la fonction d'adressage provoquait un nombre
0 0
de collisions anormal sur une même place dans la table, la méthode d'adressage dispersé
perdrait alors tout son intérêt. Plus encore qu'avec l'utilisation de fonctions secondaires, il est Pierre Pierre
important d'évaluer sur un échantillon de clés la dispersion de la fonction primaire utilisée.
Dans la méthode a j outer donnée ci-dessous, le chaînage externe est fait à l' aide d'une 2 2
J D
m Jacques m-2 Louise
Il la place est libre ::::? créer une nouvelle l i s t e
table [ h ) =new L i steNonOrdonnéeChaînée<V , C> (comp ) ;
m+1
Louise
m-1 Jacques
-
Il ajouter e dans la l i s t e table{h] zone secondaire I ..,__,._
1
tabl e [ h ] . a j out er ( e ) ; 1
1
1
m+p
�
Donnez l'algorithme d' une recherche d'un mot réservé. Quelle en est sa complexité ? Puis, 8
écrivez un algorithme qui construit la table des longueurs à partir de la table des mots réservés.
I
I
A
Exercice 21.4. Donnez une implémentation de l'inte1face Table avec une liste non ordon
?
'
?
'
@ @
Exercice 21.5. Refaites l'exercice précédent avec une liste ordonnée.
(\ (0\
Exercice 21.6. Une recherche dichotomique peut être utilisée pour rechercher la place d'un
élément à insérer dans une table ordonnée. Modifiez les algorithmes de recherche dichoto
mique afin qu'il renvoie le rang de l'élément à insérer.
Exercice 21.7. La recherche dichotomique coupe en deux parties égales l'espace de re 0 0 0
cherche sans se préoccuper de la nature de la clé recherchée. Lorsque vous recherchez un
mot dans un dictionnaire, vous l'ouvrez vers le début, le milieu ou la fin selon que le mot FIGURE 21.23 Coupure d'un arbre par rapport à la clé 8.
recherché commence par une lettre proche du début, du milieu ou de la fin de l'alphabet. La
méthode de recherche par interpolation met en œuvre cette technique et améliore la méthode Donnez la signature de cette opération. Écrivez les axiomes qui donnent fom1ellement la
de recherche dichotomique. L'idée est d'estimer l'endroit où la clé pourrait se trouver sur sémantique de cette fonction, puis programmez-la en JAVA.
la connaissance des valeurs disponibles. La figure 21 .22 montre comment calculer le rang
estimé inter entre les rangs gauche et droit d'une liste l. Notez que l'élément recherché doit Exercice 21.10. L'opération ajouter dans un arbre binaire ordonné (donnée page 273)
nécessairement appartenir à l'intervalle [ièm e ( l 1)), ièm e ( l longueur(l))].
, , ajoute un élément en feuille. Utilisez l'opération coupure de l'exercice précédent pour définir
une opération d'ajout à la racine. Vous écrirez les axiomes de cette opération, et vous la
clé(ième(l,droit)) programmerez en JAVA. Quelle est sa complexité dans le pire des cas ?
Exercice 21.11. Écrivez un programme qui compte le nombre d'occurrences des mots lus
sur l'entrée standard. Le programme affichera la liste des mots lus en ordre alphabétique
avec, pour chaque mot, son nombre d'occun-ences. Pour mémoriser les mots et leur nombre
c = clé(ième(l,inter)) ? d'occurrences, vous utiliserez un arbre binaire ordonné.
Exercice 21.12. On définit un arbre quaternaire ordonné, le type abstrait qui permet de
ranger des valeurs appartenant à un espace à deux dimensions où chaque dimension est mu
clé(ième(l,gauche))
nie d'une relation d'ordre. Chaque nœud est le père de quatre sous-arbres qui représentent
une partition de l'espace à deux dimensions, auquel appaitient la valeur du nœud, en quatre
-0 -0 quadrants. Si nous appelons les deux dimensions respectivement latitude et longitude, nous
0 0
c c poun-ons appeler les quatre quadrants, respectivement, sud-ouest (SO), nord-ouest (NO), sud
::J ::J
0 0
V V est (SE) et nord-est (NE).
T""l T""l
0 0 Ainsi, étant donnée une valeur particulière qui occupe un nœud de l' arbre quaternaire,
N gauche inter droit N
@ @ son sous-arbre sud-est ne comprend que des valeurs dont la latitude est inférieure ou égale
..... .....
8
..c ..c à la sienne et la longitude stlictement supéiieure à la sienne. Remarquez la dissymétrie des
O'I FIGURE 21.22 Le rang inter est égal à gauche +6.. Ol
ï::: ï::: relations, nécessaire pour que l'on puisse placer sans hésitation des points distincts mais qui
>- >
a.
Écrivez un algorithme de recherche par interpolation. Comparez cette méthode avec la 0 ont une coordonnée en commun.
u
recherche dichotomique. Définissez les axiomes de la fonction rechercher qui recherche dans un arbre quaternaire
Exercice 21.8. Écrivez de façon itérative 1 ' algorithme de recherche dans un arbre binaire ordonné un élément représenté par sa clé. Celle-ci est définie par ses deux coordonnées de
ordonné. longitude et de latitude.
Exercice 21.9. L'opération de coupure divise un arbre binaire ordonné en deux arbres bi Exercice 21.13. Écrivez les méthodes a j outer et supprimer pour une table représentée
naires ordonnés distincts par rapport à une clé c. Le premier arbre est tel que toutes les clés par un arbre 2-3-4. Un consei] : écrivez la méthode supprimer de façon itérative, et pensez
de ses nœuds sont inférieures ou égales à la clé c, le second arbre est tel que les clés de ses à mémoriser tous les nœuds traversés, pendant la recherche de la clé à supprimer.
nœuds sont strictement supérieures à c. La figure 21 .23 donne un exemple de coupure.
21.8 Exercices 309
Exercice 21.14. Modifiez la méthode rechercher dans une table implémentée à l'aide
d'un arbre 2-3-4, afin qu'elle renvoie tous les éléments de même clé.
Exercice 21.15. Donnez l'algorithme de la suppression d'un élément dans une table
d' adressage dispersé qui utilise des fonctions secondaires, d'une part, dans le cas de la mé
thode linéaire, d'autre part dans le cas de la méthode quadratique.
Exercice 21.16. Lorsque le taux de remplissage d'une table d'adressage dispersé ouvert
devient trop important, un prochain ajout peut considérer la table pleine. Il est alors possible
de réorganiser (en anglais rehashing) les éléments dans une nouvelle table plus grande (e.g.
deux fois la taille de la table initiale). Modifiez l'algorithme de l' opération ajouter pour mettre
en œuvre cette technique lorsque le taux de remplissage dépasse une certaine valeur.
Exercice 21.17. Les correcteurs orthographiques utilisent des tables d'adressage dispersé
pour conserver les mots d'un dictionnaire. Une technique utilisée pour réduire l'encombre
ment en mémoire du dictionnaire est de ne conserver qu'une table de bits, initialisée à 0, et
telle que pour tout mot rn du dictionnaire, on calcule t[h(m)] +--- 1 .
Combien d'éléments doit posséder cette table ? Quelle est l a réduction de place en mé
moire obtenue ?
Si pour un mot rn' quelconque, t[h(m')] = 0, que peut-on en déduire ? Et que peut-on
déduire si t[h(m')] = 1 ? Quelle est la probabilité d'en-eur pour une table de longueur n ?
Programmez un petit con-ecteur orthographique avec cette méthode. Il est possible d'amé
liorer la méthode en calculant plusieurs fonctions d' adressage indépendantes. On teste la
présence d'un mot dans la table en vérifiant que tous les t[hi(m')] sont égaux à 1 .
-0 -0
0 0
c c
::J ::J
0 0
V
T"l «:!"
T""l
0 0
N N
@ @
.....
..c ......
..c
Ol Ol
ï::: ï::
>
a. >-
0..
0 0
u u
312 Chapitre 2 2 • Files avec priorité
la file, et enfin l'opération supprimer retire l'élément le plus prioritaire de la file. Les axiomes
du type abstrait sont laissés en exercice.
Nous allons voir maintenant que les opérations d'ajout et de suppression d'éléments, qui
demandent une réorganisation du tas, sont elles aussi performantes. Elles ne nécessitent que le
parcours d'une branche de l'arbre binaire. Comme l'arbre est parfait, la hauteur d'une branche
est égale au plus à Llog2 nJ , où n est le nombre d'éléments dans le tas. La complexité des
opérations d'ajout et de suppression est, au pire, égale à O(log2 n) .
r
22.3.2 Ajouter
L'opération d'ajout consiste, dans un premier temps, à placer le nouvel élément et sa prio
rité en feuiJle, celle qui suit la dernière feuille de ]'arbre. Le nouvel élément n'est pas né
r
cessairement correctement placé et l'ordre partiel sur les éléments du tas n'est peut-être plus
respecté. Il faut alors réordonner le tas, et chercher une bonne place pour le nouvel élément.
0
FIGURE 22.1 Un arbre partiellement ordonné.
-0 -0
0 FIGURE 22.2 L'arbre de la figure 22.1 sous forme de tas. 0
c c
::J ::J
0 0
V V
0
T""l Un tas offre une représentation très efficace du type abstrait File-Priorité, puisqu'avec T""l
0 0
N N
@ une telle structure, la complexité des opérations est 0(1) ou O(Iog2 n). @
.....
..c .....
..c
Ol Par la suite, et pour simplifier, seules les valeurs des priorités apparaîtront dans les figures . Ol
ï::: Ces priorités sont des entiers, et plus l'entier sera grand, plus la priorité sera forte. ï:::
>
a. >-
a.
0 0
u u
22.3.1 Premier FIGURE 22.3 Ajout d'un élément de priorité 27.
Puisque l'élément le plus p1ioritaire est toujours placé à la racine de l'arbre, l' accès à sa Algorithme a j outer ( t , e, p)
valeur est très efficace, et sera toujours en 0 ( 1 ) . a f-- cons ( ( e , p ) , 0 , r/J )
Algorithme premier ( t ) mettre a après l a dernière feuille du t a s t
{Rôle : renvoie l ' élément l e plus priori t a i re du t a s t } réordonner-tas l ( t , a )
rendre racine ( t )
22.3 Représentation avec un tas 315 316 Chapitre 22 • Files avec priorité
nouvel élément avec la valeur de ses pères sur la branche qui remonte jusqu'à la racine du tas. '
'
1
Tant que sa valeur de priorité est supérieure à celle du père, on fait 1' échange. L'algorithme \
s' arrête quand un père possède une valeur de priorité supérieure ou égale, ou bien lorsque 13
la racine du tas est atteinte. Dans ce dernier cas, le nouvel élément est placé à la racine du
tas. La figure 22.3 page 3 1 4 montre l'ajout d'un élément de priorité 27. Après avoir été placé
en feuille, l'élément est successivement échangé avec les éléments de priorité 13 et 25. Sa
position finale est celle du fils gauche de la racine.
L'algorithme de réordonnancement est décrit récursivement comme suit :
Algorithme réordonne r-tasl ( t , a )
si a�t alors
si priorit é ( racine ( a ) ) > priori t é ( racine (père { a ) ) ) alors
échange r (valeur ( racine ( a ) ) , valeur (racine (père ( a ) ) ) )
réordonner-t a s l ( t , père ( a ) )
finsi
finsi
22.3.3 Supprimer
public V premi e r ( ) throws FileVideExcept ion; I * * Rôle : renvoie l a valeur du \noeud du sag du \noeud d' indice i *I
public void supprimer ( ) throws F i l eVideException; private Élément <V, P> sag ( int i )
public void a j out er ( V e, P p) ; return ième ( 2 * i ) ;
38 25 1 l
30 12 13 9 5
public V premi e r ( )
if
throws FileVideException {
( e s tVide ( ) ) throw new F i l eVideExcept i on ( ) ;
return racine ( l ) . valeur ( ) ;
2 3 4 5 6 7 8 9
partiel sur les éléments du tas en fonction de leurs priorités. Il réordonnancement du tas
while (i>l && cmp . supérieu r ( racine ( i ) . c lé ( ) , père ( i ) . c lé { ) ) )
Les valeurs des éléments de la file et leurs priorités seront conservées dans des objets échanger ( i , i / =2 } ;
de type Élément, défini à la page 265. L'attribut clé représente la priorité de l'élément.
L'élément qui possède la clé de valeur la plus grande possède la pri01ité la plus forte.
public class TasListeTableau<V , P > extends ListeTableau<É lément <V , P > >
Pour supprimer l'élément prioritaire de la file, on affecte au nœud de la racine, la valeur du
implements FilePriorité<V, P > nœud de rang longueur ( ) , et on supprime ce dernier élément de la liste. Le réordonnan
-0 { -0 cement du tas s' éc1it également de façon itérative, et traite, en partant de la racine, les nœuds
0 0
c protected Comparateur<P> cmp; c qui ne sont pas des feui1les (c'est-à-dire les nœuds de rang 1 à longueur ( ) / 2 jusqu'à ce
::J ::J
0 0 que la position finale soit trouvée.
V
T"'l V
T""l
0 0 public void supprimer ( } throws FileVideException
N N
@ Afin d'accroître la lisibilité des méthodes qui implémentent les opérations du type abstrait @ if ( e stVide ( ) )
.....
..c .....
..c throw new F i leVideException ( ) ;
O'I File-Priorité, nous définissons les méthodes privées suivantes, qui renvoient, respective Ol
ï::: ï::: Il l a file n ' est pas vide changer l a valeur de l a racine
>
a. ment, la valeur (de type Élément) du nœud courant, du nœud de son père et de ses fils >
a.
0 0 affect er ( l , ième ( longueur ( ) ) ) ;
u gauche et droit. u Il supprimer la dernière feuille
I * * Rôle : renvoie l a valeur du \noeud d' indice i *I super . supprime r ( l ongueur ( ) ) ;
private É lément <V, P> racine ( int i ) { int i = l , lg=longueur ( ) ;
return ième ( i ) ; Il réordonnancement du tas
while ( i <=l g / 2 ) {
/ * * Rôle : renvoie la valeur du \noeud du père du \noeud d ' i ndice i */ Il le nœud i possède a u moins un fils
private É lément <V, P > père ( int i ) { int f i l sMax =
return ième ( i / 2 ) ; ( 2 * i == lg 1 1 cmp . supérieur ( s a g ( i ) . c l é ( ) , s ad ( i ) . clé ( ) ) ) ?
22.4 Exercices 319
22.4 EXERCICES
Exercice 22.1. Proposez un alg01ithme en 0( n log2 n) qui trouve la ke plus grande valeur
d'une suite quelconque d'entiers lus sur l'entrée standard.
Exercice 22.2. Proposez un algorithme de recherche d'un élément dans un tas. Quelle est
sa complexité ? Est-ce qu'un tas est adapté à ce type d'opération ?
Exercice 22.3. Programmez une implémentation de l'interface FilePriorité à l'aide
d'un tas représenté par une structure chaînée.
Exercice 22.4. Une généralisation des tas consiste à les représenter par des arbres quel
conques. L'arbre est toujours partiellement ordonné, mais le nombre de fils de chaque nœud
n'est plus limité à deux. Donnez les algorithmes des opérations ajouter et supprimer pour un
tas dont le nombre fils de chaque nœud est borné par m. Indiquez la complexité de ces opéra
-0 -0
0 tions, puis comparez les avantages et les inconvénients d'un tas binaire et d'un tas d'ordre m. 0
c c
::J ::J
0 0
V
T""l «:!"
T""l
0 0
N N
@ @
.....
..c ......
..c
Ol Ol
ï::: ï::
>
a. >-
0..
0 0
u u
322 Chapitre 23 • Algorithmes de tri
sur les prénoms (la clé secondaire). La complexité de la clé ne modifie pas les algorithmes de
tri, seule la méthode de comparaison des clés change.
'Vl E .Ciste, 'Vi, j E [1, longueur(l)], 'Ve E E 1 53 914 230 785 121 350 567 631 11 827 180
(1) longueur(échanger(l, i,j)) = longueur(l) 11 1 91 4 2 3 0 785 121 350 567 631 53 827 180
(2) ième(éch anger ( l , i, j), i) = ième (l j) ,
11 53 1 23 0 785 121 350 567 631 914 827 180
11 53 121 1 7 85 230 350 567 631 914 827 180
(3) ième(échanger(l, i , j ) , j ) = ième(l, i )
11 53 121 180 1 23 0 350 567 631 914 827 785
(4) longueur( affecter ( l i, e)) = longueur (l )
,
11 53 121 180 230 1 350 567 631 914 827 785
(5) tir E [1 , longueur ( l)] et r =/= i, i ème ( affecter ( l , i , e ) , r ) = ième(l, r) 11 53 121 180 230 350 1 5 67 631 914 827 785
(6) r = i, ième ( affecter ( l , i, e) , r) = e 11 53 121 180 230 350 567 1 631 914 827 785
11 53 121 180 230 350 567 631 1 914 827 785
Afin que ces algorithmes de tri soient efficaces, l'implantation de la liste devra employer
11 53 121 180 230 350 567 631 785 1 827 914
un tableau pour un accès direct aux éléments de la liste. Les éléments à trier sont de type
Élément défini à la page 265. Les opérations de comparaisons spécifiques au type des clés
des éléments sont passées en paramètres de la méthode de tri. Ainsi, nous définirons une À la ,ie étape, la clé courante est à droite de la barre verticale, et le minimum de la sous-liste
classe génétique TrieurDeLi stes, hétitière de ListeTableau, paramétrée sur le type est souligné. Cet algorithme de tri s'écrit comme suit :
des valeurs et des clés à trier. Cette classe et les en-têtes des méthodes de tri auront la forme Algorithme SélectionDirecte ( 1 )
dite, l'algorithme doit procéder à la création d'un tas initial, à partir de tous les éléments de 567 180 350 121 11 5 3 1 2 30 631 785 827 914
la liste. Nous décrirons cette première phase un peu plus loin. 350 180 230 121 11 1 53 567 631 785 827 914
Voyons comment procède ce td à la ie étape. À ce moment du tri, la liste est organisée 230 180 53 121 I ll 350 567 631 785 827 914
comme l'indique la figure 23.2. Tous les éléments compris entre le rang i + 1 et n sont or 180 121 53 I ll 230 350 567 631 785 827 914
donnés de façon croissante et possèdent des clés supé1ieures ou égales à celles des éléments 121 11 I TI 180 230 350 5 6 7 631 785 827 914
du tas, compris entre le rang 1 et i. L'élément de rang 1 possède la clé la plus grande du tas. 53 I ll 121 180 230 350 567 631 785 827 914
11 53 121 180 230 350 567 631 785 827 914
n
Pour construire le tas initial, il faut ordonner de façon partielle tous les éléments de la
sous-liste de ième(1) à ième(i)
sous-liste de ième(i+ 1 ) à ième(n) triée liste. Chaque élément correspond à un nœud particulier d'un arbre parfait. On applique l' al
organisée en tas
gorithme de réordonnancement réordonner-tas2 sur chacun des nœuds (qui ne sont pas
des feuilles), en pa1tant des nœuds de rang longv,eur(l)/2 jusqu'à la racine, c'est-à-dire le
FIGURE 23.2 Tri en tas à l'étape i . nœud de rang 1 . Pour chaque nœud i, les éléments compris entre les rangs i et longueur(l)
sont réordonnés en tas. Le tas initial est construit comme suit :
Le prochain élément à placer au rang i est l'élément dont la clé est la plus grande sur Algorithme création du t a s
l'intervalle [1, i] . Sa recherche dans le tas est immédiate puisque c'est le premier de la liste. pourtout i de longueur ( l ) / 2 à 1 faire
La complexité de la recherche est donc égale à 0(1). L'élément de rang i prend ensuite la réordonner-t as2 ( 1 , i, longueur ( l )
place du premier élément de la liste qui est mémorisé. Le tas, formé des éléments compris finpour
entre les rangs 1 et 't 1, est alors réordonné par l' algorithme donné à la page 315. Enfin,
-
l'élément de clé maximale mémorisé est placé au rang i. L'algorithme complet du tri en tas
est le suivant : Écriture en JAVA
Algorithme triEnTas ( 1 )
Nous donnons, ci-dessous, l'écriture complète du tri en tas en JAVA. La méthode
{Rôle : trie l a l i s t e 1 en ordre croissant des clés }
réordonnerTas réordonne le tas depuis le rang i jusqu'au rang n, en fonction de l'élément
création du t a s i n i t i a l
{la liste 1 est organisée en tas} de rang i, Le paramètre c donne les opérations de comparaison sur les clés des éléments. Les
pourtout i de longueur ( l ) à 2 faire méthodes racine, sag et sad sont celles données à la page 317.
{Invariant : l a sous-li s t e de ième (l , i + l ) à i ème (l, longueur (l) ) } private void réordonnerTa s ( int i, int n , Cornparateur<C> c )
{est triée e t VkE [ l , i - 1 ] , Vk' E [i , longueur {l) ] , while ( i <=n / 2 ) {
clé (ième (1, k) ) �clé (ième (1, k' ) ) } Il le nœud i possède a u moins un fils
max = ième ( l , 1) int f i l sMax = (2*i == n
I l c . supérieur ( s ag ( i ) . clé ( ) , sad ( i ) . c lé ( ) ) ) ?
ième ( l , l ) +- ième ( l , i )
-0 {réordonner l e tas entre 1 et i - 1 ) -0
0 0 Il un seul fils gauche ou l e fils gauche
c c
::J réordonner-t a s 2 ( 1 , 1, i-1) ::J Il a la priorité maximale
0 0
V {max est le prochain ième (l, i ) J V 2*i
T""l ième ( l , i ) +- max
T""l
0 0 Il l e fils droit a l a pri ori t é maximale
N N
finpour
@ @ : 2*i+l;
.....
..c .....
..c if ( c . inférieur ( racine ( i ) . c lé ( ) , racine ( fi lsMax) . c lé ( ) ) )
Ol Ol
ï::: L'étape initiale de création du tas à partir de la liste de référence construit la liste : ï::: Il l e nœud i a la priori t é maximale
>
a. >
a.
0 0 échange r ( i , f i lsMax ) ;
u u i = f i l sMax;
914 827 567 785 180 350 230 631 11 121 53
Le déroulement de la méthode de tri en tas, appliquée à cette liste produit les dix étapes else return;
suivantes :
Il fin réordonnerTas
914 827 567 785 180 350 230 631 11 121 1 53
827 785 567 631 180 350 230 53 11 1 12 1 914 Enfin, la méthode principale du tri en tas est une traduction directe de l'algorithme. Elle
785 631 567 121 180 350 230 53 I li 8 2 7 914 mémorise dans une variable temporaire max le maximum du tas situé au rang 1 de la liste.
631 180 567 121 11 350 230 1 53 785 827 914 Celui-ci est remplacé par la valeur de l'élément de rang i. Le tas est alors diminué d'un
23.2 Tris internes 327 328 Chapitre 23 • Algorithmes de tri
élément, et il est réordonné entre les rangs 1 et i - 1 . Enfin, la valeur de max est affectée au 23.2.3 Méthodes par insertion
rang i, sa place définitive.
Dans ces méthodes, à la ie étape du tri, les i 1 premiers éléments forment une sous
-
La liste est parcourue à partir du rang 2 jusqu'au dernier. À 1 'étape i, les i 1 premiers -
éléments forment une sous-liste triée. Un rang d'insertion du ie élément est recherché de
façon séquentielle entre le rang i et le premier rang. Il est tel que la sous-liste reste triée. Lors
Complexité du tri en tas
de l'insertion, l'élément de rang i est mémorisé dans une variable auxiliaire et un décalage
La complexité du tii en tas est égale à la complexité de la création du tas initial, plus celle d' une place vers la droite des éléments compris entre le rang j et le rang i 1 est nécessaire. -
du tri proprement dit. Nous allons nous intéresser à la complexité dans le pire des cas. Pour améliorer l'efficacité de l'algorithme, ce décalage doit être fait pendant la recherche du
La complexité de la création du tas est égale à la somme des coûts de réordonnancement rang d'insertion.
de chacun des sous-arbres du tas. Puisque chaque élément du tas est la racine d'un sous-arbre Le tri par insertion appliqué à la liste de référence produit les étapes suivantes :
parlait, il y a n sous-arbres à réordonner. On définit le coût total du réordonnancement comme
la somme des coûts des réordonnancernents de chacun des sous-arbres, exprimé en nombre 53 1 914 230 785 121 350 567 631 11 827 180
d'itérations effectuées dans la méthode réordonnerTas. 53 914 1 23 0 785 121 350 567 631 11 827 180
53 230 914 1 78 5 121 350 567 631 11 827 180
Le coût par nœud est borné par la profondeur du sous-arbre dont il est la racine. Pour les
53 230 785 914 1 121 350 567 631 11 827 180
nœuds de rang compris entre n/2 + 1 et n, le coût est nul puisque ce sont des feuilles. Pour
"O -0 53 1 2 1 2 3 0 785 914 1 35 0 567 631 11 827 180
0 les nœuds de rang compris entre n/4 + 1 et n/2, le coût est inférieur ou égal à 1. Pour les 0
c c 53 121 230 350 785 9 1 4 1 5 67 631 11 827 180
::J nœuds de rang compris entre n/8 + 1 et n/4, le coût est inférieur ou égal à 2. Et ainsi de suite ::J
0 0 53 121 230 350 567 785 914 1 631 11 827 180
V
T""l jusqu'à la racine du tas, où le coût est inférieur ou égal à log2 (n + 1 ) - 1 . Le coût maximal V
T""l
0 0 53 121 230 350 567 631 785 914 I li 8 2 7 180
N est calculé pour un arbre paifait complet, c'est-à-dire dont le dernier niveau possède toutes N
11 5 3 121 2 3 0 350 567 631 785 914 1 827 180
@ ses feuilles. On voit qu'il est égal à la somme : @
.....
L
..c .....
..c 11 53 121 230 350 567 631 785 827 914 1 180
Ol log2(n+l) Ol 11 53 1 2 1 180 230 350 567 631 785 827 914
ï::: ï:::
> >-
a.
0 S= 2 i - 1 log2(n + 1 ) - i = n - log2(n + 1 ) a.
0
u u L'algorithme du tri par insertion séquentielle est l e suivant :
i=l
Algorithme Triinsertion ( 1 )
La somme S étant bornée par n, la complexité de la construction du tas est au pire O(n). {Rôle : trie l a l i s t e 1 en ordre croissant des clés }
pourtout i de 2 à longueur ( l ) faire
La phase de td proprement dite comporte n - 1 étapes. La complexité de chaque étape
{Invariant : l a sous-liste de ième (l, 1 ) à i ème (1 , i -1 ) est triée}
étant celle de la complexité d'un réordonnancement du tas, sa complexité est au pire égale à
x f- i ème ( 1 , i )
O(n log2 n).
j f- i-1
En ajoutant la complexité de la création du tas initial et celle de la phase de tii, l'algorithme sup f- vrai
de tri en tas conserve une complexité, dans le pire des cas, égale à 0(n log2 n).
23.2 Tris internes 329 330 Chapitre 23 • Algorithmes de tri
En supposant que les valeurs des distances choisies sont 5, 2 et 1, les étapes produites par finsi
cette méthode sont données ci-dessous. {insérer x à sa place}
ième ( l , j +distance) f- x
53 914 230 785 121 350 567 631 11 827 180 finpour
53 567 230 11 121 180 914 631 785 827 350 {réduire de moitié l a distance}
53 11 121 180 230 567 350 631 785 827 914 distance f-- distance div 2
11 53 121 180 230 350 567 631 785 827 914 fintantque
À la première étape, l'algorithme trie séparément les cinq listes suivantes formées d'élé-
ments distants de cinq positions : La complexité de ce tri est relativement difficile à calculer. Sachez qu'elle dépend très
fortement de Ja séquence de distances choisie. Trouver la suite des distances qui donne les
53 350 180 meilleurs résultats n'est pas simple. Le choix très courant d'une suite de puissances de deux
914 567 (celui de l'algorithme présenté) n'est pas très bon, car la complexité du tri est dans le pire
230 631 des cas égale à O(n2 ) . Plusieurs séquences, comme 1 , 3, 7, . , 2k + 1, donnent des com
2
. .
785 11 plexités dans le pire des cas égales à O(n31 ). En 1969, D . KNUTH indique une complexité
121 827 égale à O(n1, 2 5 ) pour la suite 1 , 4, 13, 40, 121, 364, 1093, 3280, 9841, . . . Cette suite a aussi
l'avantage d'être très facile à calculer, puisqu'il suffit de multiplier le terme précédent par 3
À seconde étape, les éléments distants de deux positions forment les deux listes suivantes, et de lui ajouter 1 : hk+l = 3hk + 1 . Le nombre de termes est Llog3 nj 1 . Nous donnons -
dans le cas d'un tri par insertion séquentielle classique. Mais alors, quel est l'avantage de h = 3 * h + 1;
cette méthode par rapport celle du tri par insertion séquentielle ? Nous avons remarqué que }
Il tri Shell
pour une liste triée, le nombre de comparaisons pour un tri par insertion est égal à n l . Ainsi
-
23.2.4 Tri par échanges Dans le pire des cas, c'est-à-dire quand la liste est triée en ordre inverse, les éléments sont
systématiquement échangés. Il y a n - 1 échanges à la première étape, n - 2 la seconde, etc.
Ces méthodes de tri procèdent par échanges de paires d'éléments qui ne sont pas dans le Puisqu'il y a n - 1 étapes, le nombre d'échanges Je pire est donc � (n2 - n) = O(n2 ). En
bon ordre, jusqu'à ce qu'il n'y en ait plus. Nous présentons deux tris : le plus mauvais et le revanche, il n'y a aucun échange si la liste est déjà triée.
meilleur des tris internes de ce chapitre.
On démontre par dénombrement que le nombre d'inversions d'une liste l d'éléments dis
tincts, c'est-à-dire le nombre de couples (i, j ) tels que i < j et ième(l, i) > ième(l,j) est
Tri à bulles (Bubble sort) en moyenne égal à :Ï(n2 - n). Il en résulte que c'est le nombre moyen d'échanges de l'algo
Comme pour le td par sélection, à la ie itération, la sous-liste formée des éléments de rang rithme, et sa complexité est donc O(n2 ) .
1 à i 1 est triée. De plus, les clés des éléments compris entre les rangs i et n sont supérieures
-
à celles de la sous-liste ordonnée. En partant du rang n jusqu'au rang i, on échange deux Le tri rapide
éléments consécutifs chaque fois qu'ils ne sont pas dans le bon ordre, de telle sorte que le
Inventé par C.A.R. HOARE au début des années 1960, le tri rapide (quicksort en anglais)
plus petit trouve sa place au rang i. Le nom de tri à bulles reflète le fait que les éléments les
doit son nom au fait qu'il est l'un des meilleurs tris existants. On le retrouve dans les environ
plus légers (les plus petits) remontent à la surface (i.e. vers le début de la liste).
nements de programmation de nombreux langages. C'est un tri par échanges et partitions. Il
Au-dessous, chaque ligne correspond à une étape et donne le résultat des échanges en consiste à choisir une clé particuJière dans la liste à tiier, appelée pivot, qui divise Ja liste en
partant de la fin de la liste. Les nombreux échanges effectués à chacune des étapes ne sont pas deux sous-listes. Tous les éléments de la première sous-liste de clé supérieure au pivot sont
indiqués faute de place. Par exemple, à la première étape 180 et 827 sont d'abord échangés, transférés dans la seconde. De même, tous les éléments de la seconde sous-liste de clé infé
puis 1 1 est échangé successivement avec toutes les valeurs de 631 à 53, soit au total neuf rieure au pivot sont transférés dans la première. La liste est alors formée de deux partitions
échanges uniquement pour la première étape. dont les éléments de la première possèdent des clés inférieures ou égales au pivot, et ceux de
la seconde possèdent des clés supélieures ou égales au pivot. Le tii se poursuit selon le même
1 53 914 230 785 121 350 567 631 11 827 180 algorithme sur les deux partitions si celles-ci possèdent une longueur supérieure à un.
11 1 53 914 230 785 121 350 567 631 180 827 À chaque étape, pour créer les deux partitions de part et d'autre du pivot, on parcourt
11 53 1 121 914 230 785 180 350 567 631 827
simultanément la liste à l'aide de deux indices, en partant de ses extrémités, et on échange
11 53 121 1 1 80 914 230 785 350 5 6 7 631 827
les éléments qui ne sont pas dans la bonne partition. Le partitionnement s'achève lorsque les
11 53 121 180 1 23 0 9 1 4 3 5 0 7 8 5 567 631 827 indices se croisent.
11 53 121 180 2 3 0 1 350 914 567 785 631 827
11 53 121 180 2 3 0 350 1 567 914 631 785 827 Par exemple, si nous choisissons comme pivot la valeur 350, le partitionnement provoque
11 53 121 180 2 3 0 350 567 1 63 1 914 785 827 deux échanges, mis en évidence ci-dessous :
! l
11 53 121 180 230 350 567 631 1 785 914 827
l 1
11 53 121 180 2 3 0 350 567 631 785 1 82 7 914
-0 -0
0 0
�
c Cet algorithme de tri s'écrit : c
::J ::J
î î
0 0 53 914 230 785 121 567 631 11 827 180
V
T""l Algorithme TriàBulles ( 1 ) V
T""l
0 {Rôle : trie la l i s t e 1 en ordre croi ssant des clés} 0
N N
@ pourtout i de 1 à longueur ( l ) - 1 faire @
..... ..... i=l j=ll
..c {Invariant : l a sous-liste de ième (l, 1 ) à i ème (1 , i - 1 ) est triée} ..c
Ol Ol
ï::: pourtout j de longueur ( l ) à i+l faire ï:::
>-
a. si c l é ( ième ( l , j ) ) < c l é ( ième ( l , j - 1 ) ) alors 1î À l'issue du paititionnement, la liste est organisée de la façon suivante :
0 0
u {échanger ième (1, j ) avec ième (l , j -1 ) } u
B ième ( 1 , j - 1 )
�
î î
ièrne ( l , j )
53 180 230 11 121 567 631 785 827 914
finsi
finpour
finpour
j=5 i=7
Il est facile de voir que le nombre de comparaisons moyen, le meilleur et le pire, est De plus, les affirmations suivantes sont vérifiées :
identique à celui du tri par sélection directe, soit � (n 2 n) = O(n2 ) . -
23.2 Tris internes 335 336 Chapitre 23 • Algorithmes de tri
do
while ( c . inférieu r ( ième ( i ) . clé ( } , pivot ) ) i++;
Vk E [1, i - 1], clé(ième(l,k)) ( pivot while ( c . supérieur ( ième ( j ) . clé ( } , pivot ) ) j--;
Vk E [j + 1 , longueur(!)], clé(ième(l,k)) � pivot if (i<=j) {
échanger ( i , j ) ;
Vk E [j + 1 , i 1], clé(ième(l,k)) = pivot i+ + ; j--;
-
}
Le pa11itionnement d' une liste entre les rangs gauche et droit autour d'un pivot est donné I l invariant :
ci-dessous : Il VkE { l , i - 1 ) , clé (ième (k) ) �pivot
Algorithme partitionnement ( 1 , gauche, droit) Il VkE {j+l, longueur () } , clé (ième (k) ) �pivot
{Parti t i onnement d ' une liste 1 autour d ' un pivot} while (i<=j ) ;
{entre les rangs gauche et dro i t } Il VkE {j+l , i - 1 ) , clé (ième (k) ) =pivot
i f- gauche 11 VkE { 1 , i -1 ) , clé (ième (k) ) �pivot
j f- droit Il VkE { j + l , longueur () } , clé (ième (k) ) �pivot
pivot f- {choisir le pivo t } if (gauche < j ) t r iRapide ( c , gauche, j) ;
répéter if (droit>i) triRapide ( c , i, droit ) ;
tantque c l é ( ième ( l , i ) ) <clé (pivot ) faire i f- i+l fintantque
tantque clé ( ième ( l , j ) ) >clé (pivot ) faire j f- j - 1 fintantque
si i:::;j alors
Pour trier une liste complète, il suffit de donner les valeurs 1 et longueur(), respectivement,
échanger ( l , i , j ) à gauche et droit lors du premier appel de la méthode. Le tri rapide s'écrit simplement :
i f- i+l public void triHoare ( Compa rateur<C> c ) {
j f- j - 1 t riRapide ( c , 1, longueur ( ) ) ;
finsi
('ï/kE [ 1 , i - 1 ] , clé (ième (1, k) ) :::;pivot}
{'ikE [ j + l , longueur (l) ] , clé (ième (l , k ) ) �pivot} La figure 23.4 montre les différentes étapes du tii sur la liste de référence. Les carrés
jusqu' à i > j indiquent les pivots choisis par la méthode précédente, et les ovales entourent les partitions
(5 3 ) (5 6 7 )
{VkE {j+l , i -1 ) , clé (ième ( l , k) ) =pivot} créées autour du pivot après échanges.
NkE {l, i - 1 ] , clé (ième (l , k) ) J::;;p ivot }
{VkE [j+l, longueu r {l ) ] , clé (ième (l, k) ) �pivot} 180 230 11 121 350 631 785 827 914
(5 3 (,,___ ) (,,_8_ )
�------------- �-------------�
Vous noterez que le balayage des sous-listes avec des tests d'inégalité stricte peut provo 180 121 230 5_67 63 1
_ 78 5 2__ 7 _14
9�
(5 3 )
_ _
�
quer des permutations inutiles lorsque la liste contient des clés identiques. On pomTait les
-0 remplacer par des tests d'inégalité au sens large, mais dans ce cas le pivot ne jouerait plus "O
0 0
c c 11 12 1 180 827
::J son rôle de sentinelle. En effet, imaginons que toutes les clés de la liste soient inférieures ou ::J 567
(5 3 )
�---------
0 0
V
T""l
égales au pivot, le parcours réalisé par la première boucle fera sortir la variable i des bornes de V
T""l
0 la liste. Si on teste l'égalité, il faut prévoir une autre gestion de sentinelle, ou plus simplement 0
N N 11 12 1
réc1ire le parcours de la façon suivante : '---
- -----
@ @
.....
..c .....
..c
Ol tantque i:::;j et c l é (ième ( l , i ) ) :::;c l é (pivot) faire if-i+l fintantque Ol
ï::: ï:::
JI
>
a. tantque i:::;j et clé ( ième ( l , j ) ) �clé (pivot ) faire j f- j - 1 fintantque >
a.
0 0
u u
Le tri d'une liste entre les rangs gauche et droit se poursuit par le partitionnement des deux
partitions créées, selon la même méthode. La méthode de tri rapide écrite récursivement en
JAVA pour une sous-1iste comprise entre les rangs gauche et droit est donnée ci-dessous :
11 53 121 180 230 350 567 631 785 827 914
private void t r i Rapide ( C ompa rateur<C> c , int gauche, int droite)
{ FIGURE 23.4 Tri rapide de la liste de référence.
int i=gauche, j=dro i t ;
C pivot = ième ( ( i + j ) / 2 ) . c lé ( ) ; Le choix du pivot conditionne fortement les performances du tri rapide. En effet, la com
plexité moyenne du nombre de comparaisons dans la phase de partitionnement d'une liste de
23.2 Tris internes 337 338 Chapitre 23 • Algorithmes de tri
longueur n est O(n), puisqu'on compare le pivot aux n - 1 autres valeurs de la liste. Il y a n sera toujours celle d' une référence, et les tris par insertion resteront meilleurs que ceux par
ou n + 1 comparaisons. Si le choix du pivot est tel qu'il divise systématiquement la liste en sélection. Nous constatons que le tii par insertion dichotomique est de loin le meilleur des tris
deux partitions de même taille, c'est-à-dire que Je pivot correspond à la médiane, le nombre élémentaires (environ 2 minutes 20 secondes pour une liste de 500 000), mais reste néanmoins
de comparaisons sera égal à n log2 n. En revanche, si à chaque étape, le choix du pivot divise moins bon que les tris élaborés.
la liste en deux partitions de longueur 1 et n - 1, les performances du tri chutent de façon La figure 23.5 montre des temps d'exécution pour des listes quelconques. Les abscisses
2
catastrophique et le nombre de comparaisons est O(n ) . Dans l'exemple donné, le tri est donnent la longueur de la liste, et les ordonnées le temps exprimé en secondes.
optimal pour les partitionnements successifs de la partition de droite produite à la première
étape ; le pivot est à chaque fois la médiane. Pour la partition de gauche, les choix successifs 4000
Tri Bulle
Sélection Directe
-
des pivots 230, 180 et 1 1 sont au contraire les plus mauvais. Dans le cas moyen, on a démon
Insertion Directe
-----
Insertion Dichotomique
····· · ·
tré, sous l'hypothèse de clés différentes et équiprobables, que le nombre de comparaisons est 3500 ··
à 1, dans le pire des cas fn/21, et dans le cas moyen environ n/6. Pour un tri complet, il en
résulte que la complexité moyenne du nombre d'échanges est O(n log2 n). 2500
V>
Comment choisir le pivot ? Le choix du pivot doit rester simple, afin de garder toute son <l>
-0
c 2000
0
efficacité à la méthode. Dans la méthode prograrnn1ée plus haut, nous avons choisi l' élé 0
<l>
V>
ment du milieu. Nous aurions pu tout aussi bien choisir Je pivot au hasard, ou le premier
1500
ou le dernier de la liste. Mais attention à ces deux derniers choix, si la liste est déjà triée
ou inversement triée, l'algorithme donnera sa plus mauvaise performance. C.A.R. HOARE
suggère de prendre la médiane de trois éléments, par exemple le premier, le dernier et celui 1000
du milieu. [BM93] ont également proposé des adaptations de l'algorithme afin de garantir les
performances du tri. 500
o L---i-=Œ�
::J:=_�
:=:
:i:
::
:;:
;;i;:
;;
:=c�;
==±:
:_�.-i.�
.:.:.:.l
..
.:.:.:.:
.:.:.
::.:.=.i
�:
23.2.5 Comparaisons des méthodes 0 50000 100000 150000 200000 250000 300000 350000 400000 450000 500000
nb d'éléments
Nous donnons dans cette section quelques éléments de comparaison des méthodes de tri
internes présentées dans ce chapitre, mis en lumière par des résultats expérimentaux obtenus FIGURE 23.5 Méthodes simples.
sur un Intel Core i5-3320M CPU à 2.60GHz, sous Linux 64 bits. Les méthodes de tri écrites
en JAVA ont été testées pour des listes dont les éléments sont tirés au hasard, en ordre croissant Parmi les méthodes élaborées, le tri rapide est incontestablement Je plus performant. Il est
-0 et décroissant. Pour les méthodes de tris simples, les longueurs des listes varient de 10 à -0 extraordinaire aujourd'hui de pourvoir trier sur un simple ordinateur 20 millions de valeurs
0 500 000 (au delà les temps de calcul deviennent vraiment trop longs). Pour les méthodes 0 en moins de 20 secondes ! Quelle que soit la façon de choisir le pivot (hasard, milieu ou
c c
::J ::J
0 élaborées, nous avons testé les tris pour des listes de longueur maximale égale à 20 000 000 0 médiane), les temps de tri sont à peu près identiques. Notez que Je tri rapide et le tri en
V
T""l V
T""l
éléments. tas possèdent une complexité théorique en nombre de comparaisons assez semblable, et en
0 0
N N fait meilleure pour le second. On constate, dans la pratique, que le premier tri est deux fois
@ Pour des listes jusqu'à mille éléments environ, toutes les méthodes se valent. Le tri est @
..... effectué en un ou deux centièmes de seconde. Au-delà de cette valeur, il apparaît des dif ..... et demi plus rapide que le second, certainement parce que le nombre de déplacements des
..c ..c
Ol Ol éléments est inférieur. D'autre part, certains auteurs ont remarqué qu'à partir d'une certaine
ï::: férences significatives entre les méthodes simples (sélection directe, tri à bulles, insertion ï:::
>
a. >
a. taille des partitions, le coût des appels récursifs devient significatif. À partir d'un certain seuil,
0 séquentielle et dichotomique) et les méthodes élaborées (tri shell, tri en tas et tii rapide). Il 0
u u environ 20 éléments, on substitue au tri rapide une méthode de tri simple, par exemple un tii
faut environ quarante-cinq minutes pour ttier 500 000 éléments avec une sélection directe,
alors qu'un quart de seconde suffit au tri rapide. par insertion séquentielle. Les résultats que nous avons obtenus de façon expérimentale n'ont
montré aucune an1élioration, mais au contraire une légère dégradation des performances.
Parmi les méthodes simples, le tri à bulles a les plus mauvaises perfonnances, sauf dans
le cas où la liste est déjà triée, il est alors un peu meilleur que le tri par sélection directe. Les Le tri par distances décroissantes donne de meilleurs résultats que le tri en tas lorsque la
tris par insertion sont meilleurs que les tris par sélection, et dans le cas particulier d'une liste liste est triée, ou triée en ordre inverse. Mais nous avons aussi vérifié que le tri par distances
déjà triée, ils donnent les meilleurs résultats de tous les tris puisque leur complexité est égale décroissantes reste nettement inférieur aux deux autres méthodes pour des listes quelconques.
à O(n). Dans le cas moyen, le tri par insertion dichotomique n'apporte pas d'amélioration Les temps d'exécution pour des listes quelconques par les méthodes élaborées sont donnés
spectaculaire à cause du décalage linéaire. En JAVA, toutefois, l'encombrement d'un élément par les courbes de la figure 23.6.
23.3 Tris externes 339 340 Chapitre 23 • Algorithmes de tri
�
53 914 230 785 121 350 567 631 11 827 180
<2 3 0
TriShell
Tri En Tas
-
------
�
100
80
istribution < 53 230
11 121
785
350
914
567
180
631 827
60
usion 11 53 121 230 350 567 631 785 827 914 180
40
20
� istribution < 11
180
53 121 230 350 567 631 785 827 914
usion 11 53 121 180 230 350 567 631 785 827 914
_...--
········ · · · · ·
0 '--" �-'-�-"
-�-"-�
�"-
-'�
�-'-�-
�-'-�
..=.:�
--.
"=-
o 2e+06 4e+06 6e+06 8e+06 1 e+07 1.2e+07 1.4e+07 1 .6e+07 1 .8e+07 2e+07 FIGURE 23.7 Étapes du tri par fusion naturelle de la suite de référence.
nb d'éléments
Ce processus de tri fusion naturelle est donné par la méthode JAVA suivante :
FIGURE 23.6 Méthodes élaborées.
public void fusionNat urelle (File f , Comparateur<C> c )
monotonie courante de f sur g. L'opération copierlesmonotonies(f,g) copie sur g toutes les Il f . fdf () ou g . fdf()
monotonies de f, à partir de la monotonie courante, et renvoie le nombre de monotonies co if ( ! f . fdf ( ) )
piées. Enfin, l'opération fusionmonotonie(j;g,h) écrit sur h la monotonie résultat de la fusion Il copi er toutes les monotonies de f à l a fin de h
des monotonies courantes de f et g. nbMono+= f . copierLesMonotonies ( h ) ;
el se
La mise en œuvre en JAVA du type abstrait Fm par extension des classes if ( ! g . fdf ( ) )
Ob jectinputStream et ObjectOutputStream est assurée par la classe générique Il copier toutes les monotonies de g à l a fin de h
F ichierMonotoniesEntrée. Elle est paramétrée sur les types des valeurs et des clés nbMono+=g . c opierLesMonotonie s ( h ) ;
des éléments à trier. Sa programmation ne pose pas de difficulté, et elle est d'ailleurs laissée return nbMono;
en exercice. Notez que seuls les fichiers d'entrée doivent être traités comme des fichiers de
monotonies.
L'opération de distribution des monotonies sur les deux fichiers auxiliaires est donnée par Dans les algorithmes de tri externe, seule la complexité associée aux déplacements des élé
ments est vraiment intéressante dans la mesure où le temps nécessaire à un accès en mémoire
la méthode distribuer :
secondaire est d'un ordre de grandeur en général bien supé1ieur à celui d'une comparaison
/ * * Rôle : répartit a l t ernativement les monotonies du fichier fl en mémoire centrale.
* sur les fichiers f2 et f3
*/ À chaque étape distribution-fusion, le nombre de monotonies est au pire divisé par deux.
private void dist ribue r ( F i l e f l , File f 2 , File f 3 , Comparateur<C> c ) Dans le pire des cas (fichier à n éléments trié à l'envers), il y aura pog2 nl étapes, chaque
throws Exception étape imposant n déplacements. Le nombre maximal de déplacements est donc n pog2 nl ·
{ Dans le cas moyen, si m est la longueur moyenne des monotonies, le nombre d'étapes sera
FichierMonot oniesEntrée<V , C> f = égal à flog2 (n/m)l
new FichierMonotoniesEntrée<V , C> (new F i l e i nputStream ( fl ) , c ) ;
ObjectOutputSt ream
g = new ObjectOutputStream ( new F i le0utputStream ( f 2 ) ) ,
h = new Object Output Stream ( new Fi leOutpu t St ream ( f3 ) ) ; 23.4 EXERCICES
while ( ! f . f df ( ) ) {
f . copierMonoton i e ( g ) ; Exercice 23.1. Écrivez la méthode trildiot, qui tiie une liste de longueur n dans un temps
if ( ! f . fdf ( ) ) f . copierMonotonie ( h ) ;
non borné. La méthode du tii est la suivante : tirez deux indices différents au hasard comptis
entre 1 et n, échangez les deux éléments associés et regardez si la table est triée. Si la table
f . close ( ) ; g . clos e ( ) ; h . close ( ) ;
est tiiée, on aiTête ; sinon on recommence. Testez cette méthode ? Jusqu' à quelle longueur de
liste cette méthode donne-t-elle un résultat en un temps raisonnable ?
Une fois les monotonies de f réparties sur g et h, il ne reste plus qu'à les fusionner sur
Exercice 23.2. On dit qu'un tri est stable si, pour deux éléments qui possèdent la même clé,
-0 f. L'algorithme de fusion est classique, il parcourt simultanément les deux fichiers g et h et -0
0 0 l 'ordre de leur position initiale est conservé dans la Jjste triée. Parn1i les tiis présentés dans
c fusionne les monotonies deux à deux. La fin de l'un des deux fichiers peut être atteinte avant c
::J ::J
0 0 ce chapitre, indiquez ceux qui sont stables.
V l' autre. Dans ce cas, il est nécessaire de recopier toutes les monotonies restantes sur f. La V
T""l méthode renvoie le nombre de monotonies éctites sur f. T""l
0 0 Exercice 23.3. Le tri par fusion procède par interclassement de sous-listes triées. L'algo
N N
@ private int fusionner ( F i l e f l , File f 2 , File f3, Comparateur<C> c ) @ rithme de ce tri s'expdme bien récursivement. On divise la liste à trier en deux sous-listes
.....
..c .....
..c
throws Exception de même taille que l'on trie récursivement par fusion. Les deux sous-listes tiiées sont ensuite
Ol Ol
ï::: { ï::: fusionnées par interclassement. Le tri par fusion d'une liste entre les rangs gauche et droit est
>
a. >
a.
0 FichierMonotoniesEntrée<V , C> 0 donné par :
u f = new FichierMonotoniesEntrée<V, C> (new FileinputStream ( f l ) , c) ,
u
Algorithme triFusion ( 1 , gauche, droit )
g = new FichierMonotoniesEntrée<V, C > (new FileinputStream ( f2 ) , c);
si gauche<droit alors
ObjectOutputStream
milieu +-- (gauche+droi t ) / 2
h = new ObjectOutputStream ( new F i l eOutput Stream ( f 3 ) ) ;
triFusion ( l , gauche, milieu)
int nbMono = O;
triFusion ( l , milieu+l, droit )
while ( ! f . fdf ( ) && ! g . fdf ( ) )
fusion ( l , gauche, milieu, droit)
f . fusionMonotonie ( g , h ) ;
finsi
nbMono++;
23.4 Exercices 343 344 Chapitre 23 • Algorithmes de tri
Quelle est la complexité de ce tri ? Quel en est son principal inconvénien t ? Programmez alexandre
a -
a -
par R. SEDGEWICK [Sed04], 1, 5, 19, 41, 109, 209, . . . dont les termes sont calculés de façon
alexandre
croissante à partir des fonctions 9 x 4k 9 x 2k + 1 et 4k 3 x 2 k + 1. Cette dernière séquence
- - 1
-
alice
donne en pratique de meilleurs résultats que celle de D. KNUTH. 1
1
1
Exercice 23.5. Programmez la méthode du tri rapide et faites des tests comparatifs en choi
sissant pour pivot : 1) le premier élément de la liste 2) la moyenne de la première et de la
dernière valeur de la liste 3) la valeur médiane des trois premiers éléments différents.
n
1
1
�
Exercice 23.6. Modifiez (légèrement) l' alg01ithme du tii rapide afin de renvoyer la valeur 1
médiane d'une liste. On rappelle que la valeur médiane divise une liste en deux sous-listes de
Exercice 23.11. Le tri externe présenté interclasse les monotonies qui existent naturelle
même taille telles que tous les éléments de la première sont inférieurs ou égaux à la médiane,
et tous les éléments de la seconde lui sont supé1ieurs ou égaux.
ment dans le fichier à tii er Une autre façon de procéder est de lire les éléments du fichier par
.
paquets de taille m, de tiier en mémoire centrale chaque paquet à l'aide d'un tri interne, et
Exercice 23.7. Nous avons vu que la complexité théorique la pire d'un tri par comparaison de distribuer les monotonies ainsi construites sur les fichiers auxiliaires. Le tri se poursuit par
est O(n log2 n). Il est toutefois possible de trier en O(n) lorsqu'on possède des informa interclassement comme précédemment. Si on dispose de 2k fichiers, une moitié en lecture,
tions sur les éléments. Prenons, par exemple, une liste de n entiers à tlier (ces n entiers sont et l'autre en écriture, une opération de fusion consistera à interclasser au plus k monotonies
distincts et compris entre 1 et n. L'algorithme suivant trie la liste l dans un tableau t sans dans un des fichiers en écriture. Quel est le nombre moyen d'étapes nécessaires au tri de n
aucune comparaison et avec n transfe1ts. éléments si les monotonies sont de longueur m et s'il y a k fichiers en lecture ? Programmez
cette méthode de tri externe pour k = 4. Notez que vous pouvez utiliser une file avec pdorité
pourtout i de 1 à n faire
t [ ième ( 1 , i ) ] +-- ième ( 1 , i )
pour trouver le minimum des k monotonies lors de l'interclassement.
finpour
Modifiez cet algorithme de façon à faire un tri sur place (sa complexité doit rester en
O(n)).
Exercice 23.8. La méthode de td par paquets généralise la méthode de l' exercice précédent.
Pour une suite de n entiers pris sur l ' intervalle [1, m], on utilise un tableau t de longueur m,
initialisé à O. Pour chaque entier i de la suite, on incrémente t [ iJ de un. À la fin, il suffit
-0 de parcourir le tableau t du début pour obtenir la suite triée. Écrivez l' algodthme qui met en -0
0 0
c œuvre cette méthode. c
::J ::J
0 0
V
T""l Exercice 23.9. Nous voulons trier une suite de chaînes de caractères dans l'ordre lexicogra V
T""l
0 phique (i.e. celui du dictionnaire). Pour cela, nous allons regrouper les chaînes dans une table 0
N N
@ structurée par paquets de longueur maximale m. Ces paquets sont constitués par des chaînes @
.....
..c .....
..c
Ol de caractères débutant par un même groupe de k caractères, le k + 1e permettant de diffé Ol
ï::: ï:::
>
a. rencier chaque chaîne. Lorsqu'un paquet est plein, on crée une table d'indirection permettant >
a.
0 de discdminer sur cette k + 1 e lettre. Par exemple, les chaînes alexandre et anaïs sont dans 0
u u
un paquet (m = 2) et sont différenciées par leur deuxième lettre (k = 2). La figure suivante
montre l'effet de l'ajout de la chaîne alice.
Définissez la structure de données qui représente la table qui mémorise les chaînes de
caractères. Écrivez l'alg01ithme d'insertion d'une chaîne dans la table, puis l'algorithme qui
affiche sur la sortie standard toutes les chaînes de caractères dans 1 'ordre lexicographique.
Chapitre 24
Ce chapitre présente quatre algorithmes sur les graphes parmi les plus classiques. Les al
gorithmes de recherche des composantes connexes et de fermeture transitive d'un graphe sont
utiles pour tester la connexité de ses sommets. Parmi les problèmes de cheminement, l' algo
rithme de DIJKSTRA permet de trouver le chemin le plus court entre un sommet paiticulier et
les autres sommets d'un graphe valué. Nous veITons également, les algorithmes A* et IDA*
qui se servent d'heuristiques pour trouver le cheminement le plus court dans le graphe. Enfin,
nous terminerons avec l'algorithme de tri topologique qui résout des problèmes d'ordonnan
FIGURE 24.2 Deux arbres couvrants issus du graphe de la figure 24. 1 .
cement des graphes orientés sans cycle. La programmation en JAVA de ces algorithmes vient
enrichir les classes qui implémentent les interfaces génériques Graphe et GrapheValué L'algorithme suivant construit une liste d'arbres couvrants Ai, associés à chaque compo
données au chapitre 19. sante connexe d'un graphe non mienté. Il fait un parcours en profondeur des sommets du
-0 -0
0 0
c c graphe. Chaque sommet non marqué s est ajouté dans l'arbre couvrant Ai. Chacun de ses
::J ::J
0 0 successeurs non marqués x est ajouté dans Ai et une arête (s , x) est créée. La construction de
V
T""l V
T""l
0
N
24.1 COMPOSANTES CONNEXES 0
N
l'arbre couvrant se poursuit récursivement avec les successeurs x .
Algorithme Composantes-Connexes (G, L)
@ @
.....
..c Une manière de vérifier s'il existe un chemin entre deux sommets est de vérifier s'ils .....
..c {Construi t une liste L des arbres couvrants de chaque
Ol appaitiennent à la même composante connexe. On rappelle qu'un graphe non orienté est Ol composante connexe du graphe non orienté G}
ï::: ï:::
>
a. >
a.
composé d'une ou plusieurs composantes connexes. Une composante connexe est formée de pourtout s de G faire
0 0
u sommets tels qu'il existe toujours un chemin qui les relie. Une façon simple de trouver toutes u si non marqué ( s ) alors
{construire l a ième composante connexe Ai
les composantes d'un graphe est d'utiliser des parcours en profondeur ou en largeur. Nous
dont l a racine est l e sommet s }
avons vu à la section 19.4 que de tels parcours à paitir d'un sommet initial s accédaient aux
a j outer l e sommet s à Ai
sommets pour lesquels un chemin depuis s existait.
cConnexes (G, s , Ad
On appelle arbre couvrant d'un graphe connexe non orienté G = (X, U), le graphe partiel ajouter ( 1 , A,iJ
G' = (X1 U') tel que U' Ç U et G' est connex.e et sans cycle. Pour trouver les composantes finsi
connexes d'un graphe, on construit les arbres couvrants qui leur sont associés lors du parcours finpour
complet du graphe.
24.2 Fermeture transitive 347 348 Chap itre 24 • Algorithmes sur les graphes
Algorithme cConnexes ( G , s, A) L'algorithme du calcul de la fermeture transitive d'un graphe G, connu sous le nom d'algo
{Parcourir en profondeur des successeurs du sommet s rithme de FLOYD-WARSHALL, consiste à ajouter, de façon incrémentale, pour tout sommet
e t construire l a composante connexe A } s de G, un arc ( x , y ) s'il existe un chemin entre x et s et un chemin entre s et y. L'algo
mettre une marque sur le sommet s rithme procède de façon itérative en parcourant l'ensemble des sommets du graphe. Il s'agit
pourtout x de G t e l que 3 une arête ( s , x ) faire donc, chaque fois qu'une relation de transitivité entre deux sommets x et y peut être établie,
si non marqué ( x ) alors
d'enregistrer la relation entre x et y (voir la figure 24.3 page 348).
{créer une arête (s, x) }
a j outer le sommet x à A
ajouter l ' arête ( s , x)
cConnexes ( G , x, A)
finsi
- -- - - - - -- - -
finpour
0
--
--
---
�--
La complexité de la création des arbres couvrants est la même que celle du parcours com
plet en profondeur d'un graphe, soit O(n2 ) si le graphe, à n sommets et p arêtes, est repré
FIGURE 24.3 Relation de transitivité entre les sommets x et y.
senté par une matrice d'adjacence, et O(max(n, p)) si le graphe est représenté par des listes
d' adjacence.
L'algorithme FLOYD-WA R S H A L L appliqué à un graphe G = (X, U) s'exprime formelle
Nous allons évoquer quelques problèmes complémentaires liés à la connexité des graphes, ment comme suit :
mais qui ne seront pas développés. Pour une composante connexe, il est possible d'associer Algorithme fermeture-t ransitive ( G )
plusieurs arbres couvrants. Il est facile de voir que, si on supprime l'arête (s5, s6), dans pourtout s de X faire
l'arbre couvrant à gauche de la figure 24.2 page 346 et que si on crée l' arête (.s4, s6), on pourtout x de X faire
obtient un nouvel arbre couvrant pour la première composante connexe. Lorsque les graphes pourtout y de X faire
sont valués, c'est-à-dire si une valeur est associée à chaque arête, un problème classique est si s # x # y et arc ( x , s) et arc ( s , y ) alors
celui de la recherche de l' arbre couvrant minimal, c'est-à-dire l' arbre dont la somme des si � arc ( x , y ) alors a j outer l ' arc ( x , y) finsi
valeurs des arêtes est la plus petite. L'algorithme de KRUSKAL et celui de PRIM apportent finsi
tous les deux une solution à ce problème. finpour
finpour
Un graphe orienté est fortement connexe, si pour tout sommet s et s', il existe à la fois un finpour
chemin de s vers .s' et un chemjn de s' vers s. Pour ces graphes, iJ s'agit donc de rechercher les
composantes fortement connexes les plus grandes. Pour cela, les algorithmes mis en œuvre
dérivent du parcours en profondeur. La figure 24.4 montre les arcs qui ont été ajoutés au graphe de la figure 1 9 . l page 231 par
-0 -0 le calcul de cet alg01ithme, et la figure 24.5 montre la fermeture transitive de ce graphe.
0 0
c c
::J ::J La complexité de cet alg01ithme est O(n3), pour un graphe G à n sommets. Cet algorithme
0 0
V
�N 24.2 FERMETURE TRANSITIVE T""l
0
N
est assez coûteux, et i1 est important que les opérations de test de ] 'existence et d'ajout d'un
arc dans le graphe soient en 0(1). La représentation d'un graphe par une matrice sera alors
@ @ préférable .
.....
..c Le calcul de lafermeture transitive d'un graphe sert aussi à vérifier l'existence, ou non, .....
..c
Ol d'un chemin entre deux sommets d'un graphe. Mais, plutôt que d' avoir à faire une vérification Ol
ï::: ï:::
>
a. >
a. L'implémentation en JAVA
0 pour deux points particuliers du graphe, il est bien souvent très utile, d'obtenir, au préalable, 0
u cette connaissance pour tous les sommets du graphe. u
La méthode générique donnée ci-dessous calcule la fermeture d'un graphe passé en para
La fermeture transitive d'un graphe G = (X, U) est égale au graphe Q+ = (X, u+) mètre. Les tests d'existence d'un arc avec le sommet courant s permettent d'éviter certaines
te] que pour tout arc ( X , y) de Q+, il existe un chemin de longueur supérieure OU égale exécutions de boucle, mais l 'algorithme n'en demeure pas moins borné par n3, et reste bien
à un dans G d'extrémité initiale x et d'extrémité finale y. La fermeture transitive est dite en O(n3).
réflexive, et notée G*, si la longueur du chemin peut être égale à O. Dans ce cas, tout arc public <S> void fermetureTrans i t ive ( Graphe<S> g) {
( x, x) est toujours présent dans G*. Remarquez que la notion de fermeture transitive n'est pas for (S s : g)
propre aux graphes. En fait, elle peut être appliquée à toute relation binaire sur un ensemble for (S x : g)
E d'éléments dont il s'agit de connaître l'accessibilité par transitivité. i f ( x ! = s & & g . arc ( x, s ) )
24.3 Plus court chemin 349 350 Chap itre 24 • Algorithmes sur les graphes
recherche de la distance minimale entre deux sommets. L'algorithme de DIJKSTRA que nous
allons présenter maintenant, permet de calculer la distance minimale entre un sommet source
et tous les autres sommets d'un graphe valué orienté. Cet algotithme nécessite des valeurs
d'arc positives ou nulles.
L'algorithme de DIJKSTRA construit de façon itérative un ensemble solution S formé de
couples x = (sx , dx), tels que pour tout sommet Sx E G, dx est égale à la distance mfoimale
entre un sommet source s et le sommet Sx. S'il n ' existe pas de chemin pour un sommet sx,
par convention, sa distance avec s est égale à l'infini, noté oo .
Ainsi, pour Je graphe donné ci-dessous et le sommet source sl, l 'algorithme renvoie l'en
semble S = { (sl, 0), (s2, 1), (s3, 4), (s4, 6), (s5, 2), (s6, oo) } .
1
7
FIGURE 24.4 Arcs issus de la fermeture transitive du graphe de la fig. 1 9 . 1 . 8
1---G
-- '
'
'
I ------- '
I
J
I ''
I 1 /
I 1/
/
1 ,,
J , 1
1
1
d, = {� Sl
si Sx
Sx = S
:/= s
FIGURE 24.5 fermeture transitive du graphe de la figure 1 9 . 1 .
-0 -0 Pour ce graphe, les valeurs initiales des ensembles S et E sont telles que :
0 0
c c
::J for (S y : g) ::J 8=0
0 0
if (x ! = y && y != s && ( g . arc ( s , y ) ) )
V
T""l V
T""l E = { (sl, O), (s2, oo), (s3, oo), (s4, oo), (s5, oo), (s6, oo)}
0 if (! g . arc (x, y ) ) 0
N N
Il l ' arc n ' es t pas déjà présent => l ' ajouter L'ensemble S est construit progressivement de façon itérative. À chaque itération, il existe
@ @
.....
..c
g . a j outerArc ( x , y ) ; .....
..c un élément m = (sm , dm) E E de distance minimale, telle que 'efx E E, dm = min ( dx) ·
Ol Ol
ï::: ï::: Cet élément est retiré de l ' ensemble E et ajouté dans S. Affirmation : la distance dm est ]a
>-
a. >
a.
0 0 distance du plus court chemin entre s et Sm. Ensuite, les distances dx de chaque x E E qui
u u
possède un arc avec sm sont recalculées de telle façon que :
24.3 PLUS COURT CHEMIN si dm + valeurArc (Sm, , Sx ) < d:r. alors
dx +- dm + valeurArc (Sm, Sx )
24.3.1 Algorithme de Dij kstra finsi
Nous appellerons distance(x, y), la distance entre deux sommets x et y d'un graphe où valeurArc(sm,Sx) est la valeur de l'arc entre le sommet Sm et Je sommet Sx· À la
G = (X, U) orienté valué, définie comme la somme des valeurs associées à chaque arc du dernière itération, l'ensemble E est vide, et S contient la solution.
chemin qui relie x et y. L'objet des algorithmes de recherche de plus court chemin est la
24.3 Plus court chemin 351 352 Chapitre 24 • Algorithmes sur les graphes
L'algorithme appliqué au graphe de la page précédente produit les six itérations suivantes : s
�------
S E
{ (sl, O)}
{(sl , O) , (s2, 1 ) }
{ (s2, 1), (s3, oo ) , (s4, 10), (s5, oo ), (s6, oo)}
{ (s3, 8) , (s4, 10), (s5, 2), (s6, oo)}
_ _ _ _ _ _ _ _G
{(sl, 0), (s2, 1), (s5, 2)} {(s3, 4), (s4, 6), (s6, oo)}
{(sl, 0) , (s2, 1), (s5, 2), (s3, 4)} { (s4, 6), (s6, oo)}
{(sl, O), (s2, 1 ) , (s5,2), (s3, 4), (s4, 6)} {(s6, oo ) }
{ (sl, 0 ) , (s2, 1), (s5, 2), (s3, 4 ) , (s4, 6 ) , (s6, oo ) } {}
Algorithme Di j k s t r a ( G , s)
{ i n i t i a l i s a t i ons} L'implémentation en Java
s f- 0
E f- {(sx , dx ) / Vsx E G, dx = 0 si Sx = s et dx = OO si Sx =/= s} Dans cette section, nous présentons la programmation en JAVA de l'algorithme DIJKSTRA.
{construire l ' ensemble S des plus courts chemins} La méthode générique D i j kstra prend en paramètre un GrapheValué avec des valeurs
tantque E f 0 faire d'arcs de type Integer. Les couples des ensembles E et S sont représentés par le type
{Invariant : soit m E E, Vk E E, dm = min (dk ) l Élément donné à la page 265. La valeur de l'élément sera le sommet, et la clé sa distance
{ Vk E S, dk = distance minimale entre s et Sk } au sommet source. Pour accroître la lisibilité de la méthode Di j k s t ra, nous définissons les
E f- E - {m}
trois méthodes privées suivantes :
s f- s u {m}
{recalculer les distances dans E} Il Cette méthode renvoie l e sommet de l ' é lément e
pourtout x de E t e l que :3 un arc ( sm , Sx ) faire private <S> S sommet ( É lémen t < S , Integer> e ) ;
si dm + va l e u rAr c (Sm, Sx ) < dx alors Il Cette méthode renvoie l a distance de l ' é l ément e
dx f- dm + va l eu rAr c (Sm 1 Sx ) private <S> int dist ( É lément < S , Integer>
e) ;
finsi Il Cette méthode change l a distance de l ' é l ément e
finpour private < S > void changerD i s t ( É lément < S , Integer> x, Integer d ) ;
fintantque
{S = { (Sx, dx) , VSx E G, dx distance minimale entre s et Sx } } L'écriture de ces méthodes ne pose aucune difficulté et est laissée en exercice. Notez que
-0 rendre S -0 pour la dernière méthode, il faut ajouter à la classe Élément une méthode changerClé,
0 0
c c
::J ::J pour modifier la valeur de la clé d'un élément.
0 0
V
T""l V
T""l Les ensembles E et S sont des objets de type Ens emble, une interface générique qui
0 Montrons que cet algorithme calcule bien l'ensemble solution S des plus courts chemins, 0
N N définit les opérations d'un type abstrait Ensemble, dont l'implémentation sera discutée à la
@
tel que Vx E S, dx est le plus court chemin de s au sommet Sx· Commençons par le choix de @
..... m. Il faut montrer que dm est bien la distance du plus court chemin de s à Sm· Si ce n'était ..... section suivante. Pour le calcul des plus courts chemins, la classe Ensemble devra au moins
..c ..c
Ol pas le cas, il existerait un n E E (voir la figure 24.6), tel que dn + distance(sn, sm) � dm. Ol fournir les méthodes suivantes :
ï::: ï:::
>
a. >
a.
0 Or, on a dm � dn et, par hypothèse, les valeurs des arcs ne peuvent être négatives. Donc n ne 0 public interface Ensemble<T> extends I t erable<T> {
u peut exister, et m possède le sommet Sm de distance minimale par rapport à s, et il est ajouté u Il Cette méthode ajoute l ' élément e à l ' ensemble courant
dans S. public void a j oute r ( T e ) ;
Il Cette méthode renvoie true si l ' ensemble courant est vide,
On en déduit par récurrence, que tous les prédécesseurs de Sm, dans le plus court chemin
Il et false sinon
de s à Sm, appa1tiennent exclusivement à S. De même, pour tout x = (sx, dx) E E, avec public boolean vide ( ) ;
dx =f oo, dx est la distance d'un meilleur chemin entre s et S:x;, à l'itération courante, et dont Il Cette méthode s upprime le plus petit élément de l ' ensemble courant
les sommets prédécesseurs de Sx sont dans S. Il est évident, que la modification des valeurs Il et renvoie sa valeur
des distances dans E, après l'ajout de m dans S, maintient cette dernière propriété, et pennet, public T supprimerMin ( ) ;
de trouver un meilleur chemin, s'il existe, passant obligatoirement par sm.
24.3 Plus court chemin 353 354 Chapitre 24 • Algorithmes sur les graphes
Les classes d'implémentation de l'interface Ensemble fournissent des constructeurs qui Le corps de la boucle principale est effectué n fois. Une première série de comparaisons
pennettent l'initialisation d'un comparateur des éléments de l'ensemble. Dans notre mé est faite lors de la recherche de l'élément m dans l'ensemble E. Si cet ensemble est représenté
thode Di j k stra les ensembles construits sont munis d'un comparateur des clés entières par une liste linéaire non ordonnée, le nombre de comparaisons pour rechercher et supprimer
des Él ément. m, est égal au cardinal de E. Il y aura donc en tout n( n+ 1 ) /2 comparaisons, la complexité est
Nous pouvons donner l'écriture complète de la méthode qui renvoie l'ensemble des plus alors O(n2 ) . Cette complexité peut être améliorée : si l'ensemble E est représenté par un tas,
courts chemins d'un sommet s à tous les autres sommets du graphe. Notez que pour éviter l'accès à m, est en 0(1), et sa suppression demande au pire log2 (n), soit au total n log2(n)
la confusion entre le type générique s des sommets du graphe et le nom S de l'ensemble comparaisons. Dans la seconde boucle, le test de vélification d'adjacence des sommets Sm et
2
sx est exécuté n(n - 1 ) /2 fois. La complexité est O(n ) . Mais, on peut remarquer que le test
solution, nous noterons ce dernier sol dans la méthode ci-dessous.
d'ajustement des distances n'est à faire que d+ ( Sm ) fois, le demi-degré exté1ieur du sommet
Rôle : calcul des plus courts chemins entre l e sommet s
2
I** de Sm. Si le nombre d'arcs p est petit devant son maximum, n , et si l'accès au je successeur
* e t les autres sommet s du graphe, selon l ' a l gorithme du sommet Sm peut être exécuté en 0(1), il sera plus judicieux de récrire la seconde boucle
* de \Dijkstra et renvoie l ' ensemble solution de la façon suivante :
*I
public <S> Ensemble< É lérnent<S , Integer>> pourtout i de 1 à degréExt (Sm ) faire
D i j k s t r a ( GrapheValué<S, Integer> g, S s) Sx +--- ièmeSucc ( S m , i) dans E
si dm + valeurAr c ( Sm , Sx ) < dx alors
Ensemble< É lérnent < S , Integer>> dx +--- dm + valeurArc ( S m , Sx )
E = new EnsernbleLi s t e < É lérnent < S , Intege r > > ( finsi
new CornparateurDeCléEntière ( ) ) , finpour
s o l = new EnsembleLi s t e <É lérnent < S , Integer> > (
new CornparateurDeCléEntière ( ) ) ; Toutefois, cette écriture nécessite un moyen de retrouver le dx correspondant au Sx pour
Il i n i t i a l i ser l ' ensemble E la mise à jour des distances dans E. Si cette correspondance peut être obtenue en 0(1), la
E . ajouter (new É lérnent < S , Integer> ( s , 0) ) ; complexité de la modification des distances est O(p).
for (S x : g)
Au total, la complexité de l'algorithme de DIJKSTRA est O(n2 ) pour des graphes denses
i f (x != s)
E . a j out e r (new É lérnent < S , Integer> ( x , Intege r . MAX_VALUE ) ) ;
avec E représenté par une liste, alors qu'elle est 0( n log2 n + max( n2 , p)) avec E représenté
Il construire l ' ensemble sol des plus courts chemins par un tas.
while ( ! E . vide ( ) ) {
Il soit m= (Sm , drn J E E, Vk E E, Sm = min (dk) 24.3.2 Algorithme A*
11 Vk E sol, dk = dis t ance minimale entre s et Bk
É lément rn = E . supprirnerMin ( ) ;
La complexité de l'algorithme de DIJKSTRA, tant spatiale que temporelle, le rend en pra
sol . a j outer (rn) ;
tique inutilisable lorsque les graphes à parcourir sont de très grande taille, comme ça peut
-0 Il pour tout sommet x de E qui possède un arc avec m -0
0 0 être le cas pour la résolution automatique de jeux de type taquin 1 . Pour résoudre de tels
c for ( É l érnen t < S , Integer> x : E) c
::J ::J problèmes, une amélioration de l'algorithme de DIJKSTRA, appelée A*, a été proposée par
0 if ( g . ar c ( sornrnet (rn) , sornrnet ( x ) ) ) 0
V V HART, NILSSON et RAPHAEL en 1968 [HNR68].
T"'l int d=di s t ( rn ) + g . valeurArc ( s ornrnet (rn) , sornrnet ( x ) ) ; T""l
0 0
N if (d<dist ( x ) ) N L'idée principale de l'algorithme A* est de passer de sommet en sommet en privilégiant
@ changerDist ( x , d ) ; @ chaque fois les sommets qui offrent un coût f (p) le meilleur, c'est-à-dire en se dirigeant vers
.....
..c .....
..c
O'I Ol le sommet final en passant par les sommets qui semblent donner le chemin le plus direct .
ï::: ï:::
>
a. >
a. Pour chaque sommet p d'un graphe valué G, on calcule le coût f = g + h, où g(p)
0 } 0
u u est la longueur du chemin le plus court connu entre le sommet de départ s et p, et h (p)
Il sol = { (Sx , dx ) , Vsx E g, dx dis t ance minimale entre s e t x}
return s o l ; l'estimation de la longueur minimale du chemin entre p et le sommet final t. L' algorithme A*
l . Le jeu de taquin, inventé à la fin du XJXc siècle aux États-Unis, est un damier de 16 cases sur le
quel coulissent 1 5 carreaux numérotés qu'il s'agir de réordonner. Le nombre de combinaisons possibles esc égal
16! = 20 922 789 888 000. On modélise cec ensemble de configurations par un graphe où chaque sommet est une
Complexité de l'algorithme configuration particulière du taquin, et où les arcs qui relient un sommet particulier à ses successeurs (encre 2 et 4)
correspondent aux configurations possibles du taquin après le déplacement d'un carreau. Le jeu de taquin consiste à
La complexité de l' algorithme de DIJKSTRA dépend de la représentation de l' ensemble E. rechercher un chemin, le plus court si possible, encre deux sommets, en général encre une configuration quelconque
Nous allons nous intéresser au nombre de comparaisons effectuées dans le pire des cas par ec la configuration initiale du jeu de taquin.
l ' algorithme pour un graphe de n sommets et p arcs.
24.3 Plus court chemin 355 356 Chapitre 24 • Algorithmes sur les graphes
appartient à la famille des algorithmes de recherche heuristiquement ordonnée (RHO). Il se A* gère deux ensembles, qui contiennent des sommets du graphe. Le premier, appelé
sert d'une fonction d'évaluation h, appelée heuristique, qui définit une estimation inférieure ouvert, contient les sommets examinés lors du parcours. Le second, appelé fermé, contiendra
de la distance entre le sommet courant p et le sommet d'arrivée t. les sommets retenus (ceux dont le coût f est le plus bas). C'est à partir de l'ensemble fermé
On dit que h est minorante si Vp E G, h (p) ( h* (p), où h* (p) est la longueur du chemin qu'à la fin de l'algorithme le chemin solution entre le point d'origine s et le point d'arrivée
le plus cou11 entre p et t. Dans le cas de la résolution d'un taquin, une heuristique habituelle t sera obtenu, en parcourant les parents depuis le sommet final t. Comme pour l'algorithme
est la distance de M A N HATTAN 2 . Si h est minorante, alors A* est dit admissible, c'est-à-dire de DIJKSTRA, le choix d'une file avec priorité mise en œuvre à l ' aide de tas pour représenter
qu'il donne chaque fois un chemin minimal (ou optimal) de s à t, s'il existe. L'algorithme A* l'ensemble ouvert sera plus efficace qu'une liste linéaire.
s'écrit formellement comme suit : La complexité temporelle de A* dépend du choix de l 'hemistique. Le nombre de sommets
Algorithme A* ( G , s, t)
traités peut être, dans le pire des cas, exponentiel par rapport à la longueur du chemin solution.
variables ouvert, fermé Ensemble
Il est clair que plus l'heuristique choisie est proche de h*, l'heuristique optimale, plus A* sera
trouvé +-- faux efficace.
f (s) +-- h ( s ) L'inconvénient majeur de A* est sa gourmandise en espace mémoire. Là encore, dans le
ouvert +-- { s} pire des cas, Je nombre de sommets mémorisés devient exponentiel, et la recherche d'un
répéter
plus court chemin avec A* est alors impraticable même pour les mémoires de très grandes
{retirer l e meilleur sommet de l ' ensemble ouvert
capacités actuelles. IDA* est une variante de A* qui permet de réduire la complexité spatiale.
et l ' ajouter dans fermé}
courant +-- leMeilleur ( ouvert)
ouvert +-- ouvert - { courant } 24.3.3 Algorithme IDA*
fermé +-- fermé U { courant }
si cou rant = t alors trouvé +-- vrai
En 1 985, R. E. KORF propose l'algorithme IDA* (Iterative-Deepening A *) [Kor85] qui,
sinon
contrairement à A* qui traite d' abord le sommet de plus petite évaluation, développe l'état
{poursuivre l a recherche du chemin }
pourtout suce de G t e l qu' 3 un a r c ( courant , s ucc) faire
le plus profond d'abord pourvu que son coût ne dépasse pas un ce1tain seuil. Le très gros
{on traite un successeur du sommet courant} avantage de IDA* sur A*, c'est qu'à tout moment, seuls sont mémorisés les sommets joignant
s au sommet courant p, à l'exclusion de tous les autres. Le nombre de sommets effectivement
f ( succ) +-- g(s, suce) + h ( succ)
parent ( s u c c ) +-- courant mémo1isés est ainsi considérablement réduit, permettant le parcours de graphe de grande
si suce � fermé alors taille.
ouvert +-- ouvert U { suce }
IDA* procède de façon itérative par augmentation progressive du seuil. Au départ, le seuil
sinon {suce déjà présent dans fermé}
est égal à h(s). À chaque itération, IDA* effectue une recherche en profondeur, élaguant
soit aux c e sommet dans fermé
si f ( succ ) < f ( aux) alors
chaque branche dont le coût f = g + h est supérieur au seuil. Si la solution n'est pas trouvée,
fermé +-- fermé - { aux } à chaque nouvelle itération la valeur du seuil est modifiée, elle devient égale au minimum des
-0 -0
0 +-- U { suce }
0 valeurs qui ont dépassé le seuil précédent. Notez qu'à chaque nouvelle itération le parcours
c ouvert ouvert c
::J ::J en profondeur refait tout ou en partie le travail d'exploration du parcours de l'itération pré
0 finsi 0
V
T""l finsi V
T""l cédente. Cela peut sembler coûteux mais R . E . KORF montre qu'en fait ça ne l'est pas car le
0 0
N finpour N plus gros du travail se fait au niveau le plus profond de la recherche. D'autre part, s'il n'existe
@ finsi @ pas de chemin pour atteindre le sommet d'arrivée, il faut prévoir de définir à l'avance un seuil
.....
..c .....
..c
jusqu' à vide ( ouvert) ou t rouvé maximal à partir duquel l'algorithme considérera qu'il n'y a pas de solution. R. E . KORF a
Ol Ol
ï::: si trouvé alors ï:::
>
a. >
a. montré que sous les mêmes conditions que A*, IDA* est complet et admissible. L'algorithme
0 {reconst i t uer le chemin sol ution à partir de l ' ensemble 0
u u IDA* est donné ci-dessous :
fermé en parcourant l e s parents depuis le sommet t d' arrivée}
sinon Algorithme IDA* ( G , s, t)
{ l ' ensemble ouvert est vide � pas de solution} variables chemin liste
finsi résolu booléen
seuil, seui l_max réel
seuil f- h ( s )
2. Pour deux points x et y de !R n , leur distance de MANHATTAN est égale à 2:::�1 lxi - Yi j. résolu f- faux
seui l_max f- . . .
24.4 Tri topologique 357 358 Chap itre 24 • Algorithmes sur les graphes
répéter une liste linéaire de sommets ordonnés de telle façon qu'aucun sommet n' apparaît avant un
chemin +--- {s } de ses prédécesseurs. Cette relation impose de fait que le graphe soit sans cycle.
seuil +--- rechercheEnProfondeur ( G , s, seu i l )
La relation d'ordre du tri topologique est partielle, et le résultat du tri peut alors ne pas
jusqu' à résolu ou seuil > seui l_max
être unique, dans la mesure où deux sommets peuvent ne pas être comparables. Prenons
si résolu alors
{la file chemin est l a solution } l 'exemple trivial de la figure 24.7. Le tri de ces trois sommets rend les suites < sl s2 s3 >
sinon ou < s2 sl s3 >.
{pas de solution }
finsi
À chaque itération, le parcours en profondeur est assuré par la fonction récursive recher
cheEnProfondeur suivante :
}
Il L contient tous les sommets de g ordonnés
return L ;
Il fin tri Topologique
FIGURE 24.8 Tri topologique. Pour un graphe à n sommets et p arcs, la complexité du tri topologique est, au pire, 0( n +
p) si le graphe est représenté par des listes d'adjacence et O(n2) s'il utilise une matrice
d' adjacence.
E L nbpred
sl, s4 0 nbpred[s2] = 3, nbpred[s3]=2 Si le graphe est représenté par des listes d'adjacence, la création de la table des prédéces
s4 sl nbpred[s2] = 2, nbpred[s3]=1 seurs demande un parcours des n sommets du graphe, et le calcul du demi-degré de chaque
s3 sl, s4 nbpred[s2] = 1 sommet réclame p tests au pire. La complexité est O(n x p) . Notez que cette complexité
s2 sl, s4, s3 peut être ramenée à O(n + p) , si la table est créée lors d'un parcours en profondeur ou en
0 sl , s4 , s3, s2 largeur du graphe. Cette amélioration est laissée en exercice. Si le graphe est représenté par
une matrice, la complexité de la création de la table est toujours au pire O(n2).
24.4.1 L'implémentation en Java Puisqu'il n'a pas de cycle, chaque sommet s du graphe est traité une seule fois dans la
phase de tri proprement dite. Le parcours de ses successeurs est alors proportionnel à p. Il en
Nous programmons le tri topologioque à l'aide de la méthode générique tri Topologique résulte une complexité égale à O(n + p) .
-0 "O
0 donnée ci-dessous. 0
c c
::J ::J
0 L'ensemble E et la liste L sont simplement représentés par deux files dont les éléments 0
24.4.2 Existence de cycle dans un graphe
V
T""l sont des sommets. La table des prédécesseurs est une table d'adressage dispersé dont les V
T""l
0 0
N valeurs sont de type Integer et les clés de sommets. Pour tout sommet, la méthode N
Le tri topologique s'applique à un graphe sans cycle. Mais que se passe-t-il si l'algorithme
@ sommetsAdj acent s renvoie l'énumération de ses successeurs . @
.....
..c .....
.c est appliqué à un graphe qui possède un ou plusieurs cycles ? Une telle situation conduit
Ol Ol au fait qu'il n'existe pas de sommet s tel que nbpred[s]=O. Il en résulte que E est vide et
ï::: public <S> File<S> triTopologique ( Graphe<S> g ) { ï:::
>
a. >
a.
0 Table<Intege r , S > nbPred = null; 0 l' alg01ithme s' an-ête. La liste renvoyée par le tri exclut les sommets qui fonnent le cycle.
u File<S> E = new F i leChaînée< S > ( ) ;
u Le tri topologique est alors un moyen de vérifier l'absence ou la présence de cycle dans un
File<S> L = new F i leChaînée<S> ( ) ; graphe.
nbPred = new HashCodeFermé<Intege r , S > (new CléSommet ( ) ) ;
Il construire l ' ensemble E et l a table des prédécesseurs
for (S s : g) { 24.4.3 Tri topologique inverse
int np = g . demiDegréint ( s } ;
if (np == 0 ) E . enfiler ( s } ; Une autre méthode pour effectuer un tri topologique est de réaliser un parcours en pro
nbPred . a joute r ( new É lément<Integer , S > (np, s) } ; fondeur postfixe du graphe (voir l'algorithme page 240). Chaque sommet s est ajouté dans
la liste L après le parcours de ses successeurs. Cette méthode est appelée tri topologique
24.5 Exercices 361 362 Chap itre 24 • Algorithmes sur les graphes
inverse car la liste obtenue est en ordre inverse, puisque les sommets apparaissent après leurs Exercice 24.3. Montrez sur un exemple que l' algorithme de DIJKSTRA donne un résultat
successeurs. faux si un arc possède une valeur négative.
Pour le graphe de la figure 24.8, cet alg01ithme construit la liste < s2 s3 s4 sl >, et Exercice 24.4. Programmez l'algorithme de DIJKSTRA en utilisant un tas et une boucle qui
propose un second tti topologique valide du graphe, < sl s4 s3 s2 >. ne parcourt que les successeurs de m dans E.
La complexité de cette méthode est celle du parcours en profondeur, c'est-à-dire O(n2 ) si
le graphe est représenté par une matrice d'adjacence et O(max(n,p)) s'il est représenté par Exercice 24.5. Pour rechercher le plus court chemin entre tout couple de sommets d'un
des listes d'adjacence. graphe, il est bien sûr possible d'appliquer itérativement l'algorithme de [Link] en pre
nant chacun des sommets du graphe comme source. L'algorithme de FLOYD donne une so
lution très simple à ce problème. La méthode consiste à considérer chacun des n sommets
24.4.4 L'implémentation en Java d'un graphe, appelons-le s, comme intervenant possible dans la chaîne qui lie tout couple de
sommets (x, y ) . Si la distance d(x, s) + d(s, y ) est infé1ieure à d(x, y), alors on a trouvé une
La programmation de la méthode t r i Topologique Inverse consiste simplement à ap
distance minimale entre x et y qui passe par le sommet s. L'algorithme de FLOYD utilise une
peler la méthode parcoursProfondeurPostf ixe avec une opération qui construit la
matrice carrée n x n pour mémoriser les distances calculées au fur et à mesure. Il s'exprime
liste des sommets. Cette opération implémente l'interface Opération, donnée à la page
comme suit :
242, à laquelle nous avons ajouté la méthode résultat qui retourne un résultat final de
parcours. Algorithme Floyd ( G , d )
{ i n i t i a l i ser la table des distances d}
public class OpérationTriTopo<T> implements Opération<T> { '<fx, y E G , d [ numéro ( x ) , numéro ( y ) ] � valeurArc ( x , y )
private File<T> f ; {calculer tous l e s plus courts chemin s }
public OpérationTriTopo ( } { f = new F i leChaînée<T> ( } ; pourtout s de 1 à n faire
public void exécuter ( T e ) { f . enfiler ( e ) ; } pourtout x de 1 à n faire
public File<T> résultat ( } { return f ; } pourtout y de 1 à n faire
si d [ x , s ] +d [ s , y ] < d[x,y] alors
d[x,y] � d [ x , s ] +d [ s , y ]
La méthode triTopologiqueinverse s'écrit simplement : finsi
finpour
public <S> F i l e < S > t r iTopologiqueinve rs e (Graphe<S> g )
finpour
Opération<S> op = new OpérationTriTopo ( } ;
finpour
g . parcoursProfondeurPost fixe ( op ) ;
rendre d
return op . résultat ( } ;
Appliquez cet algorithme sur le graphe de la page 350. Montrez que cet algorithme est
-0 -0 valide. Quelle est sa complexité ? Est-il plus efficace que celui qui consiste à appliquer n fois
0 0
c c
::J l' alg01ithme de DIJKSTRA ? Est-ce que l'algorithme de FLOYD précédent peut s'appliquer à
5 24.5 EXERCICES 0
des valeurs d'arcs négatives ? Programmez cet algorithme en JAVA.
V
T""l V
T""l
0 0
N N Exercice 24.6. En utilisant une matrice d de booléens (i.e. une matrice d'adjacence), mo
Exercice 24.1. Trouvez l'arbre couvrant du graphe donné par la figure suivante :
@ @
.....
..c .....
..c
difiez l'algorithme de FLOYD afin de calculer la fermeture transitive réflexive du graphe. Cet
O'I Ol alg01ithme est connu sous le nom d'algorithme de WARSHALL.
ï::: ï:::
>
a. >
a.
0 0 Exercice 24.7. Programmez en JAVA, les algorithmes A* et IDA* pour résoudre des ta
u u
quins à 16 cases. Comparez l'efficacité des algorithmes. Utilisez et comparez les heuristiques
suivantes :
1 . le nombre de cases mal placées ;
2. la distance de MANHATTAN.
Exercice 24.8. Modifiez l'algorithme du tri topologique afin de retourner la valeur boo
Exercice 24.2. Montrez qu'il existe un seul arbre couvrant minimal pour un graphe valué léenne vrai si le graphe possède un cycle, et la valeurfaux dans le cas contraire.
connexe (non orienté) dont les valeurs des arêtes sont positives et distinctes.
24.5 Exercices 363 364 Chap itre 24 • Algorithmes sur les graphes
Exercice 24.9. Est-il possible de passer par tous les sommets d'un graphe sans emprunter Dans ce dernier cas, il faut recommencer l'algorithme à paitir d'un des sonunets du chemin
deux fois la même arête ? Essayez de trouver ce chemin sur les deux graphes donnés par la E qui possède une arête dans U non parcourue.
figure 24.9. Montrez que s'il reste des arêtes non parcourues, le graphe partiel comporte toujours un
chemin eulérien. Écrivez en JAVA une méthode qui renvoie, s'il existe, le chemin eulérien
d'un graphe.
(a) (b)
FIGURE 24.9 .
Ce problème est connu sous le nom de chemin eulérien. EULER 3 a démontré la condi
tion nécessaire à l'existence d'un tel chemin dans un graphe connexe : soit il n'existe aucun
sommet de degré impair, soit il existe deux sommets de degré impair. Si tous les sommets du
graphe possèdent un degré pair, il s'agit alors d'un cycle eulérien (i.e. les deux extrénùtés du
chemin sont identiques). S'il existe deux sommets de degré impair, ce sont les extrémités du
chemin. Appliquez cette condition aux deux graphes précédents. Programmez une méthode
qui vérifie cette condition.
Pour trouver le chemin eulérien E d'un graphe G = (X, U), on part d'un sommet source
(on prendra n'importe quel sommet si tous les sommets ont un degré pair, ou de l'un des deux
-o sommets de degré impair), et 1' on procède selon un parcours en profondeur de la forme : -0
0 0
c c
::J
5 Algorithme parcoursEuler ( s , U, E)
0
{Antécédent s sommet origine du parcours V
T""l
U ensemble des arêtes du graphe à parcourir 0
N
Conséquent E ensemble des arêtes parcourues et U=U-E} @
.....
..c .....
..c
Ol si 3 v E X t e l que 3 arête ( s , v ) E U alors
Ol
ï::: ï:::
>
a. >
a.
parcoursEuler ( v , U- [ s , v ) , E U [ s , v) )
0 0
u finsi u
Au départ, l'ensemble E des arêtes qui forment le chemin eulérien est initialisé à 0.
Lorsque cet algorithme s'achève, deux cas de figure se présentent : soit U = 0 et le che-
. '
min eulérien de G a été trouvé, soit U =!= 0 et seule une partie du graphe a été parcourue.
3. L . EULER, mathématicien suisse ( 1 707-1783). Il a souvent été dit que la résolution de ce problème marque
l'origine de la théorie des graphes.
366 Chapitre 25 • Algorithmes de rétro-parcours
La récursivité permet les retours en arrière lors du parcours de l'arbre. Lorsque la solution
partielle est invalide, et l'ensemble des possibilités à l'étape i est épuisé, l'achèvement de
la procédure permet de revenir à l'étape précédente. Cette première version recherche une
solution pa1ticulière, la première.
Algorithme e ssay e r ( i , corre ct e )
{Antécédent la solution partielle jusqu'à l ' étape i -1
est correcte}
{Conséquent correcte = solution part i e l l e à l ' étape i
valide ou pas}
Chapitre 2 5 {Rôle : essaye d ' é t endre l a solution à la ième étape}
{ i n i t i a l i sa t i on de la l i s t e des possibi l i t és }
k +- {candidat i n i t i a l }
répéter
Algorithmes de rétro-parcours {prendre le prochain candidat dans l a l i s t e des possibi l ités}
k +- {candidat suivant }
{vérifier si l a solution part i e l l e à l ' étape i est correcte}
vérifier ( i , ok )
s i o k alors
enregistrer ( i , k )
s i non fini ( i ) alors
essaye r ( i + l , correcte)
s i non correcte alors
Les problèmes qui mettent en jeu des algorithmes de rétro-parcours sont des problèmes { l e choix k à l a i ème étape conduit à une impasse}
pour lesquels l' algmithme solution ne suit pas une règle fixe. La résolution de ces problèmes annuler ( i , k )
se fait par étapes et essais successifs. À chaque étape, plusieurs possibilités sont offertes. On finsi
en choisit une et l'on passe à l'étape suivante. Ces choix successifs peuvent conduire à une sinon {on a trouvé une solution}
solution ou à une impasse. Dans ce dernier cas, il faudra revenir sur ses pas et essayer de correcte +- vrai
nouvelles possibilités, éventuellement jusqu'à leur épuisement. C'est une démarche que l'on finsi
adopte, par exemple, dans le parcours d'un labyrinthe. finsi
jusqu' à correcte ou plus de candidats
Toutes les étapes, ainsi que les choix que l'on peut faire à chacune de ces étapes, modé
lisent un arbre de décision. Chaque nœud de cet arbre est une des étapes où sont proposés
-0 les choix. La recherche d'une solution consiste donc à parcourir cet arbre. Les branches qui -0
i
0 0 Dans cet algorithme, la [Link] teste si ]a dernière étape est atteinte ou pas, la fonction
c s'étendent de la racine aux feuilles terminales de l'arbre sont les solutions potentielles. En gé c vérifier teste la validité de la solution partielle à l'étape i, la procédure enregistrer mémorise
::J ::J
0 néral, les méthodes de rétro-parcours ne constmisent pas explicitement cet arbre de décision, 0
V V dans la solution partielle le candidat k à l'étape i, et la procédure annuler efface de la solution
T""l il est purement virtuel. T""l
0
N
0
N
partielle le candidat k à 1 'étape i.
@ Dans ce chapitre, nous présenterons les écritures récursives et itératives des algorithmes @
..... ..... Pour obtenir toutes les solutions du problème, il suffit, à chaque étape, d'essayer tous
..c de rétro-parcours donnant, si elles existent, une solution particulière ou toutes les solutions ..c
Ol Ol les candidats possibles. Remarquez que cela correspond au parcours complet de l'arbre de
ï::: possibles. Nous utiliserons ces algorithmes pour résoudre le problème des huit reines, et celui ï:::
>
a. >
a. décisions.
0 des sous-suites. Enfin, nous terminerons par une application aux jeux de stratégie à deux 0
u u
joueurs en présentant la stratégie MinMax et son amélioration par la méthode de coupure Algorithme essayer ( i )
si ok alors
'
' ,
,
c /
enregistrer ( i , k )
'
'
annul e r ( i , k ) ' ,
' ,
finsi '
' /
/
finpour
Q
' /
X
, '
/ '
, '
, '
, '
/ '
,
,
/
,
,
,
,
/
Ce problème, proposé par C. F. GAUSS en 1 850, consiste à placer huit reines sur un échi '
'
�
quier sans qu'elles puissent se mettre en échec mutuellement (selon les règles de déplacement
l+c 1-c
des reines). Il n'y a pas d'algorithme direct donnant une solution et un algorithme de rétro
parcours doit être utilisé. FIGURE 25.1 Les deux diagonales issues de la position (l,c).
Nous connaissons l'alg01ithme, nous allons nous intéresser aux stmctures de données. Il
est évident qu'on ne pourra placer qu' une reine par colonne. Pour chaque colonne c, le choix Et pour annuler une reine qui était en position (Z, c) :
se réduit donc à la ligne l sur laquelle poser la reine. À partir de ce qui vient d'être dit, il ligne [ l ] +-- diagl [ l +c ] +-- diag2 [ 1 - c ] +-- vrai
est inutile de représenter l 'échiquier par une matdce 8 x 8, un tableau de huit positions suffit
pour représenter une solution : Enfin, vé1ifier si la position (l, c) est coITecte s'écrit de façon évidente :
solution type tableau [ [1, 8) ] de [1, 8) ligne [ l ] et diagl [ l +c ] et diag2 [ 1- c ]
L'algorithme va placer une reine par colonne. Vérifier si une reine est correctement placée Nous pouvons maintenant écdre en JAVA la solution complète qui donne les quatre-vingt
nécessite de vérifier s'il n'y a pas de conflit sur la ligne et sur les deux diagonales. Cette douze solutions de ce problème. Les déclarations des quatre tableaux sont les suivantes :
vérification doit rester simple. Nous utiliserons trois tableaux de booléens, ligne, diagl et
int [] solution = new int [ 8 ] ;
diag2 tels que pour une ligne l et une colonne c :
boolean [ ] ligne new boolean [ 8 ] ;
- ligne [ 1 ] indique si la ligne l est libre ou non ; boolean [ ] diagl new boolean [ l 5 ] ;
-0 - di ag 1 [ k J indique si la ke diagonale / est libre ou non ; -0 boolean [) diag2 new boolean [ l 5 ] ;
0 0
c - diag2 [ k ] indique si la ke diagonale ")i est libre ou non. c
::J ::J
0 0 Comme l'indice du premier élément de ces tableaux est toujours égal à zéro, le calcul d'in
V
T""l
La figure 25.1 montre bien à quoi correspondent diagl et diag2 et comment représenter V
T""l dice pour accéder aux composants devra subir une translation égale à 1 pour les tableaux
0 0
-
N k en fonction de l et c. N solution et ligne, égale à -2 pour le tableau diagl, et égale à +7 pour le tableau
@ On voit donc qu'à partir de la position (l, c) , la diagonale / conespond à l'indice l + c et @ diag2. La méthode es sayer s'écrit :
.....
..c .....
..c
Ol la diagonale ")i à l'indice l c. Puisque l et c varient de 1 à 8, nous pouvons en déduire les Ol
ï::: ï:::
-
> > I** Antécédent : c-1 reines ont correctement été placées sur l e s
a. déclarations de nos trois tableaux : a.
0 0 * c-1 premières colonnes
u u * Rôle : essayer de placer l a c \ ieme reine dans la c \ ieme colonne
ligne type tableau [ 1 , 8) ) de booléen
diagl type tableau [12, 16) ] de booléen *I
diag2 type tableau [-7, 7 ) ] de booléen void e s s aye r ( int c} {
int i ;
Pour enregistrer une reine en position (l, c) , il suffit d'écrire : for ( int l=l ; 1<=8; l++) {
Il vérifier si on peut placer l a ce reine en (l, c)
solution [ c ] � l if ( l igne [ l - 1 ) && diag l [ l + c - 2 ) && diag2 [ 1 -c+ 7 ] )
ligne [ l ) � diagl [ l +c] � diag2 [ 1 - c ] � faux Il enregistrer la ce reine en (l, c)
solution [ c - 1 ] = 1 ;
25.3 Écriture itérative 369 370 Chapitre 25 • Algorithmes de rétro-parcours
de l'algorithme s'appuie sur une procédme régresser dont Je rôle consiste à trouver l'étape i { l a solution partielle à l ' étape i est correcte}
s i correcte alors
à laquelle un nouveau candidat peut être proposé. Si une telle étape ne peut être trouvée, elle
si fini ( i ) alors
signale une impasse. Son algorithme s' exp1ime plus formellement :
{on a t rouvé une solution}
Algorithme régresser ( i , impasse) écrireSolution
impasse � faux régresser ( i , irnpasse)
tantque candidat à l ' étape i dernier candidat possible faire sinon
i � i-1 i � i+l
fintantque étendre ( i )
si i=O alors impass e � vrai finsi
sinon sinon { l a solution partielle n ' es t pas correcte}
passer au candidat suivant de l ' étape i régresser ( i , impasse ) ;
fins i finsi
si non impasse alors vérifie r ( i , correcte) finsi
jusqu' à impasse
L'algolithme itératif de rétro-parcours qui recherche une solution possible est donné ci
dessous :
-0 'O
0 0
c initialisation de la solution partielle à vide c
::J
0 initialisation des différentes possibilités 5 25.4 PROBLÈM E DES SOUS-SUITES
V
T""l impasse � faux V
T""l
0 étape � 0 0
N N Ce problème consiste à construire une suite de n éléments, pris dans un ensemble de m va
@ répéter @ leurs, telle que deux sous-suites adjacentes ne soient jamais égales. Par exemple, < 1 2 1 3 >
.....
..c {passer à l ' étape suivante} .....
..c
Ol Ol et < 2 3 2 1 > sont de telles suites de longueur 4 construites sur l'ensemble { 1 2 3} .
ï::: i � i+l ï:::
>
a. >
a.
0 étendre ( i ) 0 Ce problème est résolu avec un algorithme de rétro-parcours, et nous donnerons sa version
u vérifie r ( i , correcte)
u itérative. Les valeurs des éléments de la suite sont des entiers positifs inférieurs à une valeur
tantque non ( correcte ou impa s s e ) faire valeurMax et la longueur de la suite est égale à longSuite. Le nombre d'étapes pour
régresse r ( i , impasse) atteindre une solution est donc au plus égale à longSui te. La solution est représentée par
si non impasse alors un tableau d'entiers et le numéro de l'étape courante sert d'indice pour accéder au dernier
vérifie r ( i , correcte) candidat de la solution partielle.
finsi
fintantque protected int [ ] solution;
{ impasse ou solution à l ' étape i correcte} int i ;
jusqu' à impasse ou fini ( i )
25.4 Problème des sous-suites 371 372 Chapitre 25 • Algorithmes de rétro-parcours
La méthode régre s s e r décrémente la valeur d'étape i tant qu'elle ne peut proposer de Enfin, la méthode solution qui cherche une solution particulière du problème des sous
nouveau candidat à cette étape. Lorsque i est égal à zéro, l ' impasse est atteinte. suites est programmée comme suit :
private boolean régresser ( ) { public boolean solution ( ) {
while ( i ! =O && solution [ i- l ] ==valeurMax) boolean impasse=false;
i--; i=O ;
if ( i==O) return true; do {
solution [ i- 1 ] ++; Il é tendre l a solution
return false; solution [ i+ + ] =l ;
boolean correcte=vé r i f ie r ( i ) ;
while ( ! ( correcte 1 1 impasse ) )
La vérification de la validité de la solution partielle, c'est-à-dire vérifier si la suite ne impasse=régresser ( ) ;
comporte pas deux sous-suites identiques, consiste à tester toutes les sous-suites de longueur if ( ! impasse) correcte=vé r i f i e r ( i ) ;
égale à un jusqu'à la moitié de la longueur et faisant intervenir le candidat choisi à l 'étape
i 1 . La figure 25 2 montre la progression de la taille des sous-suites vérifiées en partant de
- .
while ( ! impasse & & i==longSuite ) ;
la fin de la suite. return ! impasse;
3 3 3
cet arbre correspond à un coup final d' une paitie fictive, et à laquelle sont associées trois va pourtout i de 1 à longueu r ( forêt ( a ) ) faire
leurs possibles : partie gagnée, nulle ou perdue. Les nceuds de l' arbre correspondent, soit à un v f-- MinMax ( i èmeArbre (forêt ( a ) , i } )
coup joué par 1 ' ordinateur, soit par son adversaire virtuel, et chacun d'eux contient une valeur si v > max alors
max f-- v
qui représente le meilleur coup joué (du point de vue de l'ordinateur). La stratégie MinMax
finsi
doit son nom au fait qu'elle cherche à maximiser la valeur des coups joués par l'ordinateur et
finpour
à minimiser celle des coups joués par l'adversaire virtuel.
rendre max
Lorsque l'ordinateur joue, il évalue récursivement selon la stratégie MinMax tous les coups sinon { l ' adversaire virt u e l }
possibles pour ne retenir que le meilleur. Lorsque son adversaire virtuel joue, la stratégie de {choisir l a valeur minimale des fi l s }
l'ordinateur est de retenir le moins bon coup. Il est, par exemple, évident qu'un coup perdant min f-- Gagnée
pour l'adversaire, est à conserver puisqu'il conduit à la victoire de l'ordinateur. Récipro pourtout i de 1 à longueu r ( forêt ( a ) ) faire
quement, un coup gagnant pour l' adversaire est à éliminer puisqu'il conduit à la défaite de v f-- MinMax ( i èmeArbre (forêt ( a ) , i } )
si v < min alors
l'ordinateur. Pour un nceud donné de l'arbre de jeu, la stratégie MinMax renvoie la meilleure
min f-- v
valeur pour 1 'ordinateur.
finsi
finpour
Max rendre min
finsi
finsi
Min Il est important de bien comprendre que les programmes, qui mettent en œuvre cette mé
thode, ne construisent pas au préalable les arbres de jeu à parcourir. Ce sont les règles du jeu
et la façon d'obtenir la liste des coups possibles à chaque étape qui déterminent le parcours
d'un arbre de jeu implicite.
Max Nous donnons maintenant la programmation en JAVA de l' algorithme précédent. Nous
considérerons qu'un coup à jouer, de type Coup, est formé d'une position dans le jeu et
d' une valeur. La position est, par exemple, une case d'un damier ou d'un échiquier, et la
FIGURE 25.3 Un arbre de jeu. valeur d'un coup est prise dans l'ensemble ordonné {Perdue, Nulle, Gagnée}. Notez qu'il
est également possible de compléter, si nécessaire, ce type par la valeur d'un pion (e.g. un
La stratégie MinMax cotTespond donc à un parcours en profondeur d'un arbre de jeu com cavalier noir ou une tour blanche aux échecs). Nous définissons également un objet jeu qui
posé alternativement de niveaux Max et de niveaux Min (voir la figure 25.3). Les nceuds des pe1met d'enregistrer ou d'annuler un coup, de renvoyer une énumération des positions libres,
-0 -0
0 0 d'indiquer si un coup est gagnant ou non, ou encore si la partie est dans une situation de nulle
c niveaux Max sont les coups de l'ordinateur dont les valeurs sont le maximum de celles de c
::J ::J
0 leurs fils. Les nceuds des niveaux Min sont les coups de l' adversaire virtuel dont les valeurs 0 ou non. La méthode MinMax donnée ci-dessous tient compte de la symétrie de la méthode
V
T""l V
T""l de jeu, ce qui permet de supprimer le test sur la nature du nœud courant. Pour cela, il suffit
0 sont le minimum de celles de leurs fils. La racine de l'arbre de jeu est située à un niveau Max, 0
N dont la valeur est le meilleur coup à jouer pai· l'ordinateur. L' alg01ithme suivant exprime ce N d'inverser la valeur du meilleur coup du joueur adverse.
@ @
.....
..c
parcours d'arbre . .....
..c l e coup final n ' est pas encore trouvé
I * * Antécédent
Ol Ol
ï::: Algorithme MinMax ( a : arbre de j e u ) ï::: * Conséquent l e meilleur coup à jouer est renvoyé
>
a. >
a.
0 {Rôle : parcourt en profondeur l ' arbre de jeu a et renvoie 0 *I
u la va leur du me i l l eu r coup à jouer par l ' ordinateur}
u Coup MinMax ( )
s i feuille ( a ) alors Coup meilleurCoup = new Coup ( P e rdue ) ;
{ coup final J É numération posLibre = j eu . positionLibres ( ) ;
rendre val e ur ( a ) { i . e . gagnée, nulle ou perdue} Il essayer tous l e s coups disponibles possibles
sinon do
si typeNoeud ( a ) =ordinateur alors Coup coup = new Coup (Perdue, posit ionLibre . suivant e ( ) ) ;
{choisir l a valeur maximale des fil s } j eu . enregist e r ( coup ) ;
max f- Perdue Il vérifier l e coup
if ( j eu . coupGagnant ( coup) )
25.5 Jeux de stratégie 375 376 Chapitre 25 • Algorithmes de rétro-parcours
Pour la plupart des jeux, le nombre de coups testés est très important. Pour un jeu simple
comme le tic-tac-toe 3 (aussi appelé morpion), le premier coup joué par l'ordinateur nécessite coupure a
de parcomir un arbre de 29 633 nœuds si Je joueur humain joue son premier coup au centre de
la grille, de 3 1 973 nœuds si son premier coup est dans un coin, et de 34 3 1 3 nœuds pour une Min
autre case de la grille. Si l'ordinateur joue le premier, l'arbre de jeu initial possède 294 778
nœuds qu'il devra parcourir avant de jouer son premier coup 4 .
Une première amélioration évidente de l'algorithme précédent est d'arrêter Je parcours
des fils d'un nœud lorsque la valeur maximale attendue par le meilleur coup est atteinte. Le
Max
prédicat d'achèvement de l'énoncé itératif est simplement modifié comme suit :
do {
FIGURE 25.4 Coupure a.
4. En fait, l'ordinateur pourrait choisir au hasard n'importe quelle première case. Elles sont toutes équivalentes
pour le premier coup.
25.5 Jeux de stratégie 377 378 Chapitre 25 • Algorithmes de rétro-parcours
nœuds traités à cette profondeur sont considérés comme des feuilles et leur valeur est calculée Exercice 25.5. Soit un entier rn et un ensemble de n entiers E = { x 1 , x 2 , . . . , Xn }, cher
par une fonction qui estime l'état de la partie à ce moment-là. chez un sous-ensemble de E tel que la s01mne de ses éléments soit égale à m.
Algorithme MinMax ( a : arbre de j e u , a, {3, niveau) Exercice 25.6. Soit une tôle formée d'un ensemble de carrés identiques et adjacents. On
si niveau = 0 alors désire découper dans cette tôle un certain nombre de pièces identiques. Le découpage doit se
{profondeur maximale a t te i n t e } faire sans aucune perte et en suivant uniquement les côtés des caiTés qui composent la tôle.
rendre une estimation de la partie e n cours
sinon Construire un progra1mne qui imp1ime, si elle existe, une solution de découpage. Les don
si feui l l e { a ) alors nées du programme doivent exprimer les formes et les dimensions de la tôle et des pièces. La
figure 25.6 montre un exemple de tôle avec trois pièces à découper.
sinon tôle pièce 1
{a est un noeud ordinateur ou de son adversaire virtuel }
EP
si typeNoeud ( a ) = ordinateur alors
finsi
finsi
finsi
5
- ----
-0 25.6 EXERCICES -0 arrivée
0 0 4
c c
::J ::J 1- - - - -
0 Exercice 25.1. Écrivez de façon itérative le programme des huit reines. 0
V
T""l V
T""l
0 Éclivez un programme qui vérifie s'il est possible de passer par toutes les 0 3
N Exercice 25.2. N �----
@ cases d'un échiquier, mais une seule fois par case, à l'aide d'un cavalier selon sa règle de @
..... .....
..c ..c 2
Ol déplacement aux échecs. Ol
ï::: ï::: �----
>
a. >-
a.
0 Exercice 25.3. Modifiez le programme précédent pour qu'il recherche toutes les solutions. 0 départ
u u 1
Exercice 25.4. Le jeu Le Compte est bon de la célèbre émission télévisée Des chiffres et «
des lettres consiste à obtenir un nombre tiré au hasard entre 100 et 999 à partir des quatre
»
1 2 3 4 5
opérations élémentaires ( +, - , x , -7-) p01iant sur des entiers naturels tirés au hasard parmi
FIGURE 25.7 Un labyrinthe.
vingt-quatre nombres (deux séries de nombres de 1 à 10, et 25, 50, 75 et 100).
En utilisant l'algorithme de rétro-parcours récursif, écrivez un programme JAVA qui affiche Proposez une représentation pour le labyrinthe, puis en utilisant un alg01ithme de rétro
une suite d'opérations, si elle existe, nécessaire au calcul du nombre recherché. parcours, écrivez un programme qui renvoie, s'il existe, le chemin entre le point de départ et
le point d' aiTivée.
25.6 Exercices 381
Exercice 25.8. Utilisez et comparez les algorithmes A* ou IDA* avec l'algorithme de rétro
parcours que vous avez programmé précédemment pour trouver votre chemin dans le laby
rinthe.
Exercice 25.9. Programmez le jeu du tic-tac-toe. Quelle est la pronfondeur la plus grande
d'un arbre de jeu ?
Exercice 25.10. Le jeu du 31 se pratique à deux joueurs et un dé. À tour de rôle, les joueurs
font progresser une somme paitielle pai· additions successives de la valeur du dé. Au début,
le dé est lancé au hasai·d, et la somme partielle est initialisée à zéro. Ensuite, chaque joueur
tourne d'un quart de tour le dé sur une des quatre faces adjacentes à celle visible du coup
précédent. La nouvelle valeur du dé est ajoutée à la somme partielle. Le premier joueur qui
atteint la somme 31 a gagné, celui qui dépasse 3 1 a perdu. Programmez ce jeu.
Exercice 25.11. Le jeu hexxagon se déroule sur un plateau de jeu hexagonal comprenant
un nombre quadratique de cases (voir figure 25.8). Au début de la partie, chaque joueur
dispose de deux pions d'une couleur donnée. Les joueurs jouent à tour de rôle. Deux types
de déplacement sont possibles :
- le clonage : un pion peut se dupliquer sur l'une de ses six cases adjacentes à condition
qu'elle soit libre,
- le saut : un pion peut sauter à deux cases de distance.
-0 -0
0 0
c c
::J ::J
0 0
V
T""l «:!"
T""l
0 0
N N
(d) (e) (t)
@ @
.....
..c ......
..c
Ol FIGURE 25.8 (a) Situation initiale; (b) clonage; (c) clonage; (d) saut; (e) clonage
Ol
ï::: ï::
>
a. >-
0..
avec prise; (f) saut avec prise.
0 0
u u
Lorsqu'un pion est posé sur une case, tous les pions présents sur les six cases adjacentes
prennent la couleur du pion déplacé. Le jeu se termine lorsqu'il n'y a plus de cases libres sur
le plateau. Le vainqueur est le joueur qui possède le plus de pions de sa couleur.
Programmez ce jeu pour deux joueurs, l'ordinateur et un joueur humain, puis pour quatre
joueurs, l'ordinateur et trois joueurs humains.
384 Chapitre 26 • Interfaces graphiques
Jusqu'au milieu des années 1980, l'inte1face était essentiellement textuelle. Les terminaux
d'accès aux systèmes d'exploitation étaient presque exclusivement de type alphanumétique,
et ne permettaient l'affichage que d'un nombre limité de lignes de caractères pris dans le
jeu ASCII. D'un point de vue logiciel, ces terminaux n'offraient donc que des interfaces tex
tuelles, c'est-à-dire que l' utilisateur communiquait avec le système uniquement par des com
mandes rédigées dans un langage donné et interprétées par un interprète de commandes. Les
interprètes de c01m11andes des systèmes d'exploitation, comme un shell du système UNIX,
fonctionnent selon ce principe. L'intérêt de ce type d'interface est la richesse du langage
de commandes qui permet de définir ou d'inventer des comportements non prévus. En re
Chapitre 26 vanche, l'apprentissage du langage et la saisie des commandes sont certainement une source
de difficultés, particulièrement pour les non-informaticiens. D'autre part, les tenninaux al
phanumériques n'offrent pas, en général, la possibilité de visualiser l'exécution simultanée
de plusieurs tâches.
Interfaces graphiques
Bien que les inte1faces exclusivement textuelles n'aient pas totalement disparu, aujour
d'hui l'interface habituelle est graphique. Elle nécessite un terminal graphique, capable d'af
ficher une matdce de points (allant de 1 9 20 x 1024 pour un smartphone, à 3840 x 2160
pour les meilleurs écrans LCD) et un système de fenêtrage à l'aide duquel on dessine des
caractères, au même titre que des schémas, des images, etc. Un dispositif de pointage, en
général une souris, est toujours associé à l'écran. Il permet de désigner ou de manipuler des
parties de l'image graphique de l'écran. Pour son dialogue avec l'ordinateur, l'utilisateur se
Les applications interactives communiquent avec l'utilisateur au moyen d'interfaces. se1t de la souris et accède aux fonctionnalités du système grâce à des menus déroulants ou à
Parmi elles, les inte1faces graphiques ont révolutionné les méthodes de dialogue avec l ' ordi des boutons spécifiques. Déplacer la souris permet d'amener le pointeur sur la zone désirée
nateur et ont simplifié son utilisation. Ce type d'interface a été élaboré dès la fin des années de l'écran. Les combinaisons de clic simple ou clic double et de clic suivis d'un déplace
1960 dans les laboratoires de la compagnie XEROX, mais c'est vraiment au début des années
ment avec son bouton enfoncé permettent d'indiquer des actions, ou des « copier-coller ». Le
1980 que l'interface purement graphique du système du MACINSTOSH a permis l'accès à
système répond en affichant des menus et en faisant apparaître des fenêtres, c'est-à-dire des
l'ordinateur au plus grand nombre, et en particulier aux non-informaticiens. Aujourd'hui, zones de l 'écran dans des cadres qui affichent une infom1ation particu1ière.
la grande majorité des systèmes d'exploitation et des applications, et particulièrement dans Les intetfaces graphiques exclusivement à base de menus déroulants, de boutons, ou de
l'informatique individuelle, dispose d'une interface graphique. boîtes de dialogue facilitent l 'interaction, mais ont des limitations. Le plus souvent, elles ne
Après avoir décrit la notion de système interactif, nous nous intéresserons plus particu permettent qu'une utilisation passive, avec un dialogue immuable fixé par l'enchaînement
lièrement dans ce chapitre aux interfaces graphiques. Nous présenterons les caractéristiques des menus et des boîtes de dialogue. Elles n'offrent pas la possibilité de créer de nouveaux
-0 -0
0 0 comportements par l'extension ou la composition de comportements existants. L'utilisation
c principales des systèmes de fenêtrage, celles des fenêtres qu'ils manipulent et des outils de c
::J ::J
0 construction d'interfaces graphiques. Enfin, à travers quelques exemples simples, nous ver 0 poussée à l'extrême d'une interface graphique dispense de l'emploi du clavier, mais rapide
V
T""l V
T""l ment, on s'aperçoit que l' usage exclusif de la souris devient fastidieux, et le clavier reprend
0 rons les principes de base de la programmation des applications graphiques en JAVA avec 0
N N ses droits. En revanche, les interfaces à manipulation directe, comme les éditeurs de texte ou
SWING.
@ @ de schémas, offrent à l'utilisateur une représentation graphique des objets informatiques et
..... .....
8
..c ..c
Ol Ol en permettent une manipulation par l'intermédiaire de la souris ou du clavier. Ce type d'in
ï::: ï:::
>- >
a. te1face permet une interaction créatrice qui n'est plus limitée par les règles fixes d'utilisation
26.1 SYSTÈM ES INTERACTIFS u
0
des menus déroulants ou des boîtes de dialogue.
Une interface utilisateur désigne à la fois l 'équipement matériel et les outils logiciels qui Les interfaces des ordinateurs actuels font essentiellement appel au sens visuel de l'uti
pem1ettent à 1 'utilisateur d'assurer une communication avec l'ordinateur. Dans le passé, les lisateur (affichage sur l'écran), mais d'autres types d'interfaces permettent de combiner les
ordinateurs enchaînaient l'exécution des programmes sans attente. Le mode de communica autres sens et définissent de nouveaux modes de communication entre l'homme et la ma
tion était le traitement par lots (mode batch en anglais). Aujourd' hui, la communication avec chine. Avec des haut-parleurs, l' utilisateur peut « entendre » l'ordinateur, ou lui « parler » par
l'ordinateur est interactive, et l'utilisateur instaure un véritable dialogue avec l'ordinateur. reconnaissance vocale à l'aide d'un micro.
Dans ces systèmes interactifs, on peut distinguer deux grands types d'interfaces : textuelles De plus en plus, les écrans sont tactiles, ceux des smartphones et des tablettes numériques,
et graphiques. mais également ceux des ordinateurs. Les doigts de la main remplacent la souris, mais avec
moins de précision.
26.2 Conception d'une application interactive 385 386 Chapitre 26 • Interfaces graphiques
Citons également les systèmes de réalité virtuelle qui permettent une interaction avec tous du coup joué, d'exécution du coup suivant selon un algorithme de coupure a-/3, etc. Les
les sens de l'utilisateur dans une représentation 3D du monde. Il est aisé d'imaginer toutes réponses associées aux coups joués par l'ordinateur, ou aux prises de pièces produiront en
sortes de combinaisons d'interactions multimodales. Ces types d'interfaces sont pour ]'instant sortie des déplacements ou des suppressions de pièces à l'écran. Dans ce logiciel, un retour
beaucoup moins fréquentes que les interfaces simplement graphiques, mais vont sans aucun d'information d'une commande sera, par exemple, un changement d' apparence d'une pièce
doute se développer avec les progrès technologiques à venir 1 , et deviendront, peut-être, les déplacée ou de son suivi à l'écran lors du déplacement, qui ne provoquera pas l'exécution
interfaces habituelles des systèmes interactifs de demain. d'une opération du noyau. Cette dernière ne sera, en fait, exécutée que lorsque la pièce sera
effectivement posée par le joueur sur une case de l'échiquier.
Durant les dernières décennies, plusieurs modèles d'architecture logicielle ont été pro
26.2 CONCEPTION D'UNE APPLICATION INTERACTIVE posés pour structurer les interfaces des systèmes interactifs. Ils visent tous une séparation
claire entre l'interface utilisateur et le noyau fonctionnel afin de faciliter la construction des
Une application interactive est généralement formée de deux composants principaux, systèmes interactifs.
l ' interface utilisateur et le noyau fonctionnel. L'intedace permet le dialogue entre l' utilisa Le modèle de SEEHEIM 2 [Pfa85] repose sur un modèle linguistique (lexical, syntaxique,
teur et le noyau de l'application qui assure les fonctions de calcul. L'utilisateur applique des sémantique) du dialogue homme-machine. Il définit l'interface utilisateur comme un module
commandes à l'aide de dispositifs d'entrée qui agissent sur le noyau fonctionnel. En retour, ce distinct formé de trois composants (voir la figure 26.2). Le premier appelé présentation,
dernier produit des réponses grâce à des dispositifs de sortie. Les commandes de l'intedace le composant lexical, s'occupe de la visualisation des objets graphiques ; le deuxième, le
utilisateur exécutent des opérations internes au noyau fonctionnel qui agissent sur ses propres contrôleur de dialogue, le composant syntaxique, définit la structure de l'interaction entre
structures de données. L'interface offre donc à l'utilisateur une représentation des structures l' utilisateur et le noyau fonctionnel ; enfin, le troisième, appelé interface, le composant
internes de l'application. Notez que certaines commandes de l'interface utilisateur peuvent sémantique, établit le lien avec les fonctionnalités du noyau. Ce dernier est aussi appelé
provoquer des retours d'information sans pour autant provoquer l'exécution d'opérations du adaptateur du noyau fonctionnel.
noyau. La figure 26.1 montre cette structure.
( utilisateur
1
Noyau
fonctionnel
----- �
L::J ___ ,
Contrôleur de
dialogue ,___, Présentation ----- Utilisateur
entrées
-- -- -- - - retours sorties
1 !
--- - --- - - - - - - -- - -
- -- - -- - -- -- -
interface , commandes réponses
utilisateur :1 1
La caractélistique principale du modèle langage est sa forme séquentielle et centralisée des
L -- - - - - - - - - - -- - - - - -1
_ _
- -- - - - - - --------- --
traitements. Toutefois, elle ne correspond pas forcément au comportement des utilisateurs, en
particulier avec des interfaces à manipulation directe.
-0 -0
0 0 Un autre modèle important, le modèle multi-agents organise l' architecture de l'inter
c opérations c
::J ::J
0 0 face utilisateur autour d'un ensemble d'objets interactifs répartis. Plusieurs modèles ont été
V
T"'l noyau V
T""l construits selon ce p1incipe. Le premier d'entre eux, et le plus connu, est le modèle MVC
0 fonctionnel 0
N N (Model-View-Controller) [KP88] . Dans ce modèle, l'application interactive est construite
@ @ à partir de modèles (models), qui sont les composants logiciels internes du noyau fonc
.....
..c structures de données internes .....
..c
Ol Ol tionnel. À chaque modèle, on peut associer un ou plusieurs couples View/Controller. Une
ï::: ï:::
>
a. >
a. vue (view) gère la visualisation du modèle, et Je contrôleur (controller) assure l'interface
0 0
u u entre les modèles et les vues, auxquels il est lié, à partir des entrées de l' utilisateur. Ainsi
FIGURE 26.1 Structure d'une application interactive. quand ce dernier réalise une action sur un dispositif d'entrée (clavier, souris . . . ), le contrôleur
en irlforme le modèle associé qui modifie ses structures de données. Le modèle notifie en
Pour illustrer ces propos, prenons l'exemple d'une interface utilisateur d'un logiciel de retour son changement d'état à ses différents contrôleurs et vues qui feront leurs propres
jeu d'échecs qui visualise graphiquement sur ]'écran ]'échiquier et les pièces sous forme changements d'états qui s'imposent (voir la figure 26.3). À l'origine, le modèle MVC a été
d'icônes. Ses commandes, comme le déplacement d'une pièce sur l'échiquier à l'aide d'une mis en œuvre dans le langage à objets SMALLTALK [GR89]. On le retrouve aussi, sous des
souris, provoqueront l'exécution des opérations du noyau fonctionnel de contrôle de validité formes adaptées, dans d'autres langages, dont JAVA.
1 . Voir les projets en cours de lunettes pour une réalité augmentée par Google. 2. Il doit son nom au lieu de la conférence dans laquelle il a été présenté.
26.3 Environnements graphiques 387 388 Chapitre 26 • Interfaces graphiques
d'autre part. L'originalité du système X, puisqu'on ne la retrouve dans aucun autre système
de fenêtrage, est de séparer ces deux activités selon une relation client-serveur. Les clients
sont des applications qui assurent des calculs et qui produisent des requêtes d'affichage à
destination du serveur. Le serveur est un programme qui gère le dispositif physique formé de
l'écran graphique, du clavier alphanumérique et de la souris. Il traite les ordres d'affichage
à l'écran et reconnaît les événements émis par le clavier ou la souris, et informe les clients
concernés si nécessaire. Les événements qui se produisent sur un terminal sont par exemple
entrées __ affichage
(clavier, souris... ) (écran)
le déplacement de la souris, l' appui sur une touche du clavier, ou un bouton de la souris, le
recouvrement d'une fenêtre par une autre, etc. Les clients et le serveur d'affichage peuvent
s'exécuter sur le même ordinateur ou sur des machines différentes communiquant alors par
FIGURE 26.3 Le modèle MVC. l'intermédiaire du réseau selon les protocoles réseaux TCP/IP ou DECnet. La propriété fon
damentale du système X est d'être indépendant des ordinateurs utilisés. Seul le serveur X
Ces premiers modèles ont eu de nombreux successeurs comme le modèle Arch [BFL+ 92] dépend du terminal qu'il gère. Ainsi, plusieurs clients X qui s'exécutent sur des ordinateurs
qui est une extension du modèle de SEEHEIM, ou encore le modèle PAC [Cou87] qui cherche différents, et même sous des systèmes d'exploitation différents, peuvent soumettre des re
à combiner Jes propriétés du modèle langage et du modèle multi-agents. quêtes d'affichage à un même serveur X de façon homogène. Les clients n'ont pas besoin de
Aujourd'hui, beaucoup de travaux portent sur la modélisation et sur les techniques d'in savoir comment fonctionne le serveur, et vice versa. Clients et serveur respectent un protocole
teractions homme-machine [App07]. de communication unique et indépendant du matériel et des systèmes d'exploitation. Ce mo
dèle client/serveur de X possède de nombreux avantages. Il pennet en pa11iculier de répaitir
L'interface utilisateur et Je noyau fonctionne] sont deux parties dont ]es conceptions
la puissance de calcul sur plusieurs machines et de partager les ressources disponibles, alors
doivent être menées, autant que faire se peut, simultanément pour que les concepteurs aient
que l' affichage a Heu sur un terminal unique. Ce terminal peut être un matériel de faible coût,
une vision globale du logiciel. Mais cela n'est pas toujours possible, smtout quand il s'agit
alors que les clients s'exécutent sur des machines très puissantes et onéreuses.
de pourvoir d' une interface un noyau fonctionnel existant.
Le système de fenêtrage gère donc des fenêtres, c'est-à-dire des zones de l'écran dans
Les spécificités des utilisateurs, surtout si ceux-ci ne sont pas des informaticiens, sont
des cadres, la plupart du temps rectangulaires, qui affichent une information particulière des
aussi des points essentiels à prendre en compte lors de la conception d'un système interactif.
applications. Il offre tout un ensemble de primitives qui permettent aux programmeurs d'ap
Les compétences, les préférences et Jes goûts, les possibilités humaines (ouïe, vision . . . mais
plications interactives de créer ou de détruire des fenêtres, ou encore d'en modifier le contenu.
aussi les handicaps) ou encore la psychologie des utilisateurs sont, au même titre que les
Le système de fenêtrage X ne permet pas la manipulation interactive des fenêtres par l'utili
contraintes techniques du noyau fonctionnel, des caractéristiques fondamentales dont le ou
sateur. Par exemple, il ne prend pas en charge les déplacements des fenêtres ou leur agrandis
les concepteurs doivent tenir compte pour le développement de l'interface.
sement. C'est le travail d' une application particulière, le gestionnaire de fenêtres, qui gère le
dialogue avec l'utilisateur et assure généralement :
- le placement interactif des fenêtres ;
-0
0 26.3 ENVIRONNEMENTS GRAPHIQUES -0
0 - le recouvrement et l'empilement ;
c c
::J ::J l'icônification ;
0 Les applications qui possèdent une interface graphique s'exécutent sur des machines dont 0
V
T"'l V
T""l - le focus du clavier ;
0 le système d'exploitation est pourvu d'un système de fenêtrage. Dans cette section, nous 0
N N - le changement de taille ;
@ décrirons tout d'abord ce qu'est un système de fenêtrage et la notion de fenêtre qu'il met @ - la décoration des fenêtres ;
.....
..c en jeu. Nous présenterons ensuite les principes des outils qui servent à la construction des .....
..c
Ol Ol les menus de commandes.
ï::: interfaces graphiques. ï:::
>
a. >
a. L'écran du terminal affiche des fenêtres. Même en l'absence de toute fenêtre, il en existe
0 0
u u toujours une, la fenêtre racine, qui correspond à la totalité de l'écran et affiche une trame,
26.3.1 Système de fenêtrage une couleur, ou une image de fond. On l'appelle racine car les fenêtres créées par la suite
constituent une arborescence dont le fond d'écran est la racine.
Le système de fenêtrage peut être intégré dans le système d'exploitation, comme pour les
systèmes de MICROSOFT, mais peut être aussi un module séparé et indépendant d'un sys Une des premières fonctions du gestionnaire de fenêtres est la mise en place interactive
tème d'exploitation particulier, comme le système de fenêtrage X 3 basé sur la bibliothèque des fenêtres sur la fenêtre racine. Toute nouvelle fenêtre s'affiche sur la fenêtre racine, et peut
Xlib. Le système de fenêtrage gère l'activité d'affichage d' une part, et les ordres d'affichage se trouver dans différents états :
- visible, icônifiée, invisible, détruite ;
3. Ce système de fenêtrage a été développé à l'origine, dans les années 70, au MIT. - active ou inactive.
26.3 Environnements graphiques 389 390 Chapitre 26 • Interfaces graphiques
Une fenêtre correspond à un processus ; si elle est visible, les ordres d'affichage envoyés lumineux de l'écran, mais aussi en caractères pour certaines applications. La taille réelle
par le processus modifient ce qui s'affiche à l'écran. Une fenêtre dont on ne regarde plus le de la fenêtre dépend de la distance entre deux points lumineux, et plus précisément de sa
contenu occupera moins de place à l'écran si elle est icônijiée : dans ce cas elle est remplacée résolution, c'est-à-dire du nombre de pixels par ligne et par colonne. Plus la résolution est
par une icône, c'est-à-dire une mini-fenêtre qui porte le même nom, mais dont on ne peut grande, plus la fenêtre apparaîtra petite, et plus la précision sera grande.
pas voir le contenu. On peut également la rendre complètement invisible, à condition d'avoir
La position d'une fenêtre à l'écran se fait par un système de coordonnées, spécifiant des
un moyen de la faire réapparaître ultérieurement. Enfin, une fenêtre peut être détruite. Le
distances par rapport à un ou plusieurs repères. En général, le coin en haut à gauche d'une
passage dans l'un ou l'autre de ces états se fait par des ordres au gestionnaire de fenêtres,
fenêtre possède la coordonnée (0, 0).
donnés à l'aide de la souris.
Le placement des fenêtres permet le recouvrement et l'empilement des fenêtres les unes
La couleur
sur les autres. Les gestionnaires de fenêtres proposent en général des mécanismes, pour faire
passer en avant-plan ou en arrière-plan les fenêtres. Les anciens écrans graphiques à tube cathodique, les écrans LCD (Liquid Crystal Display)
Une seule fenêtre est active à la fois, c'est vers elle que tous les caractères frappés au ou les écrans à LED (diode électroluminescente, Light-Emitting Diode) actuels comprennent
clavier sont envoyés. On dit que cette fenêtre a le focus. La manière de rendre une fenêtre en chaque pixel trois types de phosphore ou d'électrode qui, selon la technologie, émettent
active est l 'une des caractéristiques ergonomiques importantes de toute interface graphique. respectivement les couleurs rouge, vert et bleu. L'addition de ces trois couleurs avec la même
Pour certains gestionnaires de fenêtres, une fenêtre devient active simplement si le pointeur intensité donne du blanc, l'absence de ces trois couleurs donne du noir. Les couleurs possibles
de la souris se trouve dessus ; pour d'autres, l' utilisateur doit explicitement cliquer dans la sont notées par trois nombres, qui donnent la valeur de l'intensité pour chacune des trois
fenêtre pour lui donner le focus. Bien souvent, les gestionnaires de fenêtres permettent à couleurs de base. Sur la plupart des écrans actuels, une intensité varie entre 0 et 255, c'est-à
l'utilisateur de choisir l'un ou l'autre des comportements. dire qu'elle est représentable sur un octet, et qu'une valeur nécessite un nombre de 24 bits. Il
L'agrandissement et le rétrécissement des fenêtres font aussi partie des tâches du ges
existe donc potentiellement 224 couleurs différentes, c'est-à-dire exactement 16 777 216.
tionnaire de fenêtres. Le changement de taille se fait avec la souris ; l' utilisateur ajuste de Le contenu de l'écran est représenté par une mémoire d'écran, qui doit représenter la valeur
façon interactive la taille de la fenêtre aux dimensions souhaitées. Une autre possibilité est de la couleur de chaque pixel. La taille de cette mémoire d'écran aura donc une incidence
d'agrandir à la dimension de l'écran la fenêtre, de telle façon qu'elle recouvre tout l'écran en directe sur le nombre de couleurs possibles. Pour un écran 1600 x 1280, la mémoire d'écran
masquant toutes les autres fenêtres. devra être de presque 6 Mo. Lorsque la mémoire d'écran est de taille réduite 4 , le système
Une fenêtre est normalement munie d'un décor, placé par le gestionnaire de fenêtres, qui de fenêtrage ne représente dans cette mémoire qu'un ou deux octets au lieu de trois, ce qui
peut servir à effectuer ce11aines actions sur la fenêtre. Un élément fondamental du décor est ne pennet pas de coder directement la valeur de la couleur. Ce codage est fait de manière
la barre de titre, qui comporte quelques boutons de commande, le nom de la fenêtre, et qui indirecte, par une table des couleurs associée à la mémoire d'écran, qui comprend 256 mots
pe1met également de déplacer la fenêtre sur l'écran. de 24 bits lorsqu'on fait un codage sur un seul octet. La valeur associée à un pixel dans la
mémoire d'écran est donc en fait un indice dans la table des couleurs (voir la figure 26.4).
À la fenêtre racine, on associe en général des menus, dont les entrées pem1ettent d'exé
On garde la possibilité des 16, 5 millions de couleurs, mais on ne peut en utiliser que 256 à
cuter des commandes. Contrairement à l'interface classique du système du MACINSTOSH,
-0 -0 la fois. Un système de fenêtrage permet en général de modifier très rapidement le contenu de
0 qui place en haut d'écran une barre de menus dont le contenu dépend de la fenêtre active, 0
c c la table des couleurs, ce qui permet d'en avoir une par fenêtre. Dans ce cas, le changement se
::J on utilise en général avec X des menus surgissants, qu'on peut faire apparaître en cliquant ::J
0 0 fait avec le changement de focus, au détriment des couleurs des autres fenêtres.
V
T""l n'importe oli sur la fenêtre racine. Le minimum est d'en avoir un qui permet au moins de V
T""l
0 créer quelques nouvelles fenêtres, d'appliquer ce11aines actions aux fenêtres existantes, et de 0 Tout ce mécanisme constitue le modèle ROB (Red - Green - Blue) des couleurs, qui est
N N
@ terminer l'exécution du gestionnaire de fenêtres. @ un modèle additif assez intuitif. Les coloristes utilisent plus souvent un modèle soustractif,
.....
..c .....
..c
Ol Ol plus proche de ce qu'on fait avec une boîte de peinture, et qui utiHse comme couleurs fonda
ï::: ï::: mentales le jaune, le pourpre et le bleu sombre : c'est le modèle YMC (Yellow - Magenta -
>
u
� 26.3.2 Caractéristiques des fenêtres
u
a.
0 Cyan). Un autre modèle encore utilisé, le modèle HIS (Hue - Intensity - Saturation), dénote
Une fenêtre peut être caractérisée par un certain nombre de propriétés fondamentales, que une couleur par sa teinte qui est un angle sur un cercle de couleurs possibles, son intensité,
l' utilisateur peut paramétrer. Nous nous limiterons à présenter trois caractéristiques de base : qui est une valeur entre le noir et le blanc, et sa saturation, qui va de l 'absence de couleur à la
la géométrie, la couleur et les polices de caractères. couleur pure.
Tous ces modèles sont équivalents, et on a par exemple, les couleurs suivantes dans le
>- La géométrie modèle RGB (notées en hexadécimal) :
La géométrie donne les dimensions de la fenêtre et sa position sur l'écran. La largeur et
la hauteur de la fenêtre sont mesurées en général en pixels, qui coITespondent aux points 4. Les cartes graphiques actuelles proposent des tailles de mémoire toujours plus grandes.
26.3 Environnements graphiques 391 392 Chapitre 26 • Interfaces graphiques
programmeur. Chaque widget possède un comportement qui lui est propre en réaction aux 26.3.4 Générateurs
événements auxquels il est soumis. Ainsi, pour un bouton, le fait de cliquer dessus change
son apparence graphique, et par un effet d'ombre donne l'impression qu'il est enfoncé. Même avec une boîte à outils, le développement d'une interface graphique reste une tâche
ardue. Les générateurs aident à la conception et à l'implémentation des interfaces utilisateurs.
Les premiers générateurs ont été conçus pour produire des interfaces à partir d'une spécifi
Contrairement aux applications des chapitres précédents, dont l'exécution suivait l'ordre cation exprimée sous forme textuelle dans une notation formelle (grammaires hors contexte,
séquentiel et immuable défini par leur algorithme, les programmes contrôlés par une inter langages déclaratifs spécialisés, réseaux de Pétri, etc.). La connaissance de cette notation
face graphique sont dirigés par l'utilisateur, et plus précisément par les événements auxquels formelle devait s'ajouter à celle du langage cible d'écriture du système interactif.
réagissent les composants graphiques. On parle de programmation dirigée par les événe Leurs successeurs construisent la spécification de l'interface par manipulation directe, évi
ments. Toutes les boîtes à outils fournissent des mécanismes qui permettent aux widgets de tant ainsi au concepteur la connaissance et la programmation de la notation formelle. Ces gé
l ' interface graphique de communiquer avec le noyau de l ' application. 11 existe plusieurs mé nérateurs deviennent eux aussi des outils graphiques et interactifs. Leurs éditeurs permettent
camsmes : à l ' utilisateur de placer facilement avec la souris les composants graphiques (boutons, me
nus, etc.) de la future interface. S'ils facilitent la conception des interfaces par un assemblage
interactif des objets graphiques, ce type de générateurs n'offre en général qu' une aide limi
- Les fonctions de rappel (en anglais callbacks) sont le mécanisme le plus classique tée quant à la programmation de leur comportement dynamique et leurs liens avec le noyau
dont l'intérêt ptincipal est la simplicité. Ces fonctions sont associées aux widgets lors fonctionnel de l'application.
de leur création (ou bien par une primitive spéciale). Leur programmation effectue
La dernière génération de ces systèmes permet le développement des applications par as
des actions sur les structures de données du noyau, ou sur des widgets de l'interface
semblage de composants logiciels du noyau de l'application même. Dans le monde JAVA, ces
graphique, par l'intermédiaire de paramètres fixés par la boîte à outils. Ainsi, lors
composants appelés Beans 5 éventuellement écrits et compilés séparément, sont assemblés de
qu'un widget est activé, par exemple lors d'un clic sur un bouton ou sur une entrée de
façon interactive et graphique à l'aide d'une plate-forme d' assemblage spécifique comme, par
menu, sa fonction de rappel est exécutée. Notez que la notion d'événement n'apparaît
exemple, Bean Builder ou plus récemment NetBeans. Celles-ci offrent une palette de compo
pas explicitement dans ce mécanisme. L'inconvénient des fonctions de rappel est de
sants graphiques et des mécanismes d'assemblage qui gèrent le comportement dynamique de
destructurer le code du programme. En effet, dans la mesure où les appels de ces fonc
l'application.
tions ne sont pas explicites, leur texte peut être placé n'impo11e où dans le programme,
sans cadre syntaxique ou sémantique spécifique, ce qui, lorsque leur nombre devient
important, entraîne un manque de lisibilité du programme global. Le lecteur désireux
de mettre en œuvre ce mécanisme pourra s'exercer avec la boîte à outils libsx [Gia9 1 ] 26.4 INTERFACES GRAPHIQUES EN JAVA
dont l' utilisation est particulièrement aisée dans l'environnement X.
- Les boîtes à outils qui utilisent le mécanisme d'événement, comme AWT de JAVA, La plupart des langages de programmation possède des bibliothèques de composants gra
définissent explicitement cette notion. La boîte à outils définit aussi la notion d'audi phiques prêts à l'emploi pour construire des interfaces graphiques. Le langage JAVA en pro
-0 teurs qui sont des gestionnaires d'événements associés aux widgets. Les événements -0 pose deux, AWT (Abstract Window Toolkits) et SWING. La seconde est construite à partir de
0 0
c sont représentés par des objets qui sont créés lors de l' activation d'un widget et émis à c la première et propose une version adaptée du modèle MVC (Modèle-Vue-Contrôleur). Les
::J ::J
0 0
V destination de son auditeur associé. Les auditeurs contiennent les fonctions de rappel à V exemples qui suivent sont programmés avec SWING. Leur but n'est pas de faire une présen
T""l T""l
0 exécuter lorsqu'ils interceptent un événement issu du composant graphique auquel ils 0 tation exhaustive de cette boîte à outils graphique, mais simplement d'illustrer les notions de
N N
@ sont liés. En général, l'événement est transmis en paramètre de la fonction de rappel @ base de la programmation d'interfaces graphiques avec cette boîte à outils. Pour une présenta
.....
..c exécutée. L'intérêt de cette méthode par rapp011 à la précédente est de permettre une .....
..c tion de l'ensemble des composants graphiques de SWING, nous invitons le lecteur à consulter
Ol Ol
ï::: meilleure localisation du code des fonctions de rappel. ï::: le site docs . oracle . corn/ javase/tut orial/uiswing / index . html.
>
a. >
a.
0 - Les variables actives sont des variables du noyau fonctionnel qui sont associées aux 0
u u
widgets. Lorsqu'une variable active change de valeur, l'état du composant graphique
26.4.1 Une simple fenêtre
change pour refléter la nouvelle valeur. Réciproquement, lorsque l'état du widget est
modifié, une nouvelle valeur sera affectée à sa variable active exprimant le nouvel état. Pour commencer, nous donnons ci-après le texte d'une classe qui affiche « Bonjour à tous »
Ce mécanisme est très utilisé dans la boîte à outils Tk avec le langage de script Tel dans une fenêtre graphique. Une fenêtre est une portion de l'écran, en général, rectangulaire,
[Ous94]. qui possède sur sa partie supérieure une barre de titre.
- Les machines à états, où le passage d'un état à un autre est décrit par des fonctions
de transition qui sont déclenchées (selon certaines conditions) lorsqu'un événement se
5. Ces composants sont représentés par des classes qui suivent des règles spécifiques. Ces classes doivent, par
produit. Ce modèle est utilisé dans la boîte à outils SwingStates, extension de SWING. exemple, respecter des conventions de nommages et être sérialisable pour la persistance.
26.4 Interfaces graphiques en Java 395 396 Chapitre 26 • Interfaces graphiques
public FenêtreBonjour ( ) {
Il donner un ti tre à l a fenêtre
super ( " Ma._. F e n ê t r e " ) ;
Il fixer sa dimension
setSize ( LARGEUR, HAUTEUR ) ; FIGURE 26.5 Une simple fenêtre.
Il l ui ajouter l e message de bienvenue
getContentPane ( ) . add ( Enfin, le constructeur exécute la méthode setVisible avec comme paramètre true
new JLabel ( " Bonjour�à�t o u s " , SwingConstant s . CENTER ) } ; pour afficher la fenêtre à l'écran. Notez qu'une fenêtre graphique peut exister, sans pour
Il l a rendre visible autant être visible sur l'écran. Voilà pourquoi l'appel à set Visible est nécessaire.
setVisib l e (true ) ;
La création d'un objet de type FenêtreBon j our provoque la création d'une fenêtre
semb1ab1e à ce11e de la figure 26.5.
public static void main ( String [] args) { Dans SWING, un événement est un objet. Tous les composants graphiques peuvent émettre
new FenêtreBonjour ( ) ; des événements, et peuvent être aussi à l'écoute des événements grâce à des auditeurs (liste
ners en anglais). L'auditeur d'un objet spécifie les événements qu'il désire écouter, et contient
des fonctions de rappel à exécuter, appelées gestionnaires d'événements.
Nous allons maintenant faire réagir cette fenêtre à un événement extérieur. Lorsqu'elle
SWING est un paquetage de l' API JAVA qui propose toutes les classes nécessaires à la possède le focus, nous voulons, quand l'utilisateur appuie sur la touche « q » de son clavier,
construction d'interfaces graphiques classiques. Dans Je code précédent, la directive d'im que cette fenêtre se fenne et que l' application s'achève.
portation permet un accès direct aux classes de ce paquetage, et doit normalement apparaître La classe FermetureFenêtre qui suit fabrique par héritage de la classe abstraite
en tête. La classe JF rame définit une simple fenêtre graphique avec une bordure et une barre KeyAdapter l'auditeur chargé de cette tâche. La classe KeyAdapter traite tous les événe
de titre qui peut être affichée à l ' écran et dans laquelle on pourra écrire, dessiner ou placer ments issus des touches du clavier. Parmi les méthodes abstraites de cette classe, nous allons
des composants graphiques. définir keyPressed dans l ' auditeur. Son paramètre de type keyEvent pem1et de tester
la touche qui a été appuyée grâce à la méthode getKeyChar ( ) . Les classes qui gèrent les
-0
La fenêtre graphique que nous voulons afficher possède toutes les propriétés d'un objet "'O événements appartiennent au paquetage java . awt . event.
0 JFrame avec un message à l'inté1ieur. La classe FenêtreBonjour hérite donc de la classe 0
c c
::J :::1
0 JFrame. Son constructeur exécute d'abord celui de sa super-classe pour donner un titre qui 0 import java . awt . event . * ;
V
T""l s' affichera sur la batTe de la fenêtre graphique. Puis, il exécute la méthode set S i z e pour "'" public class FermetureFenêtre extends KeyAdapter
..-l
0 0 public void keyP ressed ( KeyEvent e )
N fixer les dimensions de la fenêtre. L'unité de mesure est le pixel, qui correspond à un point N
@ lumineux de l'écran. La taille réelle de la fenêtre dépend de la distance entre deux points © i f ( e . getKeyCha r ( ) == ' q ' )
.....
..c ....,
-1= System . exi t ( O ) ;
Ol lumineux, et plus précisément de sa résolution, c'est-à-dire du nombre de pixels par ligne et °'
ï::: ï::::
>
a. par colonne. Plus la résolution est grande, plus la fenêtre apparaîtra petite, et plus la précision >
Cl..
0 0 Il fin classe Fermet ureFenêtre
u sera grande. Dans notre exemple, la fenêtre est donc un rectangle de largeur 400 pixels et de u
hauteur 200 pixels. La méthode addKeyListener permet d'ajouter à une fenêtre des auditeurs qui traitent
la réception des événements issus des touches du clavier. Pour ajouter l'auditeur précédent, il
Un conteneur (en anglais container) est un composant dans lequel on peut placer d'autres
suffit de placer dans le constructeur de la classe FenêtreBonjour ]'instruction suivante :
composants graphiques grâce à la méthode add. Le conteneur associé à une fenêtre JFrame
s'obtient grâce à la méthode getContentPane. Le message de bienvenue est placé dans ce addKeyL istener ( new FermetureFenêt re ( ) ) ;
conteneur. Un JLabel est un composant graphique dans lequel on peut placer du texte ou des
images. Nous utiliserons naturellement ce composant pour notre message « Bonjour à tous». Afin de contracter le code, notez que l'auditeur peut être également défini par une classe
Le second paramètre du constructeur de JLabel permet d'indiquer la position message. Ici, anonyme déclarée au moment de sa construction.
nous Je voulons centré.
26.4 Interfaces graphiques en Java 397 398 Chapitre 26 • Interfaces graphiques
addKeyListener ( new KeyAdapt er ( ) { observateur. Les deux fonctions de conversion précédentes indiquent le changement d'état
public void keyP ressed ( KeyEvent e ) { (setChanged) et notifient à leurs observateurs la donnée qui a changée.
if ( e . getKeyChar ( ) == 'q ' ) System . exi t ( O ) ;
Pour créer l'interface graphique, nous allons assembler des composants graphiques prédé
} }) ;
finis par la boîte à outils. Nous avons besoin d'une entrée pour la saisie de la température,
et de trois boutons pour les conversions et l'achèvement de l'application. Pour disposer ces
composants graphiques à l'intérieur d'une fenêtre graphique comme sur la figure 26.6, AWT
26.4.2 Convertisseur Celcius-Fahrenheit et SWING proposent différents systèmes d'agencement définis par les classes FlowLayout,
On se propose de programmer un convertisseur de degrés Celcius en degrés Fahrenheit (et BorderLayout, GridLayout ou encore GridBagLayout et BoxLayout. La méthode
inversement). La programmation de cette application nous permettra de mettre en évidence setLayout de la classe Container (dont hérite la classe JFrame) pem1et de fixer le type
la séparation entre le noyau fonctionnel et l'interface graphique de l'application selon le d' agencement désiré. Ensuite, les appels successifs à la méthode add permettent de placer
modèle Modèle-Vue-Contrôleur. Le cahier des charges de ce convertisseur est simple : un les composants selon le mode d'agencement choisi.
utilisateur pouffa saisir une température que le convertisseur convertira soit en degrès Celcius Les quatre widgets de l'interface sont déclarés comme attributs de la classe Vue qui hétite
soit en degrès Fahrenheit. Il pourra aussi mettre fin à l'application à tout moment. La figure de JF rame. L'entrée possède une largeur de 25 caractères au maximum, sans texte initial.
26.6 montre la fenêtre l'interface graphique de l'application. Chacun des trois boutons de type JBut ton est créé avec une étiquette qui l'identifie. Pour
l'agencement de ces widgets, nous allons utiliser la classe GridLayout qui dispose les
ConvertisseurDegrés 1 •
widgets sur une grille aux dimensions données, et la classe F lowLayout qui les dispose de
façon régulière de gauche à droite et du haut vers le bas. La fenêtre principale est une grille à
1
deux lignes et une colonne. L'entrée de type TextField est placée sur la première ligne. Les
1 11 Il 1
22.7
trois boutons sont placés dans la seconde ligne. Pour cela, ils sont regroupés dans un objet
de type JPanel. Cette classe facilite la construction hiérarchique des interfaces graphiques,
Celci us Fahrenheit Exit
puisque chaque panel pourra être décrit séparément et pourra posséder son propre système
de coordonnées et son propre système d'agencement. L'agencement par défaut d'un JPanel
est F l owLayout.
FIGURE 26.6 Un convertisseur Celcius-Fahrenheit.
Le noyau fonctionnel, le modèle, est très simple, puisqu'il est fon11é du nombre de degrés
courant et des deux fonctions de conversion. On rappelle que c = (f 32) x 5/9 et que -
JFrame 1 22.7
1
0
f = c x 9 /5 + 32. Le noyau fonctionnel est représenté par la classe Modèle suivante : (Gridlayout(2,1))
Celcius Fahrenheit
import java . ut i l . * ;
public class Modèle extends Observable {
La classe Modèle dérive de la classe java . ut i l . Observable. Cette dernière permet public class Vue extends JFrame implements Observer
de définir des objets qui pouITont être observés par d'autre objets. Dans la relation Modèle
Vue, la vue observe le modèle et visualise les changements que le modèle signale à son JTextField degré = new JTextFi eld ( " " , 25) ;
26.4 Interfaces graphiques en Java 399 400 Chapitre 26 • Interfaces graphiques
JButton celcius = new JButt on ( " C e l c i u s " ) , Les méthodes addF ahrenhei tListener et addCelciusListener associent les ac
fahr enheit = new JButton ( " F a h r e n h e i t " ) , tions, sous formes de fonctions anonymes, à exécuter lorsque l'utilisateur appuie sur le bouton
exit = new JButton ( " E x i t " ) ; Fahrenheit ou Celcius. Ces deux méthodes sont définies dans la classe Vue comme suit :
public Vue ( ) {
Il donner un t i t re à l a fenêtre public void addFahrenheitListener (ActionListener a l ) {
super ( " Convertisse u r D e g r é s " ) ; fahrenhe i t . addActionListener ( al ) ;
Il définir l a méthode d ' a gencement
getContentPane ( ) s e tLayout ( new GridLayout ( 2 , l ) ) ;
.
public void addCelciusListene r ( ActionListener a l ) {
Il ajouter l ' entrée celciu s . addActionListener ( al ) ;
getContentPane ( ) . add(degré ) ;
Il placer les trois boutons dans un panel
JPane l p = new JPanel ( ) ;
p . add ( ce l c ius ) ; La vue est un observateur du modèle. Elle doit modifier la valeur de la température qu'elle
p . add ( fahrenheit ) ; affiche lorsque le modèle lui signale un changement. Pour cela, la classe Vue implémente
p . add ( exi t ) ; l'interface java . ut i l . Observer, et définit la méthode update comme suit :
Il ajouter le panel à la fenêtre
get C o nt ent P ane ( ) . add ( p ) ; I * * Rôle : met à jour l e JTextField degré
Il définir l a t a i l l e de l a fenêtre et l a rendre visible * avec l e nombre de degrés converti
pack ( ) ; *I
setVisible (true ) ; public void updat e ( Observable o, Object d)
degré . setText ( D ouble . t oString ( (Double) d) ) ;
-0 -0
0 0 L'application Conve rt i s seurDegrés consiste à créer dans sa méthode main, le mo
c c
::J ::J
0 0 dèle, le contrôleur, et la vue et de faire observer le modèle par la vue.
V L'interface précédente ne réagit qu'à la seule insertion de caractères dans l'entrée. C'est V
T""l le comportement par défaut du widget TextField. Si l'utilisateur appuie sur l'un des T""l
0 0
N N public class Convert i s seurDegrés {
@ boutons, son apparence change, et l'événement ActionEvent est généré. La méthode @ public static void main ( String [) args) {
.....
..c actionPer formed est alors automatiquement exécutée dans l'auditeur associé au bou .....
..c Modèle m = new Modèle ( ) ;
Ol ton. Cet auditeur doit implémenter l'interface ActionListener et définir la méthode Ol
ï::: ï::: Vue v = new Vue ( ) ;
>
a. >
a.
0 actionPerf ormed. 0 Contrôleur c = new Contrôleur ( m , v) ;
u u
Dans le modèle Modèle-Vue-Contrôleur, le contrôleur assure l'interface entre le modèle et Il ajouter l a vue v comme observateur du modèle m
la vue. Plus particulièrement, il enregistre les auditeurs des événements produits par la vue. m . addObserver ( v ) ;
FIGURE 26.8 Composant de visualisation de couleurs. public CouleursRGB ( int r, int v, int b)
setLayout ( new BorderLayout ( ) ) ;
26.4.3 Un composant graphique pour visualiser des couleurs Il les étiquettes Red, Green, Blue à gauche
JPanel pl = new JPanel ( ) ;
Dans ce dernier exemple, nous allons concevoir un composant graphique qui pourra être p l . setLayout ( new GridLayout ( 3 , 1 ) ) ;
utilisé par ailleurs comme n ' importe quel autre composant graphique 6 . pl . add ( l abR) ;
Avec ce composant, que nous appellerons CouleursRGB, il s'agit de créer, de modifier p l . add ( l abV) ;
p l . add ( l abB ) ;
et de visualiser des couleurs selon le modèle RGB (Red, Green, Blue). Rappelons que dans
add ( p l , BorderLayout . WES T ) ;
ce modèle, une couleur est définie à partir de la composition de trois valeurs, comp1ises entre
Il les barres de défilement a u centre
0 et 255, représentant une valeur d'intensité des couleurs rouge, vert et bleu. Une couleur
JPanel p2 = new JPanel ( ) ;
pourra être construite et modifiée à partir des constructeurs et des méthodes de la classe, p2 . setLayout ( new GridLayout ( 3 , 1 ) ) ;
ou bien de façon interactive à l'aide de l'interface graphique donnée par la figure 26.8. Les p2 . add ( s lR) ;
batTes de défilement font varier l'intensité des trois couleurs dont les valeurs apparaissent p2 . add ( s lV ) ;
dans les trois entrées associées. Réciproquement, la modification des valeurs d'intensité dans p2 . add ( s lB ) ;
les entrées ajustera la position courante des baITes de défilement. Enfin, quelle que soit la add ( p 2 , BorderLayout . CENTER) ;
façon de modifier les intensités, la nouvelle couleur spécifiée est visualisée sur le haut de la Il les entrées à droi t e
fenêtre. JPanel p 3 = new JPanel ( ) ;
p3 . setLayout ( new GridLayout ( 3 , 1 ) ) ;
Le widget CouleursRGB est défini comme un JPanel afin qu'il puisse être com
p3 . add ( t fR) ;
posé avec d'autres widgets. Il est construit à partir de dix autres composants graphiques : p 3 . add ( t fV) ;
quatre étiquettes de type JLabel, trois barres de défilement de type JSlider et trois p3 . add ( t fB ) ;
entrées de type JTextF ield. Ces widgets sont placés selon le système d'agencement add ( p 3 , BorderLayout . EAS T ) ;
BorderLayout qui organise le panel. Les trois étiquettes Rouge, Vert et Bleu sont pla Il l ' étiquette qui visualise l a couleur au -dessus
cées à gauche (BorderLayout . EAST), les trois balTes de défilement h01izontales au centre add ( labRVB, BorderLayout . NORTH ) ;
"O (BorderLayout . CENTER), les trois entrées à droite (BorderLayout . WEST), et enfin -0
0 0
c c
:::1 l'étiquette qui visualise la couleur est positionnée au-dessus (BorderLayout . NORTH). ::J
0 0
"'" Les barres de défilement sont orientées de façon horizontale, graduées de 0 à 255. Ces V
T""l
..-l Il s'agit maintenant de relier ces composants graphiques à une couleur. Pour cela, nous
0 0
N baITes et les trois entrées coITespondantes sont initialisées aux valeurs des paramètres du N déclarons trois attributs entiers qui représenteront les trois intensités de la couleur courante.
© constructeur. @
...., ..... Ces attributs sont en fait des variables actives telles que leur modification entraîne la mise
-1= ..c
°' Ol à jour des widgets auxquels ils sont reliés ; réciproquement toute modification des widgets
ï:::: public class CouleursRGB extends JPanel { ï:::
>
Cl.. >
a. entraîne un changement de valeur de ces attributs. Le code suivant est inséré dans la classe
0 0
u Il les trois entrées pour les intensités rouge, vert e t bleu u CouleursRGB. On déclare trois entiers rouge, vert et bleu et on modifie le constructeur
JTextField tfR new JTextFiel d ( " 0 " , 3 ) , Il intensi t é rouge de façon à les iniüaliser à l'aide des méthodes de changement de valeur d'intensité (décrites
tfV = new JTextFie ld ( " 0 " , 3 ) , Il intensi t é vert plus loin), qui provoquent également la visualisation de la couleur dans l'étiquette du haut.
tfB = new JTextField ( " 0 " , 3 ) ; Il intensité bleu
private int rouge, vert , bleu;
Il les trois barres de défil ement pour ces intensités
public CouleursRGB (int r, int v, int b) {
6. Le paquetage SWING offre un tel composant, mais avec plus de fonctionnalités. l i se nomme add ( l abRVB, BorderLayout . NORTH ) ;
JColorChooser.
changerRouge ( r ) ; changerVert ( v ) ; changerBleu ( b ) ;
Lorsqu'on modifie une des intensités de la couleur courante, ce changement doit se ré précédentes qui s'exécutent de façon autonome, les applets nécessitent une autre application
percuter sur la barre de défilement et l'entrée qui lui correspondent, ainsi que sur l'étiquette graphique pour leur exécution, un visualisateur spécialisé (appletviewer) ou un navigateur
de visualisation du haut. C'est par exemple Je rôle de Ja méthode changerRouge donnée WWW. Leur contexte d'exécution est celui d'un document HTML interprété par le visuali
au-dessous. Tout d'abord, elle mémorise la nouvelle intensité rouge, puis l'affecte à la barre sateur chargé de l 'exécution de l' applet. Le chargement de l' applet dans le document HTML
de défilement (ce qui a pour effet de déplacer la position de son curseur) et à l'entrée asso se fait avec la balise OBJECT 8. Chaque applet a les moyens d'accéder à des info1mations sur
ciée. Une nouvelle couleur courante est alors créée en conservant les intensités verte et bleue son contexte d'exécution, en particulier sur ses paramètres de chargement ou sur les autres
précédentes, et on la visualise dans l'étiquette. applets du document.
I * * Rôle : change l a valeur de l ' intens i t é rouge Les composants graphiques habituels (boutons, labels, canevas, etc.) d' AWT ou SWING
* et répercute la modification sur l e s wi dgets l i é s peuvent être utilisés pour la construction des applets, et réagissent normalement aux événe
*I ments (clics de somis, etc.). Les applets peuvent également traiter des images (généralement
public void changerRouge (int r )
au format gif) et des documents sonores (généralement au format au), si l'environnement
rouge = r;
d'exécution dispose d'un dispositif de reproduction sonore.
s lR . setValue ( r ) ; Il ajuster l a barre de défil ement
Il puis l ' en t ré e
t f R . setText ( St r i n g . valueOf ( r ) ) ; Généralement placées sur des serveurs web, les applets s'exécutent localement dans le
navigateur de l'utilisateur après leur téléchargement à travers le réseau. Ce type de fonc
Il visualiser l a nouvelle couleur tionnement impose bien sûr des règles de sécurité pour garantir l 'intégrité de la machine de
labRVB . s etBackground ( new Color ( rouge, vert, bleu ) ) ; l'utilisateur. Par exemple, une applet ne peut, par défaut 9 , lire ou écrire des fichiers ou exécu
ter des programmes de la machine client. L'action d'une applet est limitée par un composant
Les deux autres méthodes de modification des intensités verte et bleue s'écrivent sur le spécifique du navigateur (SecurityManager) qui en assure le contrôle.
même modèle.
Le déplacement du curseur des barres de défilement provoque l'émission d'événements AppletCouleursRGB
ChangeEvent. La méthode stateChanged est alors automatiquement exécutée dans l'au À titre d'exemple, nous allons créer une applet qui visualise les couleurs à l ' aide
diteur associé à la barre de défilement. Cette méthode appartient à l'interface fonctionnelle de classe CouleursRGB donnée dans la section 26.4.3. L'écriture d'une applet avec
ChangeListener. SWING se fait nécessairement par héritage de la classe JApplet. L'en-tête de notre classe
L' [Link] des auditeurs de changement d'état pour chacune des barres de défilement AppletCouleursRGB a la forme suivante :
s'écrira, dans le constructeur, à l'aide d'une lambda comme suit :
import javax . s wing . *
s l R . addChangeListener ( e -> changerRouge ( s l R . getValue ( ) ) ) ; public class AppletCouleursRGB extends JApp let {
s l V . addChangeListener ( e -> changerVert ( s l V . getValue ( ) ) ) ;
slB . addChangeListener ( e -> changerBleu ( s l B . getValue ( ) ) ) ;
La classe JApplet fournit une interface entre l'applet et son contexte d'exécution par l'in
-0 L'appui sur la touche « entrée » du clavier dans l'une des entrées associées aux inten -0 termédiaire d'un certain nombre de méthodes. Plusieurs d'entre elles peuvent être redéfinies
0 0
c sités provoque l'émission d'événements ActionEvent. La méthode actionPerformed c dans la classe héritière. En particulier les méthodes suivantes :
::J ::J
0 0
V de l' auditeur associé est exécutée. L'auditeur doit alors implémenter l'inte1face V - ini t, appelée par le visualisateur lors du premier chargement de l'applet ;
T"l T""l
0 ActionListener. Là encore, on utilise des fonctions anonymes pour définir les auditeurs 0 - start, appelée par le visualisateur juste après ini t, ou automatiquement à chaque
N N
@ des trois entrées. On complète le constructeur avec : @ réapparition à l'écran (changement de page ou désicônification), pour signifier à cette
.....
..c .....
..c
t f R . addActionListener ( e-> changerRouge ( I nteger . parseint ( t f R . getText ( ) ) ) ) ; applet qu'elle doit démarrer ou redémarrer sa véritable exécution ;
O'I Ol
ï::: t f V . addActionListener ( e-> changerVert ( I ntege r . parseint ( t fV . getText ( ) ) ) ) ; ï:::
>
a. >
a.
0 t fB . addActionListener ( e->changerBleu ( I ntege r . parseint ( t fB . getText ( ) ) ) ) ; 0 du web est de fournir à la consultation, par l'intermédiaire de serveurs spécialisés, des documents de type hyper
u u
texte, c'est-à-dire contenant des références, appelées liens, à d'autres documents, eux-mêmes consultés de la même
manière. Ces documents sont des fichiers rédigés dans le langage de balise HTML (HyperText Markup Language).
Ce langage est formé d'un ensemble de commandes dispersées dans un texte ordinaire, qui permettent d'une part de
26.4.4 Applets placer des indications de présentation, d'autre pait de placer des documents graphiques ou sonores, ou encore des
programmes à exécuter, et enfin de placer des références à d'autres documents. L'accès au web se fait à l'aide de
Les app1ets sont des petites applications graphiques JAVA intimement liées au World navigateurs qui fournissent la plupart du temps une interface graphique, nécessaire pour les applets.
Wide Web, plus communément appelé web 7 . Contrairement aux applications graphiques 8. Depuis la version 4.0 de HTML, la balise OBJECT remplace APPLET qui est devenue obsolète.
7. Également appelé WWW, W3 ou encore la Toile. Le web est né au CERN en 1989 et, depuis lors, n'a cessé de 9. Elle a la possibilité de Je faire si elle possède une autorisation.
se développer. U est aujourd'hui un des outils central du monde informatique. Rappelons, que l'idée fondamentale
26.5 Exercices 405 406 Chapitre 26 • Interfaces graphiques
- stop, appelée par le visualisateur pour arrêter l'exécution de l'applet à chacune de autres boutons font apparaître le message « Perdu ». Lisez la documentation de la méthode
ses disparitions de l'écran (changement de page ou icônification) ou juste avant sa JOpt ionPane . showMe s sageDialog pour faire apparaître la fenêtre de dialogue.
destruction complète ;
Exercice 26.4. Tous les composants graphiques SWING possèdent un contexte graphique
- destroy, appelée par le visualisateur pour détrnire toutes les ressources allouées par
de type Graphies qui pe1met de donner le rendu à l'écran du composant. Ce contexte gra
l' applet.
phique est un rectangle dont les coordonnées de l'angle supérieur gauche sont (0, O). Un com
Pour notre applet, nous redéfinirons la méthode d' initialisation init. Celle-ci ajoute sim posant graphique de type JComponent possède également une méthode paintComponent
plement à l ' applet une instance de CouleursRGB. qui possède un paramètre de type Graphies avec lequel il est possible, entre autres, de des
public void i n i t ( ) { siner ou d'afficher des images dans le composant. Cette méthode est appelée implicitement
getContentPane ( ) . add ( new CouleursRGB ( 2 0 , 1 3 0 , 7 8 ) ) ; chaque fois qu'il est nécessaire de redessiner à l'écran le composant graphique. Une classe
hétitière de la classe JComponent peut dès lors redéfinir la méthode paintComponent et
afficher toutes les informations voulues dans le contexte graphique.
La destruction de J'applet est gérée par son visua1isateur et ]a méthode destroy. Notez Par héiitage de la classe JBut ton, définissez la classe MonBouton qui crée un bouton de
que les appels aux méthodes pack et setVisible deviennent inutiles puisque la gestion de taille n x n, où n est le nombre de pixels, passé en paramètre au constructeur, qui définit la
l'affichage est sous le contrôle du visualisateur. taille du bouton et qui affiche en son centre un cercle de couleur rouge de diamètre n/2 pixels.
Enfin, le lancement de l'exécution de l'applet est spécifié par la balise OBJECT placée Dans la méthode paintComponent, vous utiliserez la méthode Graphi es . f il lOval
dans un document HTML qui sera interprétée par le visualisateur. Cette commande indique pour dessiner le cercle et Color . red pour la couleur rouge. Notez que pour conserver l'ap
le nom du :fichier compilé et les dimensions d'affichage de la fenêtre. parence des JButton la redéfinition de paintComponent devra commencer par un appel
à super . paintComponent.
<OBJECT CODE=" AppletCou leursRG B . c l a s s " width=4 0 0 height=l S O >
</OBJECT> Exercice 26.5. Un auditeur de type MouseListener intercepte les événements de type
bouton de souris appuyé ou relâché, tandis qu'un auditeur de type MouseMot ionListener
traite les événements liés aux mouvements du pointeur de la souris.
Les méthodes mousePressed et mouseDragged de MouseListener et
26.5 EXERCICES MouseMot ionListener possèdent chacune comme paramètre un événement de type
MouseEvent à partir duquel on récupère les coordonnées courantes du pointeur de soutis
Pour ]es exercices qui suivent, vous aurez besoin des pages de documentation des paque
dans le Graphies avec les méthodes getX et getY.
tages d' AWT et SWING.
Écrivez la classe Segment, héritant de JPanel, qui trace un segment entre deux points,
Exercice 26.1. Modifiez la classe Convert i s seurDegrés afin de visualiser simultané par redéfinition de la méthode paint Component.
ment la valeur à convertiT et la valeur convertie. Le point de départ est donné par un appui sur un des boutons de la souris dans le JP ane 1 .
-0 Exercice 26.2. -0 Le point d'arrivée est obtenu, après déplacement de la souris, lorsqu'on relâche le bouton.
0 Une barre de menus est un composant qui se place au-dessus d'une fenêtre 0
c c Notez que le tracé du segment doit être visualisé au fur et à mesure du déplacement de la
::J et qui contient plusieurs menus. Chaque menu apparaît à l'écran lorsque l' utilisateur clique ::J
0 0
V dessus et propose plusieurs entrées. À chaque entrée est associée une commande (ou éven V souris, c'est-à-dire qu'à chaque déplacement de la somis le segment doit être retracé. Pour
T""l T""l
0 tue11ement un sous-menu). Dans SWING, un composant JMenuBar est une barre de menus 0 cela, vous utiliserez la méthode repaint dans la méthode mouseDragged.
N N
@ qui contient des objets JMenu contenant eux-mêmes des objets JMenuitem. Une barre de @ Pour tracer le segment de droite, vous utiliserez la méthode Graphies . drawLine dans
.....
..c .....
..c
Ol menus est ajoutée avec la méthode setJMenuBar. La méthode add de JMenuBar permet Ol la méthode paintComponent .
ï::: l ' ajout de menus de type JMenu. Les entrées d'un menu, de type JMenuitem, sont ajou ï:::
>
a. >
a. Écrivez une applet par héritage de JApplet dans laquelle vous ajouterez
0 0 Exercice 26.6.
u tées à l'aide de la méthode add de JMenu. Enfin, la méthode addAct ionListener de u un objet de type Segment. Écrivez le fichier html pour visualiser le tracé du segment avec
JMenuitem permet d'associer un auditeur de type ActionListener à une entrée.
.
Exercice 26.3. Écrivez l'application qui affiche une matlice n x n de JButton. Tous les
boutons sont numérotés de 1 à n2 . L'utilisateur doit chercher l'unique bouton qui, lorsqu'on
clique dessus fait apparaître une fenêtre de dialogue avec le message « Gagné ». Tous les
408 Bibliographie
[Knu73] D. E. Knuth. So11ing and Searching. The Art of Computer Programming, vol. 3 . [Str86] B. Stroustrup. The C++ Programming Language. Addison-Wesley, 2e édition,
Addison Wesley, 1973. 1986.
[Kor85] R. E. Korf. « Depth-First Iterative-Deepening : An Optimal Admissible Tree [Tanl2] Andrew Tanenbaum. Structured Computer Organisation. Pearson Education,
Search ». Artificial Intelligence, 27 :97-109, September 1985. 6e édition, 20 1 2 .
[KP88] G. Krasner et S. Pope. « A Cookbook for Using the Model-View-Controller [W + 76] W. A. Wulf et al. « An introduction to the construction and verification of A1-
User Inte1face Paradigm in Smalltalk -80 ». JOOP, 1 :26-49, August/September phard programs ». IEEE Transactions on Software engineering, 2(3) : 253-264,
1 988. 1976.
[KR88] B. W. Kernighan et D. M. Ritchie. The C Programming Language. Prentice Hall, [Wex81] R. L. Wexelblat, éditeur. History of Programming Languages. Academic Press,
2e édition, 1988. 198 1. .
[L+ 77] B. Liskov et al. « Abstraction Mecanisms in Clu ». Comm. ACM, 20 :564-576, [Wir75] N. Wirth. Introduction à la programmation systématique. Masson, 1975.
june 1 977. [Wir76] N. Wirth. Algorithms + Data Structures = Programs. Printice-Hall, 1976.
[LC06] Gary T. Leavens et Yoonsik Cheon. Design by contract with jml, 2006. [Wir85] N. Wirth. Programming in MODULA-2. Springer-Verlag, 2e édition, 1 985.
[Lut09] Mark Lutz. Learning Python, Fourth Edition. O'Reilly, 2009.
[M. 8 1 ] M. Shaw and others. Alphard : Form and Content. Springer-Verlag, 198 1 .
[M+ 89] G. Masini et al. Les langages à objets. InterEdition, 1989.
[MaclO] Peter Maclntyre. PHP : The Good Parts. O'Reilly, 1 e édition, 2010.
[MB86] M. Minaux et G. Bartnik. Graphes algorithmes logiciels. Dunod, 1986.
[Mey88] B. Meyer. Conception el programmation par objets. InterEditions, 1988.
[Mey92] B. Meyer. Eiffel : The Language. Object-Oriented Series. Prentice Hall, 1992.
[Mey97] B. Meyer. Object-Oriented Software Construction. Prentice Hall, 2e édition,
1997.
[NAJN75] K. V. Nori, V. Ammann, K. Jensen et Nageli. The Pascal (P) compiler : lm
plementation Notes. Institut für Informatik, Eidgenossische Technische Hoch
schule, 1975. republié dans Pascal - The Langage And Its lmplementation, Bar
ron D.W. ed., pp. 125- 1 70, J. Wiley and Sons, Chichester, 1981. .
-0
[Nau60] P. Naur. « Report on the Algorithrnic Language Algol 60 ». Comm. ACM, 3, 'O
0 0
c 1 960. c
::J ::J
0 0
V
[OSV08] Martin Odersky, Lex Spoon, et Bill Venners. Programming in Scala. Artima V
T""l T"l
0 Inc., 2008. 0
N N
@ [Ous94] John K. Ousterhout. Tel and the Tk Toolkil. Addison Wesley, 1 994. @
.....
..c .....
..c
Ol [Pfa85] G. Pfaff. User interface management systems. Proceedings of the Seeheim Work Ol
ï::: ï:::
>-
a. shop. Springer-Verlag, 1985. >
a.
0 0
u u
[Sam69] J. E. Sammet. Programming Languages, History and Fundamentals. Series in
Automatic Computation. Prentice-Hall, 1969.
[Sch90] D. C. Schmidt. Gperf : A perfect hash fonction generator. Second USENIX C++
Conference Proceedings, April 1990.
[Sed04] Robert Sedgewick. Algorithmes en C++. Pearson Education, 3e édition, 2004.
[SG08] Andrew Stellman et Jennifer Greene. C#. Digit Books, 2008.
[SkaOO] J. Skansholm. Java From the Beginning. Addison Wesley, 2000.
412 Algorithmique et programmation en Java
B compilateur, 7
complément
backtracking, voir rétro-parcours à deux, 24
BACKUS J., 9 à un, 24
backus-naurBACKUS-NAUR, 10 complexité, 13, 114
barre de titre, 389 composante connexe, 345
bicolore, voir arbre bicolore composants logiciels, 1 3 1
boîtes à outils, 392 compression de fichier, 26 1
boolean, 29 concaténation, 101
boucle, voir énoncé itérat�l confrontation de modèle, 110
BOYER, voir algorithme de BOYER
Index MOORE
confrontation de modèle, 1 1
connexité, 347
byte, 24 conséquent, 13
Console, 177
c constante, 1 8
C, X, 6, 10, 22, 38, 40, 53, 96, 97, 101, 106, (complexité), 1 1 5
190 constructeur, 68
C++, 10, 40, 199 continuation, 148
C#, 10 conversions de type
callbacks, voirfonctions de rappel explicites, 38
A antécédent, 1 3 ca1Té magique, 124 implicites, 38
applets, 403 cast, voir conversions de type couleur, 390, 391, 401
A*, 354
arbre, 245-252 catch, 159 coupure a-/3, 375
abstraction, 195
accès
2-3-4, 284-289 CEI, 30 crible d' É RATOSTHÈNE, 103
AVL, 276-284 chaînage externe, 304 crible d' É RATOSTHÈNE, 178
aux attributs, 69
bicolore, 289-299 chaînes de caractères, 101 Curryfication, 1 5 1
aux méthodes, 72
binaire, 252-260, 3 1 2 char, 30
achèvement (prédicat d' -), 85
complet, 253 chemin D
ACKERMANN W., 1 88
action couvrant, 345 dans un arbre, 246 DE MORGAN, 28
dejeu, 372 dans un graphe, 233
élémentaire, 1 5 déclaration d'un nom, 1 8
équilibré, 272, 276 eulérien, 363 décor, 389
structurée, 43
ordonné, 272 CHU SHIH-CHIEH, 1 08
adressage dispersé, 299-306 degré, 233
CHURCH A., 9, 143, 1 8 1
-0 affectation, 1 8 parfait, 253, 3 1 2 -0 'Dè que, 226-227
0 0 class, 70
c quaternaire, 308 c dèque, 203 , 226 227
:::J AL-K HOWÂRIZMÎ, 1 2 ::J -
0 0 do, 87
u EUCLIDE, 90 a s s e r t , 1 4, 40 u clients, 67
attribut, 67 documentation, 22, 54
FLOYD, 362 closure, voir 153 donnée, 2, 15
FLOYD-WARSHALL, 347 partagé, 70 CLU, 1 0
WARSHALL, 362 automate, 1 double, 27
COBOL, 9 , 10 DÜRER A., 1 24
allocation mémoire, 157, 1 90 AVL, voir arb re AVL codage, 5, 261
ALPHARD, 1 0, 1 4, 198 AWT, 393, 394, 398, 404, 405 collision, 300 E
analyse, 7, 1 1 5 axiomatique COLMERAUER A., 1 1
annotation, 1 4 1 de C . A . R . HOARE, 1 3 commande, voir unité de - EBCDJC, 29
anonyme, [Link] anonymes description, 1 96 commentaires, 14, 22 écriture, 17, 165
Index 413 414 Algorithmique et programmation en Java
effet de bord, 59 fonction, 16, 53, 143 HUFFMAN (code de -), 261 JColorChooser, 401
EIFFEL, ] 0, 14, 72, 158, 198, 1 99 fonctionnelle (description), 196 huit reines, 367 jeux de stratégie, 372
en-tête, 54 fonctions JVM (Java Virtual Machine), 8, 1 1
énoncé anonymes, 1 43
choix, 44 d'adressage, 299 K
composé, 43 d'ordre supérieur, 144 IBM, 9, 29
!CON, 1 1, 36 KNUTH D., 10, 332, 343
conditionnel, 44 de continuation, 148
IDA*, 356 KüRF R. E., 356
itératif, 85 de rappel, 393
identificateur KRUSKAL, 347
répéter, 87 paramétriques, 144
si, 46 secondaires, 302 de constante, 18
de fonction, 54 L
tantque, 86 for, 106
entrée standard, 15 Forêt, 247-249 de procédure, 54 labyrinthe, 365, 380
énumération, 216 FORTRAN, 9, 1 0, 1 1 9
de valiables, 19 lambda, 143
environnement fournisseur, 67 IEEE, 26, 27 récursives, 1 54
logiciel, l frères, 246 if, 47 langage
matériel, l implémentation, 198 à objets, 10
FSF, 5
équipements externes, 2 d' Arbre, 249 d'assemblage, 5
fusion, 170, 339
É RATOSTHÈNE, 103, 178 d' Arbreb, 255 de programmation, 5
de Vè que, 227
EUCLIDE, 90, 93 G fonctionnel, 143
EULER L., 363
de File, 225 intermédiaire, 7
évaluation, 36 garbage-collector, 232 de Forêt, 250 machine, 5
événements, 388, 393 GAUSS C. F., 1 27, 128, 367 de Ç}raphe, 234 non typé, 23, 136
exception, 157 générateur aléatoire, 170 de .Ciste, 205 typé, 23, 136
exécution généricité, 1 4 1 de Pile, 221 lecture, 15, 166
parallèle, 43 géométrie, 389 de Table, 266 lexical, 6
séquentielle, 43 gestionnaire de fenêtres, 4, 388 d'énumération, 2 1 6 liaison dynamique, 1 37
expressions, 35 grands entiers, 1 1 6 import, 142 LIFO, voir pile
extends, 140 graphe, 231-243 indexation, 95 linéaire (complexité), 1 15
connexe, 233 indice, 95 lisibilité, 53
F d'héritage, 1 38 instances, 68 LISP, 9- 1 1, 35, 144
non orienté, 231 int, 24 LISP 2, 9
fenêtre, voir système defenêtrage orienté, 231 interclassement, 339 liste, 203-216
fermeture, 153 sans cycle, 360 interface, 1 4 1 d'adjacence, 235, 238, 25 1
-0 fe1meture transitive, 347 simple, 232 -0 graphique, 387-405
0 0 non ordonnée, 266
c feuilles, 246 valué, 232, 234, 236, 239 c textuelle, 383
::J ::J ordonnée, 268
0 FIBONACCI, 1 83 Ç}mphe, 233-234 0 utilisateur, 383, 385
V V .Ciste, 204-205
T""l fichier, 163 GRISWOLD R . , 1 1 T""l interpolation, 307
0 0 localisation, 53
N de texte, 173 N in terprète 7
,
logarithmique (complexité), 1 15
@ séquentiel, 163, 339 H @ de commandes, 4
..... ..... logiciel, 4
..c FIFO, voirfile ..c invaiiant
Ol Ol libre, 5
ï::: file, 203, 223-225 hash-coding, voir adressage dispersé ï:::
>
a. >
a. de boucle, 85 long, 24
0 File, 224-225 HASKELL, 144 0 de classe, 75, 158 LUKASIEWlCW JAN, 35
u File-Priorité, 3 1 1-3 1 2 heapsort, voir tri en tas u
ISO, 29, 30
fils, 246 héritage, 1 3 1 , 132 IS0-8859, 29 M
final, 2 1 multiple, 1 3 8 Iterator, 2 1 6
finitude, 88 simple, 138 main, 2 1 , 75
float, 27 HÉRON L'ANCIEN, 94 MANHATTAN (Distance de -), 355, 362
J
flot, 167 HILBERT (courbes de-), 189 matrice, 1 1 9
FLOYD, voir algorithme de - HOARE C . A .R, 1 3, 195, 334, 337 JAVA,10 creuse, 229
flux, voir flot HORNER W. (schéma de -), 108, 1 16 javadoc, 22 d'adjacence, 235
focus, 389 HTML, 22, 404, 405 JAVASCRIPT, 11 produit, 123
Index 415 416 Algorithmique et programmation en Java