Vous êtes sur la page 1sur 263

Structuration des Données Informatiques - Table des Matières

Structuration des Données Informatiques


Initiation et applications
B. Ibrahim
C. Pellegrini Recherche dans ce cours:
Editions DUNOD informatique
© Bordas, Paris, 1989
ISBN 2-04-018679-4

Table des matières


(Sont aussi disponibles: la description du cours, un article en anglais, une liste de questions d'examen en
format Postscript, une liste des questions d'examens avec leurs corrigés, des enregistrements du cours +
évaluation du cours par les étudiants)
Introduction
Chapitre 1. Notion de typage
1.1.Différence de typage selon les langages
1.2. Contrôle de typage
1.2.1. Equivalence de nom
1.2.2. Equivalence de structure
1.2.3. Cas de quelques langages d'usage courant
1.3. Nomenclature
Chapitre 2. Les types scalaires
2.1. Les types discrets
2.1.1. Les entiers
2.1.2. Les types énumérés
2.1.3. Les intervalles
2.2.Fonctions prédéfinies sur les types discrets
2.3. Les réels
2.3.1. Représentation en virgule flottante
2.3.2. Représentation en virgule fixe
2.4.Compatibilité et conversion de types

http://cuisung.unige.ch/std/ (1 of 5) [07-06-2001 12:11:00]


Structuration des Données Informatiques - Table des Matières

2.4.1. Compatibilité
2.4.2. Conversion de type
Chapitre 3. Les structures statiques
3.1. Structures cartésiennes simples ( exercices disponibles)
3.1.1. Les tableaux
3.1.2. Les enregistrements
3.1.3. Les ensembles
3.2. Cardinalité
3.3. Structures cartésiennes complexes
3.3.1. Tableaux multidimensionnés
3.3.2. Tableaux d'enregistrements
3.3.3. Enregistrement de tableaux
3.3.4. Les chaînes de caractères
3.4. Structures paquetées
3.4.1. Cas des tableaux
3.4.2. Cas des enregistrements
Chapitre 4. Les types abstraits
4.1. Exemple : les tableaux
4.2. Exemple : les ensembles
4.3. Exemple : chaînes de caractères de longueur variable.
Chapitre 5. Les structures dynamiques
5.1. Les pointeurs
5.2. Ramassage des miettes, réutilisation de la mémoire
5.2.1. Ramassage des miettes
5.2.2. Chaînage des trous
Chapitre 6. Structures dynamiques linéaires non-récursives : les fichiers
6.1. Primitives de manipulation
6.2. Utilisation
6.3. Fichiers TEXT
Chapitre 7. Structures récursives linéaires : les chaînes
7.1. Construction ( exercices disponibles)

7.2. modification ( exercices disponibles)


7.3. Utilisation

http://cuisung.unige.ch/std/ (2 of 5) [07-06-2001 12:11:00]


Structuration des Données Informatiques - Table des Matières

7.3.1. Parcours
7.3.2. Recherche d'un élément
7.4. Anneaux, chaînes bidirectionnelles ( exercices disponibles)
7.4.1. Les anneaux
7.4.2. Les chaînes bidirectionnelles
7.4.3. Les anneaux bidirectionnels
7.5. Utilisation d'un élément sans données
7.6. Structures auxiliaires : Piles, files d'attente, files circulaires
7.6.1. Piles
7.6.2. Files d'attente
7.6.3. Représentation et implantation
Chapitre 8. Structures récursives non linéaires : les arbres
8.1. Les arbres binaires ordonnés ( exercices disponibles)
8.1.1. Arbres syntaxiques
8.1.2. Arbres de tri
8.1.3. Opérations sur les arbres
8.2. Arbres multiples ( exercices disponibles)
8.2.1.Représentation d'arbre multiple sous forme d'arbre binaire
8.3. Représentation d'arbres à l'aide de tableaux
8.3.1. Arbres dépliables ( exercices disponibles)
8.4. Rééquilibrage d'arbres
Chapitre 9. Structures récursives non linéaires : les listes
9.1. Opérations sur les listes ( exercices disponibles)
9.2. Détermination de l'appartenance à une liste
9.3. Partage d'éléments entre listes
9.4. Isomorphisme liste-arbre
Chapitre 10. Structures récursives non linéaires : les graphes
10.1. Nomenclature
10.2. Représentation
10.2.1. Matrice de connectivité
10.2.2. Liste de connectivité
10.2.3. Structure entièrement dynamique
10.3. Recherche en profondeur ( exercices disponibles)

http://cuisung.unige.ch/std/ (3 of 5) [07-06-2001 12:11:00]


Structuration des Données Informatiques - Table des Matières

10.3.1. Détection de cycles


10.3.2. Détermination des composantes connexes
10.4. Recherche en largeur ( exercices disponibles)
10.4.1. Méthode générale de parcours de graphe
10.4.2. Méthode générale appliquée au DFS
10.4.3. Méthode générale appliquée au BFS
10.5. Parcours d'un labyrinthe
Chapitre 11. Structures récursives non linéaires : les graphes orientés
11.1. Représentation d'un graphe orienté
11.2. Depth-First Search
11.3. Fermeture transitive ( exercices disponibles)
11.4. Le tri topologique
11.5. Tri topologique inverse ( exercices disponibles)

11.6. Composante fortement connexe d'un graphe orienté ( exercices disponibles)


Autres exercices disponibles
Chapitre 12. Structures récursives non linéaires : les réseaux
12.1. Capacité et écoulement
12.2. Représentation d'un réseau
12.3. Amélioration d'une fonction d'écoulement
12.4. Ecoulement optimal
Chapitre 13. Structures adressables par leur contenu : adressage associatif
13.1. Notion de clé
13.2. Recherche séquentielle
13.3. Recherche dans un tableau ordonné ( exercices disponibles)
13.3.1. Recherche binaire (ou dichotomique, ou logarithmique)
13.3.2. Recherche par interpolation
13.4. Hash-coding ("transformation de clés") ( exercices disponibles)
13.4.1. Chaînage externe
13.4.2. L'adressage ouvert
13.5. Les fonctions de h-code
13.5.1. Les méthodes de division
13.5.2. Les méthodes multiplicatives
Chapitre 14. Structures à support mixte

http://cuisung.unige.ch/std/ (4 of 5) [07-06-2001 12:11:00]


Structuration des Données Informatiques - Table des Matières

14.1. Mémoire virtuelle


14.2. Fichiers séquentiels indexés ( exercices disponibles)
14.2.1. Implantation
14.2.2. Mise-à-jour de fichiers séquentiels indexés
14.2.3. Clés secondaires
14.3. B-arbres (ou arbres de Bayer) ( exercices disponibles)
14.3.1. Recherche dans un B-arbre
14.3.2. Insertion
14.3.3. Structure de données (représentation interne)
14.3.4. Suppression d'un élément
Chapitre 15. Tables et arbres de décision
15.1. Les tables de décision ( exercices disponibles)
15.1.1. Propriétés des tables de décision
15.1.2. Description en terme de type abstrait
15.1.3. Exemple d'implantation
15.1.4. Conversion de tables de décisions en cascade de tests
15.2. Arbres de décision
15.2.1. Parcours d'un labyrinthe
15.2.2. Jeux de nim
15.2.3. Le jeux du Tic-Tac-Toe
15.3. Réalisation d'une procédure MiniMax
15.3.1. Variante de la méthode MiniMax : l'élagage alpha-bêta

Si vous voulez commenter ou faire une suggestion au sujet des informations que nous fournissons, vous
pouvez le faire en suivant ce lien. Bertrand Ibrahim

http://cuisung.unige.ch/std/ (5 of 5) [07-06-2001 12:11:00]


Description du cours "Structures de donnees"

TITRE: STRUCTURES DE DONNEES


Enseignant: Bertrand Ibrahim (MER)

No: 1803 Heures totales: 84 Par semaine: Cours: 4 Exercices: 2 Pratique:

Destinataire Semestre Oblig. Facult. Option Crédits


licence informatique 2 10
diplôme informatique 2 10
certificat . info et stat. 2 10
dipl. math.info. 4

Objectifs
Ce cours a pour but d'introduire un panorama des structures de données complexes en suivant l'approche
de la programmation procédurale.

Contenu
Note: un calendrier d'avancement dans le cours est disponible pour vous permettre de déterminer ce qui a
été abordé à chaque leçon.
● structures de données statiques, types abstraits, notion de pointeur,

● structures dynamiques fondamentales:

❍ chaînes (monodirectionnelles, bidirectionnelles), anneaux (monodirectionnels,


bidirectionnels), piles, files d'attentes,
❍ listes généralisées,

❍ arbres ,

❍ graphes

algorithmes de construction, de parcours et de manipulation; représentations internes, opérations


de base,
● transformation de clés et «hash-coding»,
● structures complexes: séquentiel indexé et B-arbres,
● tables et arbres de décision.

Forme de l'enseignement:
Cours ex-cathedra, exercices, travaux pratiques

http://cuisung.unige.ch/std/Descr.html (1 of 2) [07-06-2001 12:11:40]


Description du cours "Structures de donnees"

Evaluation:
● Contrôles continus :
❍ lundi 7 mai 2001 de 14h00 à 16h00, auditoire U600 (Uni Dufour, 1er sous-sol) - notes

jeudi 21 juin de 14h00 à 16h00, salle 259


● Examen écrit, juillet


● Evaluation du cours par les étudiants (année 2000)

Note: dans les deux formes d'évaluation des étudiants (contrôle continu ou examen écrit), la note sera
laissée en suspend jusqu'à avoir satisfait aux exigences des travaux pratiques (avoir 75% des TPs
acceptés par l'assistant).

Encadrement:
Bertrand Ibrahim (bur. 350), Wolfgang Müller (bur. 336), Yvan Petroff (bur. 306).

Documentation:
livre support de cours et liste d'ouvrages de référence. Liste des questions d'examens avec leurs corrigés.
Enregistrements sonores

Liaison avec d'autres cours:


Préalable requis: Algorithmique ou Introduction à l'informatique.
Préparation pour: informatique théorique, initiation à la recherche opérationnelle et langages
informatiques.

B. Ibrahim
19.04.01

http://cuisung.unige.ch/std/Descr.html (2 of 2) [07-06-2001 12:11:40]


World-Wide algorithm animation - WWW'94 full paper

World-wide algorithm animation

Bertrand Ibrahim
Computer Science Department, University of Geneva,
rue du Général Dufour 24, CH-1211 Geneva 4, Switzerland.

latest update of online version: June 7, 1994


A Postscript version of this document can be found here.
You can also directly try a demonstration, if you wish (allow for ~15 seconds start-up time).

Table of Contents

Abstract

Introduction (same, uncompressed, in case your viewer doesn't support automatic decompression)

Global description of the project (same, uncompressed)

Screen design and user interface (same, uncompressed)

Preparing a program for WWW (same, uncompressed)

Synchronization between the server script and the spawned processes (same, uncompressed)

Conclusion (same, uncompressed)

Acknowledgements (same, uncompressed)

http://cuisung.unige.ch/papers/WWW94/paper.html (1 of 2) [07-06-2001 12:11:50]


World-Wide algorithm animation - WWW'94 full paper

Abstract:
This paper reports on an innovative use of the WWW at the University of Geneva. Indeed, until now, the
web has mostly been used to make information publicly available, whether directly in documents, or
through search engines querying external databases. With the availability of forms, we are now starting
to see WWW viewers used to input information, e.g. to order a pizza or give one's opinion about some
topic.
In the Computer Science department, as a semester project for the Software Engineering class, we are
now implementing an experiment to allow first year students to have not only on-line access to a
hypertextual version of the book used in the Data Structures class, but also to "animate" the algorithms
that are described in the book. That is, the students can run, on the server, the program (or program
segment) and interact with the execution to put breakpoints in the code, display the contents of variables
and advance execution either step by step, or until a breakpoint is met, in much the same way as with a
symbolic debugger.
To do this required the development of a whole set of tools to facilitate, and even automate, the
preparation of the algorithms to allow them to be started and controlled from a WWW client like Mosaic.
It also required designing a mechanism to have the server spawn subprocesses to execute the algorithms
and have the server query the appropriate subprocess for what has to be displayed next, based on the
users' queries.
This paper will describe the technical solutions that had to be designed to make this remote control
feasible. Even though the project described in this paper has educational purposes, many of the solutions
described can prove useful in very different contexts.

Introduction (same, uncompressed)

http://cuisung.unige.ch/papers/WWW94/paper.html (2 of 2) [07-06-2001 12:11:50]


Questions d'examens de Structures de données ainsi que leurs corrigés

Questions d'examens de Structures de


données
ainsi que leurs corrigés
Contrôle continu du 7 mai 2001 (notes)
● Arbre lexicographique
● Arbre de tri

Examen écrit du 4 octobre 2000 (notes)


● Arbres + chaînes
● Les listes
● Les B-arbres

Examen écrit du 3 juillet 2000 (notes)


● Conversion arbre-chaîne
● Arbres multiples
● Listes
● Graphes orientés

Contrôle continu du 22 juin 2000 (notes)


● Fermeture transitive
● Graphe orienté
● parcours d'un B-arbre

Contrôle continu du 11 mai 2000 (notes)


● Chaînes
● Chaînes
● Arbre de tri

http://cuisung.unige.ch/std/questions.html (1 of 5) [07-06-2001 12:12:30]


Questions d'examens de Structures de données ainsi que leurs corrigés

Examen écrit du 28 février 2000


● Structures statiques
● Anneaux bidirectionnels
● Arbres dépliables
● B-arbres

Examen écrit du 11 octobre 1999


● Anneaux bidirectionnels
● Arbres
● Listes
● Chemin le plus court dans un graphe

Examen écrit du 30 juin 1999 (notes)


● Anneaux bidirectionnels
● Arbres dépliables
● Le chemin le plus court
● Hash-Coding

Contrôle continu du 14 juin 1999 (notes)


● Graphes orientés
● B-Arbres
● Adressage associatif par transformation de clés (Hash-code)

Contrôle continu du 29 avril 1999 (notes)


● Chaînes bidirectionelles
● Arbre lexicographique

Examen écrit de février 1999


http://cuisung.unige.ch/std/questions.html (2 of 5) [07-06-2001 12:12:30]


Questions d'examens de Structures de données ainsi que leurs corrigés

Examen écrit du 7 octobre 1998


● Arbre binaire
● Structures de graphes orientés (tri topologique inverse)
● comparaison de B-arbres et B*-arbres
● Hash-coding

Examen écrit du 25 juin 1998


● Arbres dépliables
● Recherche dichotomique
● Hash-coding
● Tables de décision

Contrôle continu du 15 juin 1998


● Fichiers séquentiels indexés
● Recherche dans un B-arbre
● Le chemin du moindre coût

Contrôle continu du 4 mai 1998


● Chaînes bidirectionnelles
● Chaînes mono-directionnelles
● Anneaux bidirectionnels
● Arbre syntaxique

Examen écrit de février 1998


● arbre dépliable de tri
● Fichiers séquentiels indexés

Examen écrit du 13 octobre 1997


● Structures de graphes
● un réseau de transport

http://cuisung.unige.ch/std/questions.html (3 of 5) [07-06-2001 12:12:30]


Questions d'examens de Structures de données ainsi que leurs corrigés

Examen écrit de juillet 1997


● Arbres binaires
● Graphe orienté
● B-arbre

Contrôle continu du 16 juin 1997


● Arbre lexicographique
● Hash-code
● Tables de décision

Contrôle continu du 28 avril 1997


● Chaînes bidirectionnelles
● Chaînes bidirectionnelles
● Arbre généalogique

Examen écrit du 5 mars 1997


● Tri dans une chaîne
● Arbres syntaxiques et chaînes
● B-arbre

Examen écrit du 15 octobre 1996


● Chaînes bidirectionnelles
● Fichiers séquentiels indexés et B-arbres

Examen écrit du 9 juillet 1996


● Chaînes mono-directionnelles
● Réseau autoroutier

http://cuisung.unige.ch/std/questions.html (4 of 5) [07-06-2001 12:12:30]


Questions d'examens de Structures de données ainsi que leurs corrigés

Contrôle continu du 17 juin 1996


● Anneaux bidirectionnels
● Arbre de tri
● Tri topologique inverse
● B-arbre

Contrôle continu de mars 1996


● Structure de chaîne

Contrôle continu du 20 juin 1995


● Chaînes bidirectionnelles
● Arbre lexicographique
● Plus courts chemins dans un graphe

http://cuisung.unige.ch/std/questions.html (5 of 5) [07-06-2001 12:12:30]


Enregistrements du cours Structures de Données

Enregistrements du cours Structures de


Données
Ce répertoire contient les enregistrement sonores des leçons (cf. remarques) du cours de Structures de
Données. Les fichiers sont au format MP3 (MPEG audio level 3) et peuvent être lus par la plupart des
logiciels multimédia t.q. Windows Media Player, RealPlayer, ou par des logiciels spécifiques du format
MP3, t.q. WinAmp, MP3 Studio.
Le nom des fichiers indiquent la date du cours correspondant sous la forme yymmdd, où yy sont les deux
derniers chiffres de l'année, mm deux chiffres indiquant le mois et dd deux chiffres indiquant le jour.
Bertrand Ibrahim

Name Last modified Size Description

Parent Directory 10-Apr-2001 16:18 -

cours-010313-part1.mp3 13-Mar-2001 13:35 5.0M

cours-010313-part2.mp3 13-Mar-2001 13:41 4.9M

cours-010319-part1.mp3 19-Mar-2001 11:29 5.0M

cours-010319-part2.mp3 19-Mar-2001 11:36 5.1M

cours-010320-part1.mp3 20-Mar-2001 13:41 5.5M

cours-010320-part2.mp3 20-Mar-2001 13:48 4.6M

cours-010326-part1.mp3 26-Mar-2001 12:45 4.5M

cours-010326-part2.mp3 26-Mar-2001 12:56 5.0M

cours-010327-part1.mp3 27-Mar-2001 15:23 4.8M

cours-010327-part2.mp3 27-Mar-2001 15:32 5.4M

cours-010402-part1.mp3 02-Apr-2001 11:04 4.8M

cours-010402-part2.mp3 02-Apr-2001 11:14 5.1M

cours-010403-part1.mp3 03-Apr-2001 13:01 5.2M

cours-010403-part2.mp3 03-Apr-2001 13:09 5.5M

http://cuisung.unige.ch/std/audio/ (1 of 3) [07-06-2001 12:13:03]


Enregistrements du cours Structures de Données

cours-010409-part1.mp3 09-Apr-2001 12:36 5.0M

cours-010409-part2.mp3 09-Apr-2001 13:31 4.9M

cours-010410-part2.mp3 10-Apr-2001 14:38 5.4M

cours-010423-part1.mp3 24-Apr-2001 14:38 5.1M

cours-010423-part2.mp3 24-Apr-2001 16:38 5.1M

cours-010424-part1.mp3 24-Apr-2001 17:23 5.2M

cours-010424-part2.mp3 24-Apr-2001 17:29 4.5M

cours-010430-part1.1..> 30-Apr-2001 12:09 5.1M

cours-010430-part2.1..> 30-Apr-2001 13:55 5.7M

cours-010507-part1.1..> 07-May-2001 11:33 5.5M

cours-010507-part2.1..> 07-May-2001 11:41 5.1M

cours-010508-part1.1..> 08-May-2001 12:37 5.4M

cours-010508-part2.1..> 08-May-2001 12:44 4.8M

cours-010514-part1.1..> 14-May-2001 11:57 5.9M

cours-010514-part2.1..> 14-May-2001 12:05 5.0M

cours-010515-part1.1..> 15-May-2001 19:20 5.1M

cours-010515-part2.1..> 15-May-2001 14:17 1.3M

cours-010521-part1.1..> 21-May-2001 11:25 4.8M

cours-010521-part2.1..> 21-May-2001 11:35 5.9M

cours-010522-part1.1..> 22-May-2001 14:23 5.2M

cours-010522-part2.1..> 22-May-2001 14:32 5.2M

cours-010528-part1.1..> 28-May-2001 13:50 4.7M

cours-010528-part2.1..> 28-May-2001 13:59 5.5M

cours-010529-part1.1..> 29-May-2001 17:05 5.4M

cours-010529-part2.1..> 29-May-2001 17:12 4.7M

http://cuisung.unige.ch/std/audio/ (2 of 3) [07-06-2001 12:13:03]


Enregistrements du cours Structures de Données

cours-010605-part1.1..> 05-Jun-2001 13:15 4.8M

cours-010605-part2.1..> 05-Jun-2001 13:22 4.9M

http://cuisung.unige.ch/std/audio/ (3 of 3) [07-06-2001 12:13:03]


Structuration des Données Informatiques - 0.

Introduction
Vers les années 70, la programmation des ordinateurs fut reconnue comme une discipline dont la maîtrise
était fondamentale et indispensable au succès de bien des projets scientifiques et techniques et qui était
susceptible d'être traitée et présentée scientifiquement. Elle passa du stade de l'artisanat à celui de
l'enseignement académique. Les contributions essentielles de Dijkstra et Hoare ne sont pas étrangères à
cette évolution.
● Dijkstra : "Notes on Structured Programming".

● Hoare : "An Axiomatic Basis for Computer Programming".

Ces deux articles concentrent leur attention principalement sur la structure des algorithmes représentés
par les textes des programmes. Cette nouvelle méthodologie de la programmation a clairement montré
que l'aspect de structuration, d'organisation des données lui était intimement lié.
Les programmes sont la formulation concrète d'algorithmes abstraits basés sur une représentation et une
structure particulière des données. A ce niveau il faut mentionner la contribution importante de Hoare :
"Notes on Data Structuring".
Il a montré qu'il est impossible de structurer correctement un ensemble de données si l'on n'a pas la
connaissance de l'algorithme qui va s'appliquer sur ces données et que, réciproquement, le choix de
l'algorithme peut dépendre fortement de la structure des données. En conclusion, développement de
programmes (d'algorithmes) et structuration des données sont intimement liés.
Dans les chapitres qui suivent, il y aura donc des exemples d'algorithmes illustrant la construction et
l'utilisation des structures de données décrites. Dans l'espoir de rendre ces exemples plus lisibles, les
symboles utilisés sont formés de plusieurs mots accolés dont les initiales sont en majuscule (par exemple
SommetDeDepart). De plus, tout ce qui est programme ou extrait de programme utilise une police de
caractères à chasse fixe et répond aux conventions d'indentation suivantes :
● pour une instruction composée, le mot réservé begin est placé en fin de ligne et les instructions
qui suivent sont indentées de deux caractères, y compris le mot réservé end qui termine
l'instruction composée. Par exemple :

if UneExpressionBooléenne then begin


{ suite d'instructions }
end
else begin
{ suite d'instructions }
end; { if }
● si une instruction simple ne tient pas sur une seule ligne, la deuxième ligne est indentée de deux
caractères si elle correspond à une instruction complète, comme :

if UneExpressionBooléenne then
{ une instruction très longue };

http://cuisung.unige.ch/std/0intro.html (1 of 2) [07-06-2001 12:13:33]


Structuration des Données Informatiques - 0.

while UneExpressionBooléenne do
{ une instruction très longue };
ou justifiée à droite si c'est le complément de la première ligne :

UneVariableDuProgramme :=
UneTropLongueExpression;

1. Notion de typage

Table des matières.

http://cuisung.unige.ch/std/0intro.html (2 of 2) [07-06-2001 12:13:33]


Structuration des Données Informatique - 1.0.

1. Notion de typage
Définition : Le typage est l'association, à un objet (une variable), d'un ensemble de valeurs possibles ainsi
que d'un ensemble d'opérations admissibles sur ces valeurs. C'est aussi, pour ce qui concerne les aspects
de sécurité, un mécanisme des langages de programmation qui a pour but de vérifier d'une part que les
valeurs fournies lors d'une opération sont compatibles avec le(s) type(s) correspondant à l'opération et
d'autre part que le résultat d'une opération ne transgresse aucune contrainte liée au type de cette
opération.

1.1. Différence de typage selon les langages

Table des matières.

http://cuisung.unige.ch/std/01.0.html [07-06-2001 12:13:36]


Structuration des Données Informatique - 1.1.

1.1. Différence de typage selon les langages


Les langages non-typés sont très peu nombreux. Les plus courants sont les langages machine, c'est-à-dire
les ensembles de valeurs numériques correspondants aux jeux d'instructions de différentes machines. Il
va sans dire que ces langages ne sont quasiment plus jamais utilisés de nos jours, si ce n'est par l'unité
centrale de tout ordinateur.
Les langages assembleurs permettent d'associer des mnémoniques aux différentes opérations que le
processeur central d'un ordinateur peut effectuer et peuvent être considérés comme très faiblement typés
puisqu'ils admettent généralement la déclaration de constantes entières dans différentes bases, des
constantes caractère ou chaîne de caractères et éventuellement des constantes en virgule flottante. Il n'est
toutefois fait aucun contrôle quant à la correspondance entre le type de la constante et l'opération à
effectuer.
Certains langages tels LISP ou PROLOG peuvent être considérés comme faiblement typés car, s'ils
associent un type au contenu des variables qu'ils manipulent, ce type n'est pas déterminé à l'avance par le
programmeur mais plutôt déterminé à l'exécution, par le contexte, lors de l'attribution d'une nouvelle
valeur à une variable. Une variable peut donc changer de type au cours de l'exécution du programme;
aucun contrôle n'est fait quant à la validité de l'opération.
D'autres langages dits de "haut niveau" tels que FORTRAN, BASIC peuvent être considérés comme
moyennement typés car, si un type permanent est associé à chaque variable d'un programme (parfois
implicitement), aucun contrôle n'est fait quant à la correspondance entre les opérations effectuées et le
type des variables sur lesquelles ces opérations agissent.
La tendance des langages généraux de ces dernières années (ALGOL-1968, PASCAL-1971, MODULA
2-1980, ADA-1980) va vers des langages fortement typés. Dans ces langages toute variable utilisée
dans un programme doit avoir un type associé et un contrôle est effectué lors de la compilation et lors de
l'exécution pour s'assurer que toute modification de variable ou tout passage de paramètres à des
procédures respecte les contraintes de typage, de façon à pouvoir détecter, au plus tôt dans le processus
de mise au point d'un programme, un maximum d'erreurs qui peuvent s'y être glissées. Pour ce faire, on
augmente la redondance des informations contenues dans les programmes pour pouvoir déceler les
éventuelles incohérences.

1.2 Contrôle de typage

Table des matières.

http://cuisung.unige.ch/std/01.1.html [07-06-2001 12:13:39]


Structuration des Données Informatique 1.2.

1.2. Contrôle de typage


Il existe deux approches au contrôle de typage qui est effectué par un compilateur pour un langage
fortement typé lorsque deux objets différents sont mis en correspondance (lors d'une affectation, d'une
comparaison ou d'un passage de paramètre) : l'équivalence de nom et l'équivalence de structure.

1.2.1 Equivalence de nom


Avec l'équivalence de nom, il faut que deux variables (ou paramètres) aient été déclarées en utilisant le
même identificateur de type pour qu'elles puissent être considérées comme compatibles.
Exemple
type T = record
PeuImporte: Quelconque;
end; { T }

var X, Y: T;

Z: record
PeuImporte: Quelconque;
end; { Z }
X et Y sont compatibles, mais Z n'est compatible ni avec X, ni avec Y. De même, si l'on a :
type T = ...;
MemeT = T;

var U: T;
V: MemeT;
U et V sont incompatibles.
Un problème lié à l'équivalence de nom est qu'il devient plus difficile d'écrire des sous-programmes
d'utilité générale. Par exemple, un sous-programme qui aurait comme paramètre formel un entier ne
pourrait pas être utilisé avec un paramètre effectif de type intervalle d'entier.

1.2.2 Equivalence de structure


Avec l'équivalence de structure, pour considérer deux variables comme compatibles, il suffit que l'on ait
identité des déclarations de type où l'on aurait remplacé tout identificateur de type déclaré par
l'utilisateur par la déclaration de type correspondante.
Dans les exemples vus plus haut, X, Y et Z sont compatibles. Il en est de même pour U et V.
Les problèmes liés à l'équivalence de structure sont une plus grande complexité de contrôle du typage à
la compilation et une ambiguïté possible des déclarations. Par exemple, avec les déclarations suivantes :

http://cuisung.unige.ch/std/01.2.html (1 of 3) [07-06-2001 12:13:43]


Structuration des Données Informatique 1.2.

type Complex1 = record


PartieReelle,
PartieImaginaire: real;
end; { Complex1 }

Complex2 = record
PartieImaginaire,
PartieReelle: real;
end; { Complex2 }
Complex1 et Complex2 sont compatibles. Toutefois, lors d'une affectation impliquant les deux types, la
correspondance des champs de vrait-elle se faire en fonction de leurs positions relatives ou en fonction de
leur nom ? Le mécanisme devant fonctionner même si les noms des champs sont différents, c'est une
correspondance positionnelle qui est utilisée.

1.2.3. Cas de quelques langages d'usage courant


1.2.3.1. Pascal

Dans sa spécification du langage Pascal, Wirth reste assez vague sur le sujet. Dans son manuel du Pascal,
il dit que, pour l'affectation, l'expression doit être de même type que la variable. Il dit aussi qu'un type de
donnée est spécifié soit par la déclaration de variable, soit par un identificateur. Ceci est suffisamment
ambigu pour être sujet à interprétation. Dans un grand nombre d'implantations, c'est l'équivalence de
structure qui est utilisée. Le type de chaque composante d'une des structures doit être le même que la
composante correspondante de l'autre structure.

1.2.3.2. Ada

En Ada, c'est une variante de l'équivalence de nom qui est utilisée. Ada fait, en effet, la distinction entre
type de base, type dérivé et sous-type. Un type de base correspond soit à une déclaration de type utilisant
un constructeur (enregistrement, tableau, énumération), soit à un type prédéfini du langage (integer,
boolean, . . .).
Un type dérivé fait référence à un type de base en y apportant une restriction éventuelle. Un type dérivé
bénéficie des mêmes primitives (opérations prédéfinies, sous-programmes utilisant le type de base en
paramètre) que le type de base, mais est incompatible avec ce type de base.
Un sous-type fait lui aussi référence à un type de base en y apportant une restriction éventuelle. Ce
sous-type reste toutefois compatible avec le type de base pour autant que l'opération impliquant le type
de base et son sous-type ne transgresse pas la restriction du sous-type.
La distinction entre ces trois cas se fait dans la déclaration :

type <NomDuTypeDeBase> is
<TypePredefiniOuConstructeur>;

type <NomDuTypeDerive> is

http://cuisung.unige.ch/std/01.2.html (2 of 3) [07-06-2001 12:13:43]


Structuration des Données Informatique 1.2.

new <NomDuTypeDeBase>
<RestrictionEventuelle>;

subtype <NomDuSousType> is
<TypeDeBase> <RestrictionEventuelle>;

1.2.3.3. Modula-2

Modula-2 utilise une variante de l'équivalence de nom plus laxiste qu'Ada. Si l'on reprend les exemples
vus dans le paragraphe sur l'équivalence de nom, comme pour Ada, X et Y sont considérés comme
compatibles, mais Z n'est compatible ni avec X, ni avec Y. Par contre, contrairement à Ada, U et V sont
considérés comme compatibles.

1.3. Nomenclature

Table des matières.

http://cuisung.unige.ch/std/01.2.html (3 of 3) [07-06-2001 12:13:43]


Structuration ses Données Informatique 1.3.

1.3. Nomenclature
Au vu des langages préalablement cités ci-dessus, on peut ébaucher une nomenclature de typage, tous
langages confondus (cette nomenclature est sûrement incomplète) :
● Les types de base (scalaires)

❍ discrets :

■ entiers

■ énumérés

■ intervalles

❍ réels:

■ virgule flottante

■ virgule fixe

● Les types structurés (composés)

❍ structures statiques

■ tableaux

■ enregistrements

■ chaînes de caractères

■ ensembles

❍ structures dynamiques

■ structures non récursives : les fichiers séquentiels

■ structures récursives

■ structures récursives linéaires : (chaînes, anneaux, chaînes bidirectionnelles,


anneaux bidirectionnels)
■ structures récursives non-linéaires : (les listes, les arbres, les graphes, les
réseaux)
● Les types exécutables

❍ procédures (Modula 2)

❍ tâches (Ada)

❍ prédicats (Prolog)

Comme on le verra par la suite, dans un langage donné, certains de ces types sont prédéfinis et certains
autres doivent être construits par le programmeur. Par exemple, les listes sont prédéfinies en Prolog ou en
Lisp alors qu'elles doivent être construites en Pascal. Certains types ne pourront toutefois pas être
construits par le programmeur; ainsi, il est difficile, voire impossible, d'implanter les types exécutables
dans un langage qui n'a rien de prévu à cet effet.
Pour la suite de ce cours, nous nous baserons principalement sur le langage PASCAL, nous n'aborderons
donc pas dans le détail les types exécutables.

http://cuisung.unige.ch/std/01.3.html (1 of 2) [07-06-2001 12:13:45]


Structuration ses Données Informatique 1.3.

2. Les types scalaires.


Table des matières.

http://cuisung.unige.ch/std/01.3.html (2 of 2) [07-06-2001 12:13:45]


Structuration des Données Informatique - 2.0.

2. Les types scalaires


Les types scalaires sont les types de données (généralement prédéfinis dans le langage de
programmation) ne contenant qu'une seule information élémentaire. Ils se partagent en deux groupes : les
types discrets et les réels.

2.1 Les types discrets.

Table des matières.

http://cuisung.unige.ch/std/02.0.html [07-06-2001 12:13:47]


Structuration des Données Informatique - 2.1

2.1. Les types discrets


Ils représentent des objets dont les valeurs possibles sont énumérables, en y ajoutant la contrainte d'un
nombre fini de valeurs.

2.1.1 Les entiers


Ils représentent des valeurs numériques signées ne comportant pas de partie fractionnaire. Le nombre de
valeurs représentables sur un ordinateur étant fini, les entiers sont généralement limités à un intervalle de
valeurs possibles : de -MaxInt-1 à MaxInt, MaxInt étant donc le plus grand nombre entier représentable.
Pour beaucoup d'implantations sur micro-ordinateurs, MaxInt vaudra 32767. Pour les implantations sur
plus grosses machines, on aura une valeur beaucoup plus grande, typiquement 232.
Les opérations possibles sur des entiers sont : l'addition, la soustraction, la multiplication, la division, le
modulo et la comparaison ( =, >, >=, <, <=, <> ).
En MODULA-2, il existe un type prédéfini d'entiers non signés (CARDINAL).

2.1.2 Les types énumérés


2.1.2.1 Les booléens

Les objets de type booléen ne peuvent prendre que deux valeurs possibles : true et false. Les expressions
comportant des valeurs booléens sont appelées des expressions logiques. Les opérations possibles sont :
and, or, not (opérateur monadique) et la comparaison ( =, <> ).
Il est à noter que les opérateurs de comparaison donnent un résultat booléen, même si les opérandes sont
d'un autre type.
Exemple
1 < 3 résultat vrai ou faux
'a' < 'c' "
1.5 > 3.6 "
true = false "
Sur certaines implantations, une variable logique non initialisée peut avoir une valeur illégale.

2.1.2.2. Les caractères

Le jeu de caractères d'une machine est sensé être l'énumération, dans un certain ordre, des lettres et
signes affichables sur un écran ou imprimables sur papier. Il existe une norme (ASCII) pour un jeu de
128 caractères comprenant non seulement des caractères imprimables mais aussi des caractères de
"contrôle". Cette norme ne comporte pas de lettres accentuées.
Sur la plupart des machines, on utilise un octet pour représenter un caractère; il est donc possible d'en
énumérer 256 différents. Si les 128 premiers caractères sont presque toujours ceux de la norme ASCII,
chaque constructeur a ses propres conventions pour les 128 caractères restants.

http://cuisung.unige.ch/std/02.1.html (1 of 2) [07-06-2001 12:13:51]


Structuration des Données Informatique - 2.1

Les opérations possibles sont principalement les comparaisons : =, <>, >, <, <=, >= et la fonction de
conversion entier ->caractère : chr().

2.1.2.3. Les énumérations définies par l'utilisateur

Il est possible de déclarer une suite ordonnée de symboles qui ont un sens particulier pour l'utilisateur.
Par exemple, on peut déclarer un

type semaine = (Dimanche, Lundi, Mardi,


Mercredi, Jeudi, Vendredi,
Samedi).
Toute variable du type Semaine pourra prendre une de ces sept valeurs (et aucune autre valeur ne sera
légale). Il est à noter que la plupart des implantations du PASCAL ne permettent pas d'imprimer, lors de
l'exécution, la valeur (le symbole) d'une variable de ce type.
Comme pour les caractères, les opérations possibles sont les comparaisons ( =, <>, <, >, <=, >= ).

2.1.3. Les intervalles


Les intervalles ne forment pas, à proprement parler, un type de base. Il s'agit plutôt de types générés en
restreignant l'ensemble des valeurs possibles d'un des types de base vus précédemment.
Exemple

type JourOuvrable = Lundi .. Vendredi;


Minuscule = 'a' .. 'z';
Naturel = 0 .. MaxInt;
Toutes les opérations applicables au type de base sont applicables au type intervalle qui en est dérivé.
L'intérêt de l'utilisation des intervalles réside dans l'amélioration de la lisibilité des programmes (on voit
plus clairement quelles sont les valeurs possibles) et l'augmentation de la sécurité de programmation (des
valeurs illégales peuvent être automatiquement détectées à l'exécution).

2.2. Fonctions prédéfinies sur les types discrets

Table des matières.

http://cuisung.unige.ch/std/02.1.html (2 of 2) [07-06-2001 12:13:51]


Structuration des Données Informatiques - 2.2.

2.2. Fonctions prédéfinies sur les types discrets


Pour l'ensemble des types discrets, il existe en Pascal des fonctions prédéfinies :
● ord() pour donner la position relative de l'argument par rapport à la première valeur de
l'énumération.
Exemple ord(Lundi) = 1
ord(false) = 0
ord('A') = 65
pour un entier i, ord(i) = i !!
La plupart du temps la fonction "ord" n'est pas implantée mais simplement considérée comme une
notation de conversion de type. Ceci explique le fait que la fonction "ord", avec pour paramètre un
nombre entier négatif, puisse retourner un résultat négatif.
● pred() pour donner l'élément qui précède l'argument dans l'énumération. Un appel à la fonction,
avec pour argument le premier élément de l'énumération, provoque une erreur à l'exécution.
Exemple pred(Lundi) = Dimanche
pred(Dimanche) n'existe pas
pred('b') = 'a'
pred(i) = i-1

succ() pour donner l'élément qui suit l'argument dans l'énumération. Un appel à la fonction, avec

pour argument le dernier élément de l'énumération, provoque une erreur à l'exécution.
Exemple succ(Dimanche) = Lundi
succ(Samedi) n'existe pas
succ('a') = 'b'
succ(i) = i+1

2.3. Les réels

Table des matières.

http://cuisung.unige.ch/std/02.2.html [07-06-2001 12:13:53]


Structuration des Données Informatiques - 2.3

2.3. Les réels


De tous les types de base, ce sont les nombres réels qui posent le plus de problèmes de représentation.
L'ensemble des nombres réels étant, par définition, infini et indénombrable, il est impossible de le
représenter avec une totale exactitude dans un ordinateur. La représentation qui en est faite ne peut donc
constituer qu'une approximation. Il existe d'ailleurs plusieurs techniques de représentation, chacune ayant
ses avantages et ses inconvénients.

2.3.1. Représentation en virgule flottante


La représentation en virgule flottante consiste à représenter un nombre réel à l'aide de 3 composantes : le
signe (S), l'exposant (E) et la mantisse (M). L'on considère que la virgule décimale est placée juste à
gauche de la mantisse et que le bit de poids le plus élevé de la mantisse est à 1 (sauf pour représenter
0.0), de façon que la mantisse représente toujours un nombre compris dans l'intervalle [0.5,1.0] (valeur
normalisée). Le nombre réel représenté correspond alors à S*(M*2E).
Exemple : représentation de valeurs décimales en binaire

Une valeur normalisée est une valeur dont la mantisse a le bit de plus fort poids à 1 (sauf pour représenter
la valeur 0). Dans l'exemple précédent, la représentation (S, M, E) était à chaque fois normalisée. Il existe
différentes variantes pour ce qui est de la manière de représenter des exposants négatifs ou des réels
négatifs, mais quelle que soit la manière, les qualités et inconvénients restent à peu près les mêmes.
Exemples de représentation de nombres et d'exposants négatifs:
● sur une certaine variété de machines ayant des mots machine de 36 bits, les nombres réels en
simple précision ont 1 bit de signe, 8 bits d'exposant et 27 bits de mantisse. La représentation est
en complément à un avec un exposant biaisé. Cela signifie que,
pour un exposant de 0, le champ E contiendra 128,
pour un exposant de 127, le champ E contiendra 255 et
pour un exposant de -128, le champ E contiendra 0.
Si le nombre a un signe négatif, l'ensemble des bits sont inversés.

http://cuisung.unige.ch/std/02.3.html (1 of 3) [07-06-2001 12:14:03]


Structuration des Données Informatiques - 2.3

● sur un co-processeur arithmétique couramment utilisé dans des micro-ordinateurs, les nombres
réels sont représentés sur 32 bits avec un bit de signe, un exposant de sept bits en complément à
deux et une mantisse en valeur absolue.
Les principales qualités de la représentation en virgule flottante sont :
● l'utilisation optimale de la représentation binaire d'un ordinateur (si les nombres sont normalisés et

● une répartition très étalée de la "gamme" de nombres représentables (typiquement 1040 pour de la
simple précision et 101000 pour de la double précision).
Les principaux inconvénients étant que :
● il n'y a pas moyen d'éviter des erreurs d'arrondi à chaque calcul et

● les opérations arithmétiques sont plus complexes que sur des nombres entiers.

Par exemple, il n'est pas du tout certain que l'expression logique


sqr( sqrt(2.0) ) = 2.0

correspondant à soit vraie

2.3.2. Représentation en virgule fixe


La représentation en virgule fixe consiste à utiliser une représentation similaire aux nombres entiers en
attribuant un nombre de "bits" fixes à la partie fractionnaire et un autre nombre de "bits" à la partie
entière.
L'avantage principal de cette représentation étant de pouvoir utiliser les opérations arithmétiques des
nombres entiers auxquelles s'ajoute l'opération de décalage. Ces opérations sont très efficaces et
accélèrent d'autant le calcul.
Les principaux inconvénients sont que la répartition de la "gamme" de nombres représentables est
beaucoup plus étroite que pour la virgule flottante (typiquement 1010 à 1020) et que les erreurs d'arrondis
sont aussi inévitables et même s'accumulent beaucoup plus vite.
Exemple : pour des nombres tenant sur 32 bits, on pourrait avoir 1 bit de signe, 23 bits pour la partie
entière et 8 bits pour la partie fractionnaire. Le plus grand nombre représentable serait environ 223 soit
inférieur à 108 et le plus petit nombre représentable serait 2-8 soit environ 10-2.

2.3.2.1. Représentation BCD

Il existe une variante de la représentation en virgule fixe qui s'appelle la représentation BCD (Binary
Coded Decimal). Elle est surtout utilisée dans les langages à usage commercial (tel que COBOL) et
permet de représenter des nombres comme une suite de chiffres allant de 0 à 9, l'emplacement de la
virgule étant fourni séparément. L'idée de cette représentation étant de ne pas avoir d'erreur d'arrondi tant
que l'on se limite à utiliser des nombres en base 10 ayant un nombre limité de chiffres.
Exemple

http://cuisung.unige.ch/std/02.3.html (2 of 3) [07-06-2001 12:14:03]


Structuration des Données Informatiques - 2.3

Fig.2.1. Représentation BCD.


Ce qu'il faut retenir de tout cela, c'est que les nombres réels, tels qu'ils sont représentés en machine, n'ont
pas les mêmes propriétés qu'en mathématique. Par exemple, (X/Y)*Y n'est pas forcément égal à X.
Les langages FORTRAN, PASCAL et MODULA II, ainsi que beaucoup d'autres langages, n'offrent que
la représentation en virgule flottante. ADA et COBOL offrent les deux possibilités. Pour la
représentation en virgule fixe, COBOL permet de spécifier le nombre de chiffres significatifs alors que
ADA permet de définir l'intervalle de valeurs possibles ainsi que l'écart entre deux valeurs consécutives.

2.4. Compatibilité et conversion de types

Table des matières.

http://cuisung.unige.ch/std/02.3.html (3 of 3) [07-06-2001 12:14:03]


Structuration des Données Informatiques - 2.4

2.4. Compatibilité et conversion de types


Lors d'une affectation, de l'évaluation d'expressions ou du passage de paramètres à une procédure, on
peut être en présence d'objets (variables, constantes ou littéraux) de types différents. Cette combinaison
de types pourra être acceptable ou non selon certaines règles qui dépendent du langage. Pour le langage
PASCAL, les règles sont les suivantes :

2.4.1. Compatibilité
● Affectation : le tableau ci-dessous indique quel peut être le type de l'expression affectée à une
variable.
type de la variable à gauche de l'affectation type de l'expression à droite de l'affectation
réel entier ou intervalle d'entiers
discret même type discret ou intervalle
intervalle type de base de l'intervalle
● Evaluation d'expression : le tableau ci-dessous montre quel est le type associé à une expression en
fonction du type de ses composants.
type de
type des composants
l'expresion
entier entier ou intervalle d'entier
réel réel & entier ou intervalle d'entier, réel
discret discret, intervalle du même type

Il est à noter que les opérateurs de comparaison admettent des arguments de n'importe quel type de
base pourvu que les types des deux arguments soient compatibles au sens de l'affectation; le
résultat, lui, est toujours de type booléen. A cause de cela, l'utilisation des parenthèses est parfois
nécessaire pour ôter certaines ambiguïtés dans l'expression.
Exemple d'expression ambiguë : A = B and C = D
A = B and C = D pourrait être interprété comme ((A=B) and C) = D, ce qui impliquerait que C et D
soient booléens pour que l'expression soit valide;
une autre interprétation serait A = (B and (C=D)), qui serait valide si A et B étaient booléens;
une troisième interprétation serait A = ((B and C) = D), ce qui impliquerait que A, B, C et D soient
booléens;
finalement, une quatrième interprétation serait (A=B) and (C=D), ce qui impliquerait que A soit de type
compatible avec B et C compatible avec D.
Cette ambiguïté est parfois levée par la spécification de règles de précédence indiquant dans quel ordre
les opérations d'une expression doivent être évaluées.
● passage de paramètre

http://cuisung.unige.ch/std/02.4.html (1 of 3) [07-06-2001 12:14:07]


Structuration des Données Informatiques - 2.4

Les règles sont les mêmes que pour l'affectation, avec, au lieu de la variable à gauche de l'affectation, le
paramètre formel et, au lieu de l'expression à droite, le paramètre effectif.

2.4.2. Conversion de type


La conversion de type, en PASCAL, est généralement faite explicitement à l'aide de fonctions de
conversions. Seule la conversion entier ->réel est faite implicitement lorsque c'est nécessaire. Les
fonctions de conversions prédéfinies sont :
fonction type de l'argument type de résultat
ord discret entier
chr entier caractère
trunc réel entier
round réel entier

Le langage ADA impose, quant à lui, des contraintes beaucoup plus strictes. Il fait la distinction entre
types dérivés et sous-types. Un sous-type, de même qu'un type dérivé, consiste en une restriction
apportée à un type de base. Un sous-type reste compatible avec son type de base alors qu'un type dérivé
est incompatible avec son type de base.
Exemples

subtype OneDigit is integer range 0..9;


subtype JourOuvrable is Semaine
range Lundi..Vendredi;
type Entier is new integer;
type MilieuDeSemaine is new Semaine
range Mardi..Jeudi;
Dans les exemples qui précèdent,
OneDigit est compatible avec integer,
JourOuvrable est compatible avec Semaine,
mais Entier n'est pas compatible avec integer
et MilieuDeSemaine n'est pas compatible avec Semaine.
Il est toutefois possible de faire une conversion explicite d'un type dérivé vers son type de base (ou
vice-versa) en préfixant l'objet du type à convertir par le type du résultat. Par exemple, si Jour est une
variable de type Semaine, MilieuDeSemaine(Jour) est de type MilieuDeSemaine (il y aura
erreur si la valeur associée à la variable Jour n'est pas comprise dans l'ensemble des valeurs possibles
pour le type MilieuDeSemaine ).
L'intérêt de cette distinction entre types dérivés et sous-types est une fois encore au niveau de la sécurité
de programmation. Par exemple, si une variable est sensée représenter une superficie et une autre
variable sensée représenter une longueur, cela n'aurait pas beaucoup de sens de vouloir les additionner,
même si elles sont toutes les deux des valeurs numériques entières.

http://cuisung.unige.ch/std/02.4.html (2 of 3) [07-06-2001 12:14:07]


Structuration des Données Informatiques - 2.4

Modula-2, quant à lui, est très similaire au Pascal sur ce plan, au détail près qu'il n'a pas de conversion
explicite d'entiers en réels. A la place, il faut utiliser une fonction de conversion ( float ).

3. Les structures statiques


Table des matières.

http://cuisung.unige.ch/std/02.4.html (3 of 3) [07-06-2001 12:14:07]


Structuration des Données Informatiques - 3.0.

3. Les structures statiques


3.1. Structures cartésiennes simples

Table des matières.

http://cuisung.unige.ch/std/03.0.html [07-06-2001 12:14:30]


Structuration des Données Informatiques - 3.1.

3.1. Structures cartésiennes simples


Les structures cartésiennes simples sont des structures regroupant plusieurs composantes de type de base.
Nous verrons ici trois types de structures cartésiennes simples :
● les tableaux,

● les enregistrements,

● les ensembles.

Des variables ayant ces structures fondamentales ne changent que de valeurs, jamais de structure ou
d'ensemble de valeurs de base. Conséquence : l'espace qu'elles occupent en mémoire reste constant.

3.1.1. Les tableaux


Les tableaux sont constitués d'un regroupement de données homogènes (ayant le même type) de type
quelconque. Les données individuelles sont repérées par un sélecteur que l'on nomme indice du tableau.
L'indice d'un tableau doit être de type énuméré. Cela pourra donc être un type défini par l'utilisateur, un
intervalle d'entiers, de caractères ou de booléens.
Exemple
type Index = 0..9;
Semaine = (Dimanche, Lundi, Mardi,
Mercredi, Jeudi, Vendredi,
Samedi);
T1 = array [Index] of integer;
T2 = array [0..9] of integer;
T3 = array [boolean] of char;
T4 = array [false..true] of char;
T5 = array [char] of integer;
T6 = array [Semaine] of integer;

var HeuresDeTravail : T6;

begin
...
HeuresDeTravail[Lundi] := 8;
...
end

les opérations possibles se limitent à la manipulation des composantes individuelles (affectation,


comparaison...). Seul le passage de paramètre permet de manipuler un tableau dans sa totalité. Pas de
constantes "tableau" possibles en Pascal; en Ada, c'est toutefois possible, les valeurs sont énumérées
entre parenthèses, par exemple ainsi : (Constante1, Constante2 . . . ) ou (Indicem . . Indicen =>
Constante1, . . . ).

http://cuisung.unige.ch/std/03.1.html (1 of 3) [07-06-2001 12:14:37]


Structuration des Données Informatiques - 3.1.

3.1.2. Les enregistrements


Les enregistrements sont constitués d'un regroupement de données hétérogènes (de types différents) de
type quelconque. Les données individuelles sont repérées par un sélecteur que l'on nomme champ de
l'enregistrement. L'identificateur de champ doit être un identificateur conforme aux règles lexicales du
Pascal.
Exemple
type Individu = record
Nom, Prenom: string;
Age: integer;
Sexe: (Feminin, Masculin);
end; { Individu }

var Lui: Individu;

begin
. . .
Lui.Age := 15;
. . .
end

Les opérations possibles sur les enregistrements dans leur globalité sont : l'affectation et la comparaison (
=, <> ). Les opérations possibles sur les différents champs d'un enregistrement sont celles associées au
type du champ.
Pas de constantes "enregistrement" possibles en Pascal; en Ada, c'est toutefois possible (agrégats) : les
valeurs sont énumérées entre parenthèses, dans l'ordre des champs ou en indiquant explicitement le nom
du champ avant chaque valeur, par exemple (Constante1, Constante2 . . . ) ou (Champi => Constantei, . . .
).

3.1.3. Les ensembles


Les ensembles sont des structures que l'on ne retrouve quasiment qu'en Pascal. Modula 2 en restreint
nettement l'utilisation et Ada ne l'offre pas de manière prédéfinie. Les ensembles se rappochent beaucoup
de la notion mathématique d'ensembles. Ils sont construits à partir de types de bases énumérés et
permettent d'indiquer, pour chacune des valeurs du type de base, si cette valeur est présente ou absente de
l'ensemble en question. Il n'y a pas de sélecteur possible.
Exemple

var S, Voyelles, Consonnes: set of char;


. . .
S := []; { ensemble vide }
Voyelles := ['a','e','i','o','u','y'];

http://cuisung.unige.ch/std/03.1.html (2 of 3) [07-06-2001 12:14:37]


Structuration des Données Informatiques - 3.1.

Consonnes := ['b'..'d','f'..'h','j'..'n',
'p'..'t','v'..'x' ,'z'];
S := [ 'b'..c ]; { noter le fait que c
est une variable }

Les opérations sur les ensembles ne sont possibles que globalement. On dispose de l'affectation, l'union
(+), l'intersection (*), la diffé rence (-), la comparaison ( =, <> ), le test d'appartenance d'une valeur du
type de base ( in ) et l'inclusion (<).
N.B. L'expression a S sera notée : not (a in S).

3.2. Cardinalité

Table des matières.

http://cuisung.unige.ch/std/03.1.html (3 of 3) [07-06-2001 12:14:37]


Structuration des Données Informatiques - 3.1, exercice 1

Tableaux
Question posée au contrôle continu du 28 février 2000
program question1;

var gHeap : array [1..7] of integer;

procedure affiche(inValeurCourant, inValeurFin : integer);


begin
if (inValeurCourant<=inValeurFin) then begin
writeln('a: ',gHeap[inValeurCourant]);
affiche(inValeurCourant*2,inValeurFin);
writeln('b: ',gHeap[inValeurCourant]);
affiche(inValeurCourant*2+1,inValeurFin);
writeln('c: ',gHeap[inValeurCourant]);
end (* then *)
else begin
writeln('d: ',inValeurCourant);
end; (* if *)
end; { affiche }
procedure remplir;
var i : integer;
begin
gHeap[1]:=4;
gHeap[2]:=2;
gHeap[3]:=5;
gHeap[4]:=1;
gHeap[5]:=3;
gHeap[6]:=6;
gHeap[7]:=7;
gHeap[7]:=8;
end; { remplir }

begin
remplir
writeln('----------premiere partie');
affiche(1,1);
writeln('----------deuxieme partie');
affiche(1,3);
writeln('----------troisieme partie');
affiche(1,5);
writeln('----------fin')
end.
Qu'est-ce que ce programme affiche à l'écran?

http://cuisung.unige.ch/std/ex/3/1a.html [07-06-2001 12:14:41]


Structuration des Données Informatiques - 3.2.

3.2. Cardinalité
Définition : La cardinalité est le nombre de valeurs distinctes appartenant à un type T. Un objet de ce
type T ne pourra bien entendu avoir qu'une seule de ces valeurs à un instant donné
Exemple
type Forme = (Carré, Rectangle, Cercle,
Ellipse);
Monnaie = (Franc, Livre, Dollar,
Lire, Yen, Mark);
Genre = (Feminin, Masculin);
card(Forme) = 4
card(Monnaie) = 6
card(Genre) = 2
type SmallInteger = 1..100;
type Vecteur=array[1..3] of SmallInteger;
card (SmallInteger) = 100
card (Vecteur) = 1003

card (boolean) = 2
card (integer) = 2*(MaxInt+1) { pour le langage Pascal }

card (real) = 2n-1 pour une représentation normalisée, où n est le nombre de bits utilisés pour représenter
les nombres réels. Le premier bit de la mantisse étant toujours à 1, sauf pour la valeur 0.0, certaines
architectures ne mémorisent pas ce premier bit et ont, par conséquent, une cardinalité de 2n pour les
nombres réels.
On peut résumer les principales caractéristiques des structures cartésiennes simples dans le tableau
suivant :
Structure : Tableau Enregistrement Ensemble
r : record
Déclaration a : array[I] of To S1 : T1; S2 : T2; Sn : Tn; set of To
end;
Sélecteur : a[i] (i I) r.s (S S1,...Sn ) aucun
Accès aux par le sélecteur par le sélecteur avec le nom Test d'appartenance
composantes: et le calcul de l'indice déclaré d'un élément avec l'opérateur de relation in
Type des Toutes ident. Peuvent être Toutes ident. et de
composantes: de type To différents type scalaire To

http://cuisung.unige.ch/std/03.2.html (1 of 2) [07-06-2001 12:14:48]


Structuration des Données Informatiques - 3.2.

Cardinalité : Card(To)Card(I 2Card(TO)

prod.cartésie

Ces structures simples peuvent être combinées pour former des structures plus complexes .

3.3. Structures cartésiennes complexes

Table des matières.

http://cuisung.unige.ch/std/03.2.html (2 of 2) [07-06-2001 12:14:48]


Structuration des Données Informatiques - 3.3.

3.3. Structures cartésiennes complexes


3.3.1. Tableaux multidimensionnés
Déclaration :
type Matrices = array[Ligne] of
array[Colonne] of To;
(ou : Matrices = array[Ligne,Colonne] of To)
Sélecteur :
var m : Matrices;
m[i][j] ou m[i, j]
Cardinalité :
card(m) =card(To)card(Ligne).card(Colonne)
Déclaration :
type Esp3D:array[Profondeur] of Matrices;
(ou : array [Profondeur, Ligne, Colonne] of To).
Sélecteur :
var e : Espace3D;
e[p] [i] [j] ou e[p] [i, j] ou e[p,i],[j] ou e[p, i, j]
Cardinalité :
card(e)=card(To)card(Profondeur).card(Ligne).card(Colonne)
Le nombre de dimensions d'un tableau n'est, dans la plupart des langages, pas limité !

3.3.2. Tableaux d'enregistrements


Déclaration :
type Point = record
X, Y: integer;
end; { Point }

Suite = array[1..Max] of Point;


var S1, S2 : Suite;
Sélecteur :
. L'abscisse du premier point de la suite S1: S1[1].X
b. Ecrire tous les points de la deuxième suite dont l'ordonnée est su périeure à 10

for i := 1 to Max do
if S2[i].Y > 10.0 then
writeln (S2[i].X, S2[i].Y);
Cardinalité :

http://cuisung.unige.ch/std/03.3.html (1 of 5) [07-06-2001 12:15:20]


Structuration des Données Informatiques - 3.3.

card(S1) = card(integer)2.Max

3.3.3. Enregistrement de tableaux


Déclaration :

const Max = . . . ;
AlfaLong = 15;
type Alfa = packed array[1..AlfaLong]
of char;
Service = (Actif, Reserviste,
Complementaire, Reforme);
{ Il n'est pas dans les intentions des
auteurs d'être sexiste }

Adulte = record
Nom: Alfa;
Prenoms: array [1..3] of Alfa;
case Sexe:(Feminin,Masculin) of
Feminin:(NomDeJeuneFille: Alfa);
Masculin:(Incorporation:Service);
end; { Adulte }
var L1, L2: array[1..Max] of Adulte;

Sélecteur :
le deuxième prénom de la première personne de la liste L1:
L1[1].Prenoms[2]
Cardinalité :
card(L1) = ( card(char)4.15. (card(char)15 + card(Service)) )Max
N.B. : la cardinalité d'un enregistrement avec variant est égale à :
card. des compos. avant variant . ( card.de chaque variante)

On peut de la même manière construire des tableaux d'ensembles et des enregistrements contenant des
ensembles.

3.3.4. Les chaînes de caractères


3.3.4.1. Chaînes de longueur fixe

Les chaînes de caractères telles qu'elles sont définies par N. Wirth dans son manuel du Pascal sont
disponibles dans la quasi-totalité des implantations du Pascal. Leur déclaration se fait de la manière
suivante :

http://cuisung.unige.ch/std/03.3.html (2 of 5) [07-06-2001 12:15:20]


Structuration des Données Informatiques - 3.3.

Variable:packed array[intervalle] of char;


L'intervalle spécifié dans la déclaration ci-dessus est généralement de la forme 1..Longueur, mais
cela n'est pas obligatoire; n'importe quel intervalle d'entiers est admissible.
Des chaînes constantes sont spécifiées de la manière suivante :
'suite de caractères'
Si l'on désire une apostrophe dans la suite de caractères, il faut en mettre deux qui se suivent, comme
dans :
'c''est un exemple'.
Les primitives de manipulation de chaînes de caractères sont les suivantes :
● affectation : VariableChaîne1:= VariableChaîne2;
VariableChaîne:= ChaîneConstante;
Les chaînes doivent être de même longueur. Certaines implantations admettent que la chaîne constante
soit plus courte que la variable à laquelle elle est affectée. La chaîne constante est alors complétée par
des blancs en fin de chaîne.
● lecture : elle n'est pas possible en tant qu'opération de base (sauf pour certaines variantes
d'implantation). Il faut lire dans un tableau de caractères non compacté puis utiliser la procédure
prédéfinie pack.
● écriture : write(VariableChaîne);
write(ChaîneConstante);
● comparaison : <, >, =, <>. La comparaison se fait sur tous les caractères, entre chaînes de même
longueur. Là aussi, si un des opérandes est une chaîne constante plus courte que l'autre opérande,
certaines implantations complètent la chaîne par des blancs.
● construction et décomposition : pour pouvoir manipuler individuellement les caractères d'une
chaîne, il faut utiliser les procédures prédéfinies pack et unpack. Ces procédures permettent de
convertir un simple tableau de caractères en une chaîne de caractères et vice-versa.

3.3.4.2. Chaînes de longueur variable

Cette variante est propre à certaines implantations telles que Pascal UCSD et Turbo Pascal. Elle permet
de disposer d'un jeu plus complet de primitives de manipulation et, surtout, de pouvoir faire varier la
longueur effective de la chaîne durant l'exécution. La déclaration se fait de la manière suivante :
string[NbCarMax];
ou string; { correspond à string[80] }
NbCarMax indiquant la longueur maximale que pourra avoir la chaîne.
Les chaînes constantes sont spécifiées de la même manière que des chaînes de longueur fixe.
Pour pouvoir déterminer quelle est la longueur effective d'une chaîne de caractères, un attribut de
longueur est associé à chaque variable de ce type. De plus, les caractères composant la chaîne sont
accessibles individuellement comme pour un tableau de caractères : si S est déclaré de type string, le

http://cuisung.unige.ch/std/03.3.html (3 of 5) [07-06-2001 12:15:20]


Structuration des Données Informatiques - 3.3.

premier caractère de la chaîne sera stocké en S[1]. Le nombre de caractères effectivement stockés est
généralement indiqué par S[0]. L'attribut de longueur doit toujours appartenir à l'intervalle
[CHR(0)..CHR(NbCarMax)].
Les primitives de manipulation sont les suivantes :
● affectation : S:=S2; ou S:='suite de caractères'; si la longueur de la chaîne à
droite de l'affectation ne dépasse pas la longueur maximale de S. On peut aussi avoir C:=S[i];
ou S[i]:=C; si C est de type CHAR et i une expression donnant un résultat entier positif
inférieur ou égal à la longueur réelle de S.
● lecture : read(S); on ne peut lire qu'une chaîne par ligne. Si le nombre de caractères disponibles
sur la ligne courante est plus grand que la taille maximale de la chaîne, les caractères en trop sont
perdus. S'il y a moins de caractères que la chaîne ne peut en contenir, l'attribut de longueur de la
chaîne est ajusté en conséquence.
● écriture : write(S); ne sont écrits que les caractères correspondant à la longueur réelle de la
chaîne.
● comparaison : <, >, =, <>. La comparaison tient compte de la longueur réelle des chaînes. Si les
longueurs sont différentes, la plus courte est complétée par des blancs. La relation d'ordre
correspond à l'ordre alphabétique.
● un ensemble de procédures et de fonctions prédéfinies:

function length(S: string): integer;


retourne l'ordinal de l'attribut de longueur de la chaîne.
procedure insert(Insertion: string;
VAR Destination: string;
Position: integer);
Insertion doit être un string non-vide; Position doit appartenir à l'intervalle
[1..length(Destination)+1]. Après l'insertion, le premier caractère de Insertion se trouvera en
Destination[Position].
procedure delete(VAR S: string;
Position, Longueur: integer);
Position et (Position+Longueur-1) doivent appartenir à l'intervalle [1..length(S)].
function copy(S: string;
Position, Longueur: integer): string;
fournit, en sortie, la partie de S commençant en Position et ayant Longueur caractères. Les
restrictions concernant les valeurs de Position et Longueur sont les mêmes que pour
DELETE.
function concat(S1,S2...:string): string;
peut avoir un nombre variable de paramètres. Fournit, en sortie, la concaténation des
différentes chaînes passées en paramètre.
function pos(SousChaîne,Chaîne: string): integer;
recherche la sous-chaîne à l'intérieur de la chaîne et indique en quelle position elle
commence (0 = pas trouvé).

http://cuisung.unige.ch/std/03.3.html (4 of 5) [07-06-2001 12:15:20]


Structuration des Données Informatiques - 3.3.

procedure str(Valeur: integer; VAR Chaîne: string);


convertit un entier en chaîne de caractères. La longueur de la chaîne correspondra au
minimum nécessaire pour représenter la valeur en question.
Ce modèle de chaînes de caractères de longueur variable comporte certaines inconsistances. Il est en effet
à noter que les fonctions COPY et CONCAT ne répondent pas à la syntaxe du Pascal, puisqu'une
fonction de l'utilisateur ne peut pas fournir une valeur de retour de type string, ni avoir un nombre
variable de paramètres. Tout programme devant être portable devra donc éviter d'utiliser ces deux
primitives, puisqu'elles ne peuvent pas être facilement remplacées par des fonctions de l'utilisateur.
Ce modèle comporte aussi certains pièges. Chaque fois que l'on accède à un "string", l'attribut de
longueur est contrôlé. Le principal problème que l'on peut rencontrer en utilisant des strings peut seposer
si l'on passe un string par référence à une procédure. En effet, lors de la déclaration des paramètres
formels, on est obligé de spécifier la longueur maximale du string; or cette longueur peut ne pas
coïncider avec la longueur maximale du paramètre réel. D'où un contrôle erroné des indices qui déjoue
les mécanismes de sécurité généralement associés au langage Pascal.
Exemple

type ChaîneCourte = string;


ChaîneLongue = string[255];

var Chaîne: ChaîneCourte;

procedure Remplis(VAR S: ChaîneLongue);


begin
...
S := ...;{ expression fournissant une }
{ chaîne de plus de 80 car. }
...
end; { Remplis }

begin { programme principal }


Remplis(Chaîne);{va "écraser" les}
end. {positions mémoires}
{qui sont après Chaîne}

3.4. Structures paquetées

Table des matières.

http://cuisung.unige.ch/std/03.3.html (5 of 5) [07-06-2001 12:15:20]


Structuration des Données Informatiques - 3.4.

3.4. Structures paquetées


Les abstractions que l'on manipule dans un programme par l'intermédiaire des variables permettent de
concevoir, comprendre, vérifier les programmes sur la base des lois qui gouvernent les abstractions.
Exemple : un programme manipulant les abstractions de nature économique peut être entièrement conçu,
compris et vérifié en considérant uniquement des lois économiques).
Cependant, si la connaissance exacte des principes de fonctionnement d'un ordinateur ou d'un langage
n'est pas indispensable (sauf pour les professionnels que sont les informaticiens), il est bon d'avoir
ponctuellement une idée plus précise de comment un programme écrit dans langage s'exécute en
machine. Dans le domaine qui nous intéresse, ici les structures de données, il est important de savoir
comment sont représentées des structures fondamentales telles que :
❍ les tableaux.

❍ les enregistrements.

Le problème de la représentation des structures de données revient à faire correspondre, à l'abstraction


que représente la structure, une organisation particulière de la mémoire centrale de la machine. En
première approximation, la mémoire centrale d'une machine est un tableau de cellules élémentaires
appelées mots. Les indices de ces mots sont appelés adresses.
var Memoire : array [Adresses] of Mots;
Les cardinalités d'adresses et de mots varient fortement d'une machine à l'autre. Par exemple :
quelques milliers < card(Adresse) < plusieurs millions

3.4.1. Cas des tableaux


La conversion indice Æ adresse pour la j-ème composante d'un tableau s'effectue de la manière suivante :
i = i0 + (j-1) * S

avec : i0 = adresse de la première composante


S = nombre de mots qu'occupe une composante (ce nombre peut être fractionnaire)
Le cas idéal serait : S = 1, mais ce n'est pas souvent le cas; alors S est arrondi à l'entier supérieur le plus
proche. Donc, chaque composante occupera S' mots, avec S'-S laissé inoccupé.
Cette méthode, qui consiste à arrondir le nombre exact de mots nécessaires à l'entier supérieur, est
appelée remplissage (padding).
Exemple

http://cuisung.unige.ch/std/03.4.html (1 of 4) [07-06-2001 12:16:01]


Structuration des Données Informatiques - 3.4.

Fig. .3.1 Remplissage


Le facteur d'utilisation est donné par :
U = S / S'
l'objectif étant d'avoir un facteur n aussi proche que possible de 1. Notons que :
U > 0,5 si S > 0,5 mot

Cependant, si S 0,5, le facteur d'utilisation peut être largement amélioré si l'on met plusieurs
composantes du tableau dans un seul mot. Cette méthode est appelée compactage : si n composantes sont
compactées
dans un seul mot, le facteur d'utilisation est :
U = (n * S) / (n * S)' avec (n*S)' = 1 géné
Exemple

Fig. 3.2. Compactage


L'accès à la j-ème composante d'un tableau compacté suppose :
. le calcul de l'adresse i du mot contenant cette composante
i = (j-1) div n
b. le calcul de la position k qu'occupe la composante à l'intérieur du mot.
k = ((j-1) mod n) +1 = j - (i * n)
Ce compactage, l'utilisateur doit pouvoir l'exiger si nécessaire sans avoir à se préoccuper de son
mécanisme. C'est pourquoi le langage Pascal introduit deux variantes aux structures tableaux et
enregistrements qui sont :
packed array [...]
packed record ...
Exemple (chaînes de longueur fixe)
type alfa = packed array [1..n] of char;
Si la structure occupe moins de place en mémoire, ses composantes deviennent, par contre, plus difficiles

http://cuisung.unige.ch/std/03.4.html (2 of 4) [07-06-2001 12:16:01]


Structuration des Données Informatiques - 3.4.

d'accès. Pour un tableau compacté, en Pascal, il faudra passer par les procédure pack et unpack pour
manipuler les composantes individuelles; par contre, on pourra utiliser l'affectation sur le tableau
complet.
C'est pour des raisons de compromis entre efficacité d'implantation et occupation en mémoire que les
structures compactées ont la contrainte de ne pas avoir de composante de taille inférieure à un mot
machine qui soit à cheval sur deux mots machine. C'est pourquoi il est généralement exigé que dans
l'exemple vu plus haut, n soit un nombre entier.

3.4.2. Cas des enregistrements


Les différentes composantes d'un enregistrement sont stockées les unes à la suite des autres. Dans un
enregistrement non paqueté, chaque composante doit commencer au début d'un nouveau mot machine.
Pour les mêmes raisons de compromis entre efficacité du code et occupation de la mémoire, dans un
enregistrement paqueté, les composantes de taille supérieure à un mot doivent commencer au début d'un
mot et seules les composantes de taille inférieure à un mot pour lesquelles il y aurait encore de la place
dans le dernier mot utilisé se ront placées au milieu d'un mot (pas de composante à cheval sur deux
mots). On emploie donc simultanément les techniques de remplissage et de compactage.
Pour un enregistrement, il n'y a pas de changement dans l'utilisation (pas de décompactage nécessaire),
seul le temps d'accès sera plus long, car le compilateur devra générer un code plus complexe pour
accéder à l'information.
Exemple

Fig. 3.3. Enregistrement paqueté.

http://cuisung.unige.ch/std/03.4.html (3 of 4) [07-06-2001 12:16:01]


Structuration des Données Informatiques - 3.4.

4. Les types abstraits.


Table des matières.

http://cuisung.unige.ch/std/03.4.html (4 of 4) [07-06-2001 12:16:01]


Structuration des Données Informatiques - 4.0.

4. Les types abstraits


Un type abstrait est une structure qui n'est pas définie en terme d'implantation en mémoire ou par une
explicitation de ses composantes, mais plutôt en termes d'opérations et de propriétés sémantiques. La
spécification d'un type abstrait est indépendante de toute représentation de la (ou des) structure(s) en
machine et de l'implantation des opérations associées. Ceci a pour intérêt d'empêcher l'utilisateur d'une
telle structure de modifier directement la structure avec le risque de la rendre éventuellement
incohérente. Une autre conséquence intéressante est l'amélioration de la portabilité qui découle de
l'indépendance entre les manipulations de la structure et la méthode d'implantation choisie.
Il existe différentes approches et langages pour la spécification d'un type abstrait. Quelle que soit
l'approche, la spécification d'un type abstrait comporte deux parties : la structure logique et les
opérations.
La spécification de la structure logique décrit une instance du type abstrait, les relations qui peuvent
exister entre les éventuelles composantes de cette instance et les assertions invariantes qui décrivent les
restrictions à apporter à ces relations.
La spécification des opérations décrit la syntaxe et la sémantique des primitives de manipulation du type
abstrait. Si un langage formel est utilisé pour cette spécification, une description de ce langage doit
accompagner la description du type abstrait.
La plupart des structures que nous avons déjà vues, ainsi que la plupart des structures que nous verrons
plus loin, peuvent être décrites en termes de types abstraits. Bien qu'il existe des formalismes algébriques
permettant de décrire des types abstraits, dans les exemples qui suivent, nous nous contenterons de
décrire les types abstraits en langage naturel sans faire de séparation explicite entre les différentes parties
de la spécification. Le but des spécifications qui seront données plus loin est principalement d'améliorer
la compréhension des différentes structures décrites plutôt que de prouver la complétude ou la
consistance de leur implantation.

4.1. Exemple : les tableaux

Table des matières.

http://cuisung.unige.ch/std/04.0.html [07-06-2001 12:16:32]


Structuration des Données Informatiques - 4.1.

4.1. Exemple : les tableaux


La spécification de la structure de tableau peut se faire de la mani ère suivante :
● leur déclaration spécifie un intervalle d'indices et un type de base. Ceci détermine indirectement la
taille du tableau. Une variante pourrait être que la déclaration spécifie la taille du tableau et le type
de base, en supposant que l'indice est de type entier et que la borne inférieure est égale à 1 (cas du
langage FORTRAN, si l'on suppose que l'initiale de l'identificateur de tableau sert à la
spécification du type de base, en l'absence de déclaration explicite).
● le rôle du tableau est de conserver pour chaque valeur d'indice une valeur de type de base associée.

● une seule valeur du type de base peut être associée à chaque valeur d'indice.

● les primitives de manipulations sont

1. associer une valeur du type de base à une certaine valeur d'indice,


2. fournir la valeur de type de base associée à une certaine valeur d'indice.
3. en fonction de la déclaration, diagnostiquer la validité de l'indice fourni.
● On peut envisager de pouvoir associer, lors de la déclaration d'un tableau, une valeur initiale à
quelques (ou toutes les) valeurs d'indices. C'est le cas du langage Ada, par exemple.
On peut noter qu'il n'a pas été spécifié de primitive permettant de tester si une valeur a été associée à un
certain indice. On peut aussi noter que l'utilisation d'un intervalle d'indices induit un séquencement
implicite des valeurs de type de base associées à ces indices. Ainsi, l'on pourra dire que deux valeurs se
suivent dans le tableau si les valeurs des indices auxquels elles sont associées se suivent dans la séquence
des indices. Cela ne signifie pas pour autant que ces valeurs de type de base seront effectivement
stockées dans des zones contiguës de la mémoire (même si c'est presque toujours le cas en réalité, pour
des raisons d'efficacité).

4.2. Exemple : les ensembles

Table des matières.

http://cuisung.unige.ch/std/04.1.html [07-06-2001 12:16:35]


Structuration des Données Informatiques - 4.2.

4.2. Exemple : les ensembles


● leur déclaration spécifie un type de base qui doit être un type énuméré.
● le rôle d'un ensemble est d'indiquer la présence ou l'absence de chacune des valeurs possibles du
type de base dans cet ensemble.
● les primitives de manipulations sont :

1. regroupement d'une ou plusieurs valeurs de type de base sous forme d'ensemble,


2. est d'appartenance d'une valeur de type de base à un ensemble,
3. l'affectation d'un ensemble à une variable de type ensemble compatible,
4. la comparaison de deux ensembles,
5. le test d'inclusion d'un ensemble dans un autre,
6. union de deux ensembles,
7. intersection de deux ensembles,
8. différence de deux ensembles.
Ces quatre dernières primitives pourraient être construites à partir des trois premières. De plus, les trois
dernières sont redondantes entre elles car chacune peut être facilement définie à l'aide des deux autres.
On pourrait donc omettre l'une d'entre elles, voire les quatre, sans réduire la fonctionalité du type abstrait
"ensemble".
On peut noter qu'il n'est pas nécessaire d'inclure le test de non-appartenance à un ensemble, car il suffit
pour cela de prendre la négation du test d'appartenance.

4.3. Exemple : chaines de caractères de longueur


variable

Table des matières.

http://cuisung.unige.ch/std/04.2.html [07-06-2001 12:16:37]


Structuration des Données Informatiques - 4.3.

4.3. Exemple : chaînes de caractères de longueur


variable.
● leur déclaration spécifie éventuellement une longueur maximale. Ceci est toutefois une contrainte
d'implantation qui n'est pas indispensable.
● leur rôle est de stocker une séquence de caractères de longueur arbitraire.
● les primitives de manipulations sont :
1. constituer une chaîne de caractères à partir d'une séquence de zéro, un ou plusieurs
caractères.
2. l'affectation d'une chaîne à une variable de type compatible,
3. fournir la longueur d'une chaîne,
4. fournir le caractère se trouvant à une certaine position,
5. redéfinir le caractère se trouvant à une certaine position,
6. insérer une chaîne dans une autre à partir d'une certaine position
7. détruire une portion d'une chaîne, à une certaine position et sur une certaine longueur,
8. concaténer deux chaînes,
9. extraire une sous-chaîne d'une chaîne,
10. trouver la position de la première occurrence d'une sous-chaîne
On pourrait constituer un modèle nettement simplifié en reprenant les primitives 1 à 5 et en y ajoutant la
possibilité d'insérer un caractère en fin de chaîne. Toutes les autres primitives pourraient alors être
construites à partir de cette version simplifiée. L'avantage de construire un modèle plus complexe est que
l'implantation que l'on en fera pourra être plus efficace.
Le langage Pascal ne permet pas la réalisation complète de tous les aspects des types abstraits,
principalement par le fait qu'il ne permet pas de cacher les détails d'implantation. Des langages plus
récents, tel que Ada, permettent de construire ces types abstraits d'une manière beaucoup plus sûre, par le
biais de la modularisation et de l'utilisation de types privés. Ce langage fait même la distinction entre
type privé, sur lequel l'affectation et la comparaison sont encore possibles, et type privé limité, sur lequel
aucune opération n'est possible (il ne peut alors plus servir que de paramètre de procédure).

5. Les structures dynamiques


Table des matières.

http://cuisung.unige.ch/std/04.3.html [07-06-2001 12:16:41]


Structuration des Données Informatiques - 5.0.

5.Les structures dynamiques


Les structures statiques sont toutes caractérisées par une cardinalité finie. Cependant des structures plus
élaborées sont caractérisées par une cardinalité infinie. Ce sont les structures de données dynamiques
telles que : les chaînes, les listes, les arbres, les graphes, etc.
L'objectif principal des structures de données est de permettre à l'utilisateur de développer une solution
informatique à un problème qui soit conceptuellement aussi proche du problème que possible. Prenons
par exemple, un système de réservation pour une compagnie aérienne. On peut distinguer trois niveaux
de traitement :

● horaires
● vols
domaine
● dates
du probléme
● destinations
● réservations
● fichiers
● tableaux
implantation
● enregistrements
du systéme
● chaînes de caractéres
● structures de données plus complexe
● octets
niveau
● suites de mots mémoires
mémoire
● pages

Nous nous intéresserons plus particulièrement au niveau intermédiaire. C'est celui où l'on met en oeuvre
les concepts généraux des structures de données et où l'on développe des algorithmes permettant de gérer
ces structures efficacement, indépendamment du problème pour lequel elles seraient utilisées.
Définition : une structure dynamique est une structure dont la taille (le nombre de composantes) peut
varier en cours d'exécution.
On distingue généralement deux sortes de structures dynamiques : les fichiers et les structures récursives.
Les structures dynamiques peuvent être subdivisées en deux catégories : les structures linéaires et les
structures non-linéaires
fichier structures non-récursives
structures dynamiques linéaires
chaînes
listes
structures récursives
structures dynamiques non-linéaires arbres

http://cuisung.unige.ch/std/05.0.html (1 of 2) [07-06-2001 12:16:46]


Structuration des Données Informatiques - 5.0.

graphes

Une structure récursive est une structure (en général basée sur un enregistrement) dont la définition
comporte une référence à elle même. S'il n'y a qu'une seule référence, cette structure est linéaire; s'il y en
a plusieurs, cette structure est non-linéaire. Pour beaucoup de langages procéduraux, ces références se
font à l'aide du type pointeur.

5.1. Les pointeurs

Table des matières.

http://cuisung.unige.ch/std/05.0.html (2 of 2) [07-06-2001 12:16:46]


Structuration des Données Informatiques - 5.1

5.1. Les pointeurs


Une variable de type pointeur est une variable dont le contenu (adresse) peut indiquer l'emplacement en
mémoire d'une autre variable, créée dynamiquement lors de l'exécution et ayant un type de base précis
(objet pointé). Ceci constitue la principale différence entre la notion de pointeur dans un langage de haut
niveau et la notion d'adresse en assembleur.
On peut décrire les pointeurs en terme de type abstrait de la manière suivante :
● le rôle d'un pointeur est de permettre l'accès à une structure qui serait créée lors de l'exécution.

● la déclaration doit spécifier le type de base de l'objet qui pourra être accessible à l'aide du pointeur.

● primitives de manipulation :

1. associer à un pointeur un objet (de type de base) qui sera accessible à l'aide de ce pointeur
(ceci ne définit pas pour autant la valeur qui sera associée à l'objet),
2. détruire l'objet accessible à l'aide du pointeur,
3. indiquer l'absence d'objet à accéder,
4. permettre à un pointeur d'accéder au même objet qu'un autre pointeur,
5. associer une valeur de type de base à l'objet accessible par un pointeur,
6. fournir la valeur de type de base associée à l'objet accessible par un pointeur (diagnostiquer
le cas où il n'y a pas d'objet à accéder),
7. comparer deux pointeurs de même type de base pour savoir s'ils permettent d'accéder au
même objet.
Ce modèle ne spécifie pas ce qu'il advient d'un éventuel objet précédemment accessible à l'aide
d'un pointeur lorsque l'on demande la création d'un nouvel objet accessible à l'aide de ce même
pointeur sans avoir détruit, au préalable, l'objet précédemment accessible. Il ne spécifie pas non
plus ce qui se passe si l'on essaye d'accéder à l'objet sensé être accessible à l'aide d'un pointeur si
l'on n'a jamais associé d'objet à ce pointeur, ni indiqué l'absence d'objet accessible (pas
d'initialisation du pointeur).
Dans ce qui suit, nous allons voir la manière dont les pointeurs sont implantés en Pascal. La déclaration a
la forme :
VAR P: ^TypeDeBase;
Si l'on désire qu'un pointeur n'indique l'emplacement d'aucun objet (primitive 3), on peut lui affecter la
constante prédéfinie NIL (quel que soit le type de base).
Lorsqu'une variable de type pointeur est déclarée dans un programme ou une procédure, le contenu
(adresse) est indéterminé au moment de l'activation du programme ou de la procédure (pas forcément
NIL). Pour définir le contenu d'une variable de type pointeur, on a trois possibilités:
● lui affecter la valeur NIL ( P := NIL; ) (primitive 3)

● lui affecter le contenu d'une autre variable du même type ( P := Q; ) (primitive 4)

● passer la variable en paramètre aux procédures prédéfinies NEW ou DISPOSE (p.ex. NEW(P); )
(primitives 1 et 2)

http://cuisung.unige.ch/std/05.1.html (1 of 3) [07-06-2001 12:16:53]


Structuration des Données Informatiques - 5.1

La procédure NEW (primitive 1) a pour tâche :


● de trouver un emplacement libre en mémoire suffisamment grand pour contenir un objet de type
TypeDeBase,
● de réserver la place nécessaire et

● de mettre l'adresse de cet emplacement dans la variable de type pointeur qui lui est fournie en
paramètre (P).
Après l'appel, on aura donc deux objets accessibles :
● P (le pointeur), qui contient une adresse (généralement 1 mot machine), et

● P^ (l'objet pointé ne possédant pas d'identificateur propre), qui contient une information de type
TypeDeBase.
L'opérateur postfixé "^" permet d'implanter les primitives 5 et 6; la 5 en affectant une valeur à P^
(P^ := valeur_de_type_TypeDeBase) et la 6 en utilisant P^ dans une expression (par exemple dans une
expression logique : if P^ = valeur_de_type_TypeDeBase then . . . ).
La procédure DISPOSE (primitive 2) a pour tâche :
● de libérer la place occupée par l'objet pointé (P^) et

● de mettre la variable pointeur (P), qui lui est passée en paramètre, à NIL pour indiquer qu'elle ne
pointe plus vers rien.
Si l'on utilise l'affectation pour redéfinir le contenu d'une variable de type pointeur (primitive 4), il faut
bien faire attention au cas où la variable pointait préalablement sur un objet en mémoire. Cet objet
pourrait bien ne plus être accessible par le programme tout en continuant à occuper de la place en
mémoire. On utilisera préalablement la procédure DISPOSE (primitive 2), qui libérera la place et mettra
le pointeur à NIL. Ceci peut être illustré de la manière suivante :

Fig. 7.1. Effet de l'affectation sur un pointeur


Dans le même ordre d'idées, si une procédure possède une variable locale (de type pointeur) qui pointe
vers une structure dynamique, il faut s'assurer, avant de sortir de la procédure, soit que cette structure est
accessible autrement que par la variable locale, soit que l'on libère la place occupée par l'objet pointé. La
récupération automatique de la place mémoire occupée par les variables locales de la procédure
n'implique en effet pas la destruction des objets pointés.
L'affectation permet à deux variables de type pointeur de pointer vers le même objet en mémoire
(primitive 4). Si l'on désire qu'un de ces deux pointeurs ne pointe plus vers rien, on utilisera l'affectation
de la valeur NIL (primitive 3) et non pas la procédure DISPOSE (primitive 2), car il faut que l'objet
pointé continue d'exister puisqu'une autre variable pointe aussi vers lui.

http://cuisung.unige.ch/std/05.1.html (2 of 3) [07-06-2001 12:16:53]


Structuration des Données Informatiques - 5.1

Lorsqu'un pointeur est passé en paramètre à une procédure, le passage par valeur empêche toute
modification du pointeur mais pas de l'objet pointé. L'objet pointé peut donc être malencontreusement
modifié, même si les apparences ne le montrent pas explicitement.
Il est à noter que dans des langages de programmation tels que Pascal, où les structures dynamiques
doivent être gérées explicitement à l'aide de pointeurs, il est difficile de manipuler ces structures
dynamiques de manière globale, par exemple pour copier une structure dans sa totalité ou concaténer
deux structures en une.

5.2. Ramassage des miettes, réutilisation de la


mémoire

Table des matières.

http://cuisung.unige.ch/std/05.1.html (3 of 3) [07-06-2001 12:16:53]


Structuration des Données Informatiques - 5.2

5.2. Ramassage des miettes, réutilisation de la


mémoire
Lorsqu'un traitement implique, non seulement la création dynamique d'objets à l'aide, par exemple, de la
procédure "new", mais aussi la destruction, en cours d'exécution, de certains de ces objets, l'occupation
mémoire risque de ne plus être contiguë, mais a de forts risques de devenir fragmentée :

occupation mémoire avant la suppression de


Obj3
Obj1 Obj2 Obj3 Obj4 libre

occupation mémoire après la suppression de


Obj3
Obj1 Obj2 libre Obj4 libre

La réutilisation de l'espace mémoire libéré peut se faire de différentes manières :


● déplacement des objets pour ne plus avoir qu'une seule zone libre égale à la somme des fragments
libres.
● chaînage des "trous" par la procédure de libération (dispose) et réutilisation par la procédure
d'allocation (new).

5.2.1. Ramassage des miettes


La première solution, appelée "ramassage des miettes" (Garbage Collection), ne peut se faire que si le
système gère l'ensemble des objets alloués dynamiquement à l'aide d'informations supplémentaires
adjointes à chaque objet, permettant ainsi de tous les parcourir pour voir à quels autres objets ils font
référence.
Chaque nouvelle allocation dynamique est effectuée en utilisant une place mémoire jusque là inoccupée.
Ce n'est que lorsque cette place fera défaut qu'une réorganisation globale de l'espace mémoire sera
entreprise pour regrouper en une seule zone contiguë l'ensemble des places libérées en cours d'exécution.
Cette procédure est assez difficile à implanter et, qui plus est, assez longue à exécuter.
On trouve souvent l'emploi de cette solution dans des environnements associés à des langages de
programmation où les listes sont des structures privilégiées (Lisp par exemple). L'intérêt de cette solution
est de fournir la certitude qu'un objet sera créé s'il existe assez de place en mémoire pour cela. Un de ses
inconvénients est que le processus de récupération peut intervenir à n'importe quel moment et provoquer
un ralentissement provisoire mais notable des temps de réponses du système. Ceci peut être très gênant si
l'on a une application temps réel, qui nécessite des temps de réponse prévisibles.

http://cuisung.unige.ch/std/05.2.html (1 of 3) [07-06-2001 12:16:58]


Structuration des Données Informatiques - 5.2

5.2.2. Chaînage des trous


La deuxième solution est plus facile à mettre en oeuvre mais ne résout pas tous les problèmes pour
autant. Elle consiste à chaîner les trous entre eux avec, pour chaque trou, l'indication de sa taille et de
l'emplacement du trou suivant.
Si tous les objets créés dynamiquement ont la même taille, on peut chaîner les zones libres entre elles
dans n'importe quel ordre. La procédure d'allocation dynamique n'aura qu'à utiliser le premier de cette
chaîne pour allouer de la place à un nouvel objet.
Si les objets créés dynamiquement ont des tailles différentes, on risque d'arriver à une situation où la
totalité de l'espace libre permettrait de créer un nouvel objet, mais où aucun trou n'est assez grand pour le
contenir. Pour minimiser cet effet, il est donc important de pouvoir regrouper, en un seul bloc, des trous
contigus. Pour ce faire, il est nécessaire de chaîner les trous dans l'ordre d'apparition en mémoire. Chaque
fois qu'un objet dynamique est détruit, la "chaîne libre" est parcourue pour voir où ranger ce nouveau
trou. S'il se trouve juste à côté d'un autre trou, ou entre deux trous, les deux ou trois trous sont alors
regroupés pour ne plus en former qu'un.
Deux principales possibilités d'utilisation des trous peuvent être envisagées. La méthode dite de la
première convenance (first fit) et celle de la meilleure convenance (best fit). La première consistant à
parcourir la chaîne libre depuis son début et à utiliser le premier trou suffisamment grand pour contenir le
nouvel objet; la deuxième méthode consiste, elle aussi, à parcourir la chaîne libre, mais cette fois jusqu'à
la fin de la chaîne pour déterminer quel est le plus petit trou suffisamment grand pour contenir le nouvel
objet, à moins que l'on ne rencontre, en cours de route, un trou ayant la taille exacte de ce nouvel objet.
La méthode du "best fit" semble de prime abord plus intéressante. Elle a toutefois l'inconvénient
d'impliquer une plus longue recherche et de générer des trous de plus en plus petits qui seront
difficilement réutilisables. C'est donc la méthode de la première convenance (first fit) qui est
généralement choisie.
Les avantages principaux du chaînage de trous sont la simplicité d'implantation et la rapidité d'exécution.
L'inconvénient principal est que l'on ne peut pas garantir la création d'un nouvel objet, même s'il y a
assez de place en mémoire. La fragmentation de l'espace libre peut en effet être telle qu'aucun morceau
n'est assez grand pour contenir l'objet à créer.
Certains systèmes combinent les deux solutions (ramassage des miettes et chaînage des trous) pour avoir
les avantages de chacune. Le chaînage des trous est utilisé jusqu'à ce que l'on arrive à une situation où
aucun trou n'est assez grand pour le nouvel objet à créer. Ce n'est qu'alors que le processus de ramassage
des miettes est amorcé.

http://cuisung.unige.ch/std/05.2.html (2 of 3) [07-06-2001 12:16:58]


Structuration des Données Informatiques - 5.2

6. Structures dynamiques linéaires non


récursives : les fichiers
Table des matières.

http://cuisung.unige.ch/std/05.2.html (3 of 3) [07-06-2001 12:16:58]


Structuration des Données Informatiques - 6.0

6. Structures dynamiques linéaires


non-récursives :
les fichiers
Un fichier (tel que défini en Pascal) est une structure homogène dont les différents éléments résident
séquentiellement sur un support externe, principalement disques ou bandes magnétiques. Dans plusieurs
implantations du Pascal il est aussi possible de déclarer des fichiers en accès direct, mais généralement
leur taille doit être fixée à la création du fichier et ne peut pas évoluer en cours d'exécution; Ces fichiers à
accès direct ne sont donc pas des structures dynamiques.
On peut considérer les fichiers séquentiels comme un cas particulier de chaînes avec les restrictions
suivantes :
● insertion à la fin uniquement

● suppression de tous les éléments

● le chaînage des éléments est implicite

● on ne peut accéder à un élément que dans la mesure où l'on vient d'accéder à son prédécesseur.
Seul le premier élément fait exception.
Déclaration : var NomInterne: file of TypeDeBase;
Du fait que le séquencement des éléments du fichier est implicite, une copie de l'élément courant est
associée au fichier. Cela peut être interprété comme une fenêtre que l'on promène d'un élément à l'autre
et qui permet de voir le contenu de l'élément courant. En Pascal, cette fenêtre contenant une copie de
l'élément courant est accessible à l'aide de la notation de pointeur (NomInterne^), comme si la variable
fichier (NomInterne) était un pointeur vers cet élément. Ceci n'est toutefois qu'un artifice de syntaxe, car
on ne peut pas manipuler une variable de type fichier comme on manipulerait une variable de type
pointeur

6.1. Primitives de manipulation

Table des matières.

http://cuisung.unige.ch/std/06.0.html [07-06-2001 12:17:00]


Structuration des Données Informatiques - 6.1

6.1. Primitives de manipulation


PROCEDURE RESET(NomInterne [,NomExterne]);
permet d'accéder à un fichier en lecture et définit le contenu de NomInterne^ (fenêtre)
correspondant au premier élément du fichier.
PROCEDURE REWRITE(NomInterne [,NomExterne]);
permet d'accéder à un fichier en écriture.
PROCEDURE GET(NomInterne);
avance d'un élément et définit le contenu de NomInterne^.
PROCEDURE PUT(NomInterne);
copie le contenu de NomInterne^ sur le disque à la fin du fichier.
FUNCTION EOF(NomInterne): BOOLEAN;
prédicat de fin de fichier.
FUNCTION EOLN(NomInterne): BOOLEAN;
prédicat de fin de ligne.
FUNCTION IORESULT: INTEGER;
indique si la dernière opération d'entrée/sortie s'est bien terminé. Le résultat de la fonction vaudra
0 si tout s'est bien passé, sinon la valeur retournée donnera une indication sur le type de problème
rencontré.
PROCEDURE CLOSE(NomInterne);
termine l'accès au fichier.

6.2. Utilisation

Table des matières.

http://cuisung.unige.ch/std/06.1.html [07-06-2001 12:17:02]


Structuration des Données Informatiques - 6.2

6.2. Utilisation
● TypeDeBase peut être n'importe quel identificateur de type simple ou complexe, mais ne doit pas
être ou comporter de composante de type fichier.
● La déclaration PROGRAM devrait comporter la déclaration des fichiers (noms internes) utilisés.
Ceci n'est pas toujours nécessaire suivant les implantations.
● Une variable tampon (fenêtre) est associée au fichier et est référencée par NomInterne^. On a donc
deux variables différentes : la variable NomInterne de type fichier et la variable NomInterne^ de
type TypeDeBase. Les opérations GET et PUT effectuées sur la variable NomInterne définissent
ou utilisent respectivement le contenu de NomInterne^.
● On ne peut accéder à un fichier que par le début ou à la suite de la dernière opération (séquentiel
monodirectionnel).
● Si l'on ne spécifie pas de NomExterne à l'ouverture, le fichier sera temporaire. La syntaxe de ce
nom externe du fichier correspond à celle du système d'exploitation utilisé.
Exemple

{transfert d'un tableau d'enregistrements


dans un fichier }

const MaxTab = 100;


type Enregistrement = record . . .
end;
var I: integer;
F: file of Enregistrement;
Tab: array[1..MaxTab]
of Enregistrement;
begin
rewrite(F,'NomDuFichier');
... { suite d'instructions définissant}
... { le contenu de Tab }
for I := 1 to MaxTab do begin
F^ := Tab[I];
put(F);
end; { for }
close(F, Lock);
... {suite du programme }
end.

{transfert d'un fichier dans un tableau}


...
reset(F,'NomDuFichier');
{EOF vrai si le fichier} est vide }
I := 0;

http://cuisung.unige.ch/std/06.2.html (1 of 2) [07-06-2001 12:17:05]


Structuration des Données Informatiques - 6.2

while not eof(F) do begin


I := succ(I);
Tab[I] := F^;
get(F); {Lit la prochaine valeur, }
{EOF vrai s'il n'y en a plus}
end; { while }
close(F);
...

6.3. Fichiers TEXT

Table des matières.

http://cuisung.unige.ch/std/06.2.html (2 of 2) [07-06-2001 12:17:05]


Structuration des Données Informatiques - 6.3

6.3. Fichiers TEXT


En Pascal, le type TEXT est prédéfini et correspond à FILE OF CHAR. La manipulation de ce type de
fichiers peut se faire, non seulement à l'aide des primitives GET et PUT, mais aussi avec les primitives
READ, READLN, WRITE, WRITELN. Dans le deuxième cas, il suffit de mentionner, comme premier
paramètre, la variable de type TEXT.
Exemple
var C: char; I: integer; R: real;
F: text;
...
reset(F,'NomExterne');
read(F,C);
readln(F,I,R);
...
Les primitives GET et PUT ne permettent de manipuler le fichier TEXT que caractère par caractère. Les
primitives READ, READLN, WRITE et WRITELN permettent, comme pour les fi chiers prédéfinis
INPUT et OUTPUT, de lire ou d'écrire des variables de différents types : caractères, entiers, réels,
chaînes de caractères et intervalles d'entiers ou de caractères.
Il peut y avoir erreur à l'exécution si le(s) caractère(s) lu(s) ne correspond(ent) pas au type de la variable
spécifiée dans l'appel (principalement pour des variables de type numérique). Pour éviter ce genre
d'erreur, il faudrait qu'un programme ne lise que des variables de type caractère ou chaîne de caractères
et fasse lui même, de manière explicite, la conversion en valeurs numériques s'il y a lieu.
La façon dont les marques de fin de ligne et de fin de fichier sont stockées dans un fichier de type TEXT
dépend de l'implantation des modules d'entrée/sortie. Dans certains cas, la fin de ligne sera représentée
par le caractère de contrôle de retour de chariot, dans un autre cas cela pourra être sous la forme d'un
attribut de longueur précédant chaque ligne et indiquant le nombre de caractères de la ligne. De même,
pour la fin de fichier, plusieurs solutions sont em ployées selon l'implantation. Le problème principal
pour une implantation donnée est de déterminer si la marque de fin de fichier apparait en même temps
que la marque de fin ligne (de la dernière ligne) ou après que l'on ait essayé de passer à la ligne suivante.

7.Structures récursives linéaires : les


chaînes
Table des matières.

http://cuisung.unige.ch/std/06.3.html [07-06-2001 12:17:08]


Structuration des Données Informatiques - 7.

7. Structures récursives linéaires : les


chaînes
Définition : une chaîne est une suite finie de n éléments du même type. Elle sera notée : C = (e1 e2 . . .
en)
N.B. si n=0, C=( ) est la chaîne vide
Une chaîne est une structure dont chaque élément possède un et un seul pointeur vers un autre élément du
même type. Les chaînes sont parfois appelées listes linéaires.
Exemple

type Element = record


Info: InfoType;
Suivant: ^Element;
end; { Element }

var Debut: ^Element;

{initialisation:}Debut:=NIL;{chaîne vide}
Les principales opérations sur les chaînes sont :
- construction:
insertion au début, à la fin, avant ou après un élément donné.
- modification:
suppression d'un élément donné, du premier ou du dernier élément.
- utilisation:
recherche d'un élément, parcours de la chaîne, détermination de la longueur.
Nous n'aborderons pas de manière détaillée le changement de valeur d'un élément donné, car cela revient
quasiment à effectuer une recherche de l'élément à changer.
On peut décrire les chaînes en terme de type abstrait de la manière suivante:
● le rôle d'une chaîne est de stocker un nombre indéterminé d'éléments de même type dans un certain
ordre. Cet ordre pourra dépendre de la chronologie d'insertion des éléments, de la valeur des
éléments insérés ou d'un quelconque autre critère fixé par l'utilisateur.
● la déclaration d'une chaîne doit spécifier le type de base des éléments qu'elle pourra contenir.

● primitives de manipulation :

1. initialiser une chaîne à vide.


2. insérer un élément en début de chaîne.
3. insérer un élément en fin de chaîne.
4. insérer un élément avant un élément de la chaîne auquel on a accès.

http://cuisung.unige.ch/std/07.0.html (1 of 2) [07-06-2001 12:17:11]


Structuration des Données Informatiques - 7.

5. insérer un élément après un élément de la chaîne auquel on a accès.


6. détruire l'élément en début de chaîne.
7. détruire l'élément en fin de chaîne.
8. détruire un élément de la chaîne auquel on a accès.
9. permettre l'accès au premier élément de la chaîne.
10. fournir la valeur de type de base contenue dans un élément auquel on a accès.
11. indiquer si l'élément auquel on a accès possède un successeur.
12. permettre l'accès à l'élément suivant un élément auquel on a déjà accès
13. indiquer si la chaîne est vide.
On pourrait encore énumérer d'autres primitives de manipulation de chaîne basées sur la position des
éléments dans la chaîne. Par exemple, insérer un élément en N-ème position de la chaîne (si cette
position existe), détruire le N-ème élément de la chaîne (si cette position existe), trouver la position du
premier élément contenant une valeur donnée (si un tel élément existe), fournir la longueur de la chaîne .
..

7.1. Construction

Table des matières.

http://cuisung.unige.ch/std/07.0.html (2 of 2) [07-06-2001 12:17:11]


Structuration des Données Informatiques - 7.1

7.1. Construction
Exemples

type VersElement: ^Element;

procedure InsertionDebut(var Debut: VersElement;


Information: InfoType);

var Nouveau: VersElement;

begin
new(Nouveau); {1:crée un nouvel élém. }
with Nouveau^ do begin
Info := Information; {2:y met l'info }
Suivant := Debut; {3:le relie à la }
end; { with } { chaîne existante }
Debut := Nouveau; {4:le nouvel element devient le 1-er}
end; { InsertionDebut } {5:la var. locale disparaît }
On peut illustrer les quatre étapes de cette procédure à l'aide de la figure 7.1.

L'insertion en début de chaîne a l'inconvénient de stocker les éléments dans l'ordre inverse de traitement.
Si l'on désire que l'ordre de stockage corresponde à l'ordre d'insertion, il faut insérer les éléments à la fin
de la chaîne. En conservant les déclarations faites plus haut, l'algorithme d'insertion en fin de chaîne peut
être décrit par la procédure InsertionFin1 de la page suivante.

procedure InsertionFin1(var Debut: VersElement;


Information: Info);

http://cuisung.unige.ch/std/07.1.html (1 of 3) [07-06-2001 12:17:18]


Structuration des Données Informatiques - 7.1

var Courant: VersElement;


begin { InsertionFin1 }
if Debut = nil then begin {chaîne vide}

{ création du premier élément }


new(Debut);
with Debut^ do begin
Info := Information;
Suivant := nil;
end; { with }
end
else begin

{ recherche du dernier élément }


Courant := Debut;
while Courant^.Suivant <> nil do
Courant := Courant^.Suivant;

{ crée un élément après le dernier }


new(Courant^.Suivant)
with Courant^.Suivant^ do begin
Info := Information;
Suivant := nil;
end; { with }
end;
end; { InsertionFin1 }
Cet algorithme d'insertion en fin de chaîne est assez inefficace car il faut à chaque fois parcourir toute la
chaîne pour trouver le dernier élément. Si l'on doit renouveler cette opération à plusieurs reprises, on a
intérêt à avoir un pointeur indiquant la fin de la chaîne, en plus du pointeur de début. Il est à noter que
cette solution complique un peu les autres manipulations de chaînes car il faut s'assurer, à chaque
modification, que le pointeur Fin est mis à jour s'il y a lieu.

type Chaine: record


Debut, Fin: VersElement;
end; { Chaine }

procedure InsertionFin2(var LaChaine:Chaine;


Information: Info);

var Nouveau: VersElement;

begin { InsertionFin2 }
new(Nouveau); { créer le nouvel élém. }
with Nouveau^ do begin
{ remplir le nouvel élément }

http://cuisung.unige.ch/std/07.1.html (2 of 3) [07-06-2001 12:17:18]


Structuration des Données Informatiques - 7.1

Info := Information;
Suivant := nil;
end; { with }

with LaChaine do begin


{ mise-à-jour de la str. de chaîne}
if Debut = nil then
{ chaîne vide } Debut := Nouveau
else Fin^.Suivant := Nouveau;
Fin := Nouveau;
end; { with }
end; { InsertionFin2 }
Il est clair que, pour de telles chaînes (avec pointeur Fin), l'ensemble des opérations effectuées devra
tenir à jour ce pointeur Fin.
Si l'on désire stocker les éléments dans un ordre indépendant de l'ordre d'insertion (par exemple triés), on
peut être amené à faire des insertions après ou avant un élément donné.

procedure InsertionApres(Courant: VersElement;


Information: InfoType);

{ cette procédure ne fonctionne que pour


des chaînes spécifiées par un pointeur
Debut (pas de pointeur Fin) }

var Nouveau: VersElement;

begin { InsertionApres }
new(Nouveau);
with Nouveau^ do begin
Info := Information;
Suivant := Courant^.Suivant;
end; { with }
Courant^.Suivant := Nouveau;
end; { InsertionApres }
Pour une insertion avant un élément donné, il est généralement plus simple de faire une insertion après,
puis d'échanger les informations du nouvel élément et de l'élément donné. Cela évite de devoir parcourir
toute la chaîne depuis son début pour retrouver le prédécesseur de l'élément à insérer.

7.2. modification

Table des matières.

http://cuisung.unige.ch/std/07.1.html (3 of 3) [07-06-2001 12:17:18]


Structuration des Données Informatiques - 7.1, exercice 1

Exercice suivant

Chaînes mono-directionnelles
Question posée au contrôle continu du 4 mai 1998
Compléter le programme suivant en donnant le corps des procédures Insertion et Afficher, étant donnée
la structure de données citée par la suite
N.B. On conserve dans un tableau TabElement des chaînes avec leur taille respective.
Program Chaines;
{Programme qui permet d'affecter des valeurs aux chaînes qui se
trouvent dans un tableau et d'afficher le contenu de ce tableau}
CONST
MaxNbSommets = ...;
MaxNumChaine = ...;
TYPE
PtrNoeud = ^Noeud;

Noeud = RECORD
Donnee: integer;
Suivant: PtrNoeud;
END;

EltNoeud = RECORD
Taillechaine: Integer;
Chaine: PtrNoeud;
END;

VAR TabElement: ARRAY[1..MaxNumChaine] OF EltNoeud;

Procedure Insertion(Valeur, NumChaine : integer);


{Procédure permettant d'insérer une valeur à la fin de la
NumChaine-ème chaîne}

Procedure Afficher;
{Procédure permettant d'afficher le contenu du champs "Donnee"
pour toutes les chaînes du tableau}
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/7/1a.html [07-06-2001 12:17:20]


Structuration des Données Informatiques - 7.2

7.2. modification

procedure SuppressionPremier(var Debut: VersElement);


var Courant: VersElement;
begin { SuppressionPremier }
if Debut = nil then {chaîne déjà vide}
else begin
Courant := Debut;
Debut := Debut^.Suivant;
dispose(Courant);
end; { else }
end; { SuppressionPremier }

procedure SuppressionDernier(var Debut: VersElement);

var Courant: VersElement;

begin { SuppressionDernier }
if Debut = nil then {chaîne déjà vide}
else if Debut^.Suivant = nil then
dispose(Debut){chaîne d'un seul él.}
else begin
Courant := Debut;
{ tant qu'il y a 2 successeurs,
passe à l'élém. suivant}
while Courant^.Suivant^.Suivant <> nil do
Courant := Courant^.Suivant;
{ l'élément pointé par Courant n'a }
{ plus qu'un successeur: le dernier }
dispose(Courant^.Suivant);
end; { else }
end; { SuppressionDernier }

procedure SuppressionMilieu(Courant: VersElement);

{ supprime l'élément pointé sans modifier


le pointeur. Restriction: ne marche pas
pour le dernier élément}

var Prochain: VersElement;

begin { SuppressionMilieu }
Prochain := Courant^.Suivant;
{ copie obj. pointés et non pointeurs }

http://cuisung.unige.ch/std/07.2.html (1 of 2) [07-06-2001 12:17:23]


Structuration des Données Informatiques - 7.2

Courant^ := Prochain^;
dispose(Prochain);
end; { SuppressionMilieu }
autre possibilité :

procedure SuppressionMilieu(var Courant:VersElement);

{ - Supprime l'élément pointé et change


le pointeur pour le faire pointer
vers l'élément suivant.
- Pour qu'elle fonctionne correctement,
il faut que, lors de l'appel à cette
procédure, on utilise comme paramètre
effectif le pointeur "suivant" de
l'élément précédent. }

var Ancien: VersElement;

begin { SuppressionMilieu }
Ancien := Courant;
Courant := Courant^.Suivant;
dispose(Ancien);
end; { SuppressionMilieu }
Si l'on se trouve dans une situation où aucune de ces deux possibilités de SuppressionMilieu ne peut être
appliquée telle quelle parce que les contraintes qui y sont liées sont trop restrictives, il faudra envisager
de modifier le premier exemple, soit en rajoutant, pour le traitement du dernier élément, le parcours de la
chaîne jusqu'à trouver l'avant-dernier, soit en ayant un élément "bidon" qui reste inutilisé à la fin de la
chaîne et qui évite de devoir supprimer le dernier élément. Dans ce dernier cas, une chaîne vide
contiendrait, malgré tout, au moins l'élément "bidon".

7.3. Utilisation

Table des matières.

http://cuisung.unige.ch/std/07.2.html (2 of 2) [07-06-2001 12:17:23]


Structuration des Données Informatiques - 7.2, exercice 1

Chaînes mono-directionnelles
Question posée à l'examen écrit du 9 juillet 1996
Soit une d`une chaîne monodirectionnelle avec des éléments entiers. Ecrivez une procédure Pascal qui
supprime à la fois le premier et le dernier élément de la chaîne, et ceci de manière répétitive, jusqu`a ce
qu'il ne reste qu'un seul élément, ou jusqu'a ce que la chaîne soit vide. La procédure donnera comme
résultat soit le dernier élément restant, soit le pointeur "nil".
Exemples:
1.

Dans ce cas, on supprime les éléments contenant les valeurs entièlres 2 et -1, et le résultat de la
procédure sera alors 3.
2.

Dans ce cas, on supprimera d'abord 2 et 5, ensuite 3 et -1, et le résultat sera alors "nil".
Solution

http://cuisung.unige.ch/std/ex/7/2a.html [07-06-2001 12:17:29]


Structuration des Données Informatiques - 7.3

7.3. Utilisation
7.3.1. Parcours
Il est assez courant de devoir appliquer un certain traitement T() à l'ensemble des éléments d'une chaîne
(par exemple impression). Cela peut se faire très simplement de la manière suivante :

var p, Debut: VersElement;


...
p := Debut;
while p <> nil do begin
T(p^);
p := p^.Suivant;
end; { while }

7.3.2. Recherche d'un élément


La recherche d'un élément ayant une valeur particulière semblerait, au premier abord, ne pas poser plus
de problèmes que le simple parcours. Toutefois, si l'on écrit l'algorithme suivant :

var p, Debut: VersElement;


...
p := Debut;
while (p <> nil) and (p^.Info <> ValeurRecherchee) do
p := p^.Suivant;
if p <> nil then { trouvé } ...
la condition logique de la boucle "while" n'est pas toujours définie. En effet, si p=nil, la structure p^
n'existe pas ! (ce qui est différent d'une variable dont le contenu est indéfini). Dans un tel cas,
l'algorithme est en erreur. Pour remédier à cet inconvénient, on peut envisager plusieurs solutions :
1. utilisation d'un débranchement explicite "GoTo" qui interrompt la répétition

while p <> nil do


if p^.Info = ValeurRecherchee then GoTo Trouve
else p := p^.Suivant;
{ on arrive ici si la valeur cherchée n'a
pas été trouvée }
. . .
Trouve: { on arrive ici si la valeur a
été trouvée }
Cette solution n'est pas très élégante et peut nécessiter l'utilisation de plusieurs débranchements.
2. variable booléenne ("flag") enregistrant le fait que la valeur cherchée a été trouvée

var PasTrouve: boolean;

http://cuisung.unige.ch/std/07.3.html (1 of 2) [07-06-2001 12:17:32]


Structuration des Données Informatiques - 7.3

PasTrouve := true;
while (p <> nil) and PasTrouve do
if p^.Info = ValeurRecherchee then PasTrouve := false
else p := p^.Suivant;
if not PasTrouve then . . .
3. certaines implantations du Pascal assurent explicitement l'évaluation de gauche à droite des
expressions logiques et admettent des pragmas permettant d'inhiber les contrôles de validité. Dans
de tels cas on peut alors écrire l'algorithme de la manière suivante :

while (p <> nil) and


{$R-} (p^.Info <> ValeurRecherchee)
{$R^} do
p := p^.Suivant;
if p <> nil then { trouvé } . . .
où le pragma {$R-} indique au compilateur de ne pas générer de code pour contrôler la validité des
opérations qui suivent et le pragma {$R^} indique au compilateur d'annuler l'effet du pragma
{$R-}.

7.4. Anneaux, chaînes bidirectionnelles

Table des matières.

http://cuisung.unige.ch/std/07.3.html (2 of 2) [07-06-2001 12:17:32]


Structuration des Données Informatiques - 7.4

7.4. Anneaux, chaînes bidirectionnelles


7.4.1. Les anneaux
Si l'on revient un instant sur l'algorithme "InsertionFin2", on peut remarquer que, pour accélérer
l'insertion en fin de chaîne, nous avons été amenés à ajouter un pointeur "fin de chaîne" en plus du
pointeur "début". Une autre solution consiste à rendre la chaîne circulaire. Cela se fait en remplaçant,
dans le dernier élément, le pointeur "Suivant" à nil par un pointeur "Suivant" qui pointe vers le premier
élément de la liste. Il est alors plus simple d'avoir un pointeur "Dernier" en lieu et place du pointeur
"Début" :

Une telle chaîne circulaire s'appelle un anneau. L'intérêt d'une telle structure est de pouvoir atteindre
tous les éléments de la chaîne depuis n'importe quel élément. Nous pouvons alors considérer la chaîne
comme n'ayant ni début ni fin. Si l'on désire toutefois pouvoir reconnaître le premier élément, il suffit de
tester si le pointeur vers cet élément est égal au pointeur "Dernier^.Suivant".

7.4.1.1. Manipulations sur les anneaux

Les algorithmes d'insertion, de suppression et de parcours d'éléments sont très similaires à ceux que nous
avons vus pour les chaînes linéaires. Il faut juste faire attention, lors d'insertion ou de suppression en fin,
de mettre correctement à jour le pointeur dernier. De plus, lors d'insertion après un élément donné, il faut
tester si l'insertion s'est faite en fin d'anneau pour ajuster le pointeur Dernier en conséquence. Pour une
insertion avant un élément donné, il faut renoncer au "truc" de l'insertion "après" avec échange des
contenus, si l'élément donné se trouve en fin de chaîne.
La plupart des algorithmes d'insertion doivent traiter le cas particulier de l'anneau vide. Cela peut se faire
à l'aide des instructions suivantes :

if Dernier = nil then begin {anneau vide}


new(Dernier);
with Dernier^ do begin
Info := ...;
{ l'él. est son propre succ.}
Suivant := Dernier;
end; { with }
end; { if }
Pour le parcours d'un anneau il faut prendre en compte le fait qu'il n'y a plus de pointeur à nil pour
indiquer la fin de la chaîne. Cela donne un algorithme de parcours tel que :

http://cuisung.unige.ch/std/07.4.html (1 of 5) [07-06-2001 12:18:12]


Structuration des Données Informatiques - 7.4

procedure Parcours(dernier: VersElement;


Traite: procedure);

var Courant, premier: VersElement;

begin { Parcours }
if Dernier = nil then { anneau vide }
else begin
Premier := Dernier^.Suivant;
Courant:= Premier;
repeat
{ traitement quelconque à
appliquer à chaque élément }
Traite(Courant);
Courant := Courant^.Suivant;
until Courant = Premier;
end;
end; { Parcours }

7.4.2. Les chaînes bidirectionnelles


Si l'on désire souvent pouvoir atteindre des éléments d'une chaîne qui se trouvent quelques positions
avant un élément donné, on peut alors adjoindre, à chaque élément, un pointeur supplémentaire vers son
prédécesseur. On obtient alors une chaîne bidirectionnelle :

La déclaration contient un pointeur supplémentaire chargé d'indiquer l'emplacement du prédécesseur. Il


faut donc que, lors de l'insertion ou de la suppression d'un élément, l'on tienne à jour les deux pointeurs
des éléments impliqués dans l'opération.

7.4.2.1. Manipulations sur les chaînes bidirectionnelles

Exemple d'insertion en début (ressemble beaucoup à l'exemple vu au paragraphe 1) :

type VersElement = ^Element;


Element = record
Info: ...;
Precedent,
Suivant : VersElement;
end; { Element }

http://cuisung.unige.ch/std/07.4.html (2 of 5) [07-06-2001 12:18:12]


Structuration des Données Informatiques - 7.4

procedure InsertionDebut(var Debut: VersElement;


Information: ...);

var Nouveau: VersElement;

begin { InsertionDebut }
new(Nouveau);
with Nouveau^ do begin
Info := Information;
Suivant := Debut;
Precedent := nil;
end; { with }
if Debut <> nil then Debut^.Precedent := Nouveau;
Debut := Nouveau;
end; { InsertionDebut }
Exemple d'insertion avant un élément donné (il n'est plus nécessaire d'utiliser le "truc" de l'insertion après
avec échange des contenus) :

procedure InsertionAvant(var Debut: VersElement;


Courant: VersElement;
Information: ...);
var Nouveau: VersElement;

begin { InsertionAvant }
{ création d'un nouvel élément }
new(Nouveau);
with Nouveau^ do begin
Info := Information;
Suivant := Courant;
Precedent := Courant^.Precedent;
end; { with }

{ modification des liens des éléments existants }


Courant^.Precedent := Nouveau;
if Debut = Courant then Debut := Nouveau
else Nouveau^.Precedent^.Suivant := Nouveau;
end; { InsertionAvant }
Exemple de suppression d'un élément donné :

procedure SuppressionMilieu(var Debut: VersElement;


var Courant: VersElement);

begin { SuppressionMilieu }
{ mise-à-jour du pointeur Suivant du

http://cuisung.unige.ch/std/07.4.html (3 of 5) [07-06-2001 12:18:12]


Structuration des Données Informatiques - 7.4

prédécesseur et du pointeur Precedent


du successeur }
if Courant = Debut then begin
Debut := Debut^.Suivant;
if Debut <> nil then
Debut^.Precedent := nil;
end
else with Courant^ do begin
Precedent^.Suivant := Suivant;
if Suivant <> nil then
Suivant^.Precedent := Precedent;
end; { with }
{ destruction de l'élément courant }
dispose(Courant);
end; { SuppressionMilieu }
Pour une chaîne bidirectionnelle, l'insertion ou la suppression en fin implique toujours un parcours
complet de la chaîne depuis son début, à moins que, comme pour les chaînes monodirectionnelles, l'on
gère un pointeur Fin.

7.4.3. Les anneaux bidirectionnels


Une chaîne bidirectionnelle peut, elle aussi, être rendue circulaire. On obtient alors un anneau
bidirectionnel :

Cette structure bénéficie des avantages combinés qu'apportent le pointeur "Prédécesseur" et la circularité.
Les opérations de suppression d'éléments et les manipulations en fin de chaîne s'en trouvent nettement
simplifiées.
On peut d'ailleurs résumer la facilité avec laquelle on peut effectuer les opérations d'insertion, de
destruction et de déplacement dans une chaîne, ou dans les différentes variantes que l'on vient de voir, de
la manière suivante (F=facile, D=difficile) :

http://cuisung.unige.ch/std/07.4.html (4 of 5) [07-06-2001 12:18:12]


Structuration des Données Informatiques - 7.4

7.5. Utilisation d'un élément sans données

Table des matières.

http://cuisung.unige.ch/std/07.4.html (5 of 5) [07-06-2001 12:18:12]


Structuration des Données Informatiques - 7.4, exercice 1

Exercice suivant

Anneaux bidirectionnels
Question posée au conctrôle continu du 17 juin 1996
On vous fournit un entier ainsi qu'un anneau bidirectionnel ayant comme éléments des entiers, pouvant
être négatifs. On vous demande de vous déplacer dans l'anneau à la position absolue donnée par l'entier
qui vous est donné. Là, vous prenez l'entier qui se trouve dans l'élément courant, vous supprimez
l'élément courant, et vous vous déplacez dans l'anneau, cette fois-ci de façon relative à la position
courante en fonction du nombre qui s'y trouvait. Vous devez répéter ces pas (prendre l'entier, supprimer
l'élément, se déplacer) jusqu'à ce que il n'y ait plus d'élément dans l'anneau, jusqu'à ce que vous sortiez
de l'anneau, ou bien jusqu'à ce que vous rencontriez la valeur 0 (zéro) dans l'anneau. (Attention, si vous
arrivez à la fin de l'anneau, il ne faut pas repartir depuis le début; de même, si on arrive au début,
il ne faut pas continuer à la fin).
On vous demande de fournir, dans un paramètre de sortie, le type d'arrêt et d'imprimer, dans la
procédure, les valeurs se trouvant dans les éléments visités. Utilisez les déclarations suivantes:

type TypeArret = (SortieAnneau, ElementNul, AnneauVide);


Anneau = ^Element;
Element = record
Entier : Integer;
Predecesseur, Succeseur : Anneau;
end; { Element }

procedure DeplacerDansAnneau(var UnAnneau : Anneau;


Deplacement: integer; var Arret : TypeArret);
Exemple:

Dans le cas ci-dessus, si l'entier donné initialement est 1, on va à la première position, on prend 2, on
l'imprime et on supprime la première position de l'anneau. On se déplace en avant de 2 positions
(correspondant à la position 3 dans l'anneau initial), on prend -2, on l'imprime et on supprime l'é lément.
Ensuite il faut signaler la fin du programme, parce qu'avant l'élément que l'on vient de suprimer il n'y a
qu'un seul élément (contenant 4) et l'on ne peut donc pas se déplacer de 2 éléments en arrière.
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/7/4a.html [07-06-2001 12:18:41]


Structuration des Données Informatiques - 7.5

7.5. Utilisation d'un élément sans données


Comme on l'a vu dans un bon nombre des exemples précédents, la prise en compte de la chaîne vide
nécessite généralement un traitement particulier (modification du pointeur "Debut" . . .). Pour éviter de
devoir traiter ce cas particulier on peut envisager de "sacrifier" un élément de la chaîne à seule fin de ne
jamais avoir le pointeur "Debut" à nil. Cet élément ne contient pas de donnée et doit rester en fin de
chaîne. L'initialisation d'une chaîne à vide se fait, en conséquence, de la manière suivante (pour une
chaîne monodirectionnelle):

new(Debut);
Debut^.Suivant := nil;
De la même manière, le test d'une chaîne vide se fera de la manière suivante :

if Debut^.Suivant=nil then {chaîne vide}


Avec cette méthode, le premier algorithme de suppression d'un élément donné que nous avons vu au
paragraphe 2 (SuppressionMilieu) n'a plus de restriction de fonctionnement puisque le dernier élément de
la chaîne a contenir des données ne se trouve pas en dernière position de la chaîne.
Cette méthode peut aussi être appliquée aux structures d'anneaux mono- et bidirectionnels et aux chaînes
bidirectionnelles. Quelle que soit la structure et l'opération, il faudra juste faire attention à ne pas
appliquer de traitement à ce dernier élément, qui est d'ailleurs facilement reconnaissable de par sa
position à l'extrémité de la structure.
Le test de structure vide est le même pour les chaînes bidirectionnelles que pour les chaînes
monodirectionnelles. Pour les anneaux, ce test doit être légèrement adapté :

if Debut^.Suivant=Debut then{anneau vide}


Les modifications à apporter aux différents algorithmes vus tout au long de ce chapitre sont laissés à la
perspicacité du lecteur.

7.6. Structures auxiliaires: Piles, files d'attente,


files circulaires

Table des matières.

http://cuisung.unige.ch/std/07.5.html [07-06-2001 12:18:44]


Structuration des Données Informatiques - 7.6

7.6. Structures auxiliaires: Piles, files d'attente, files


circulaires
Ces structures de données que l'on qualifie aussi de structures intermédiaires car elles s'utilisent souvent
en conjonction avec d'autres, sont d'une grande importance dans de nombreux domaines tels que :
● systèmes d'exploitation,

● évaluation d'expressions (arithmétiques, logiques, etc.),

● bases de données (interrogation, mise-à-jour)

Ces structures se caractérisent par un comportement particulier en ce qui concerne l'insertion et


l'extraction d'un élément. Ainsi, pour une pile, on insère et on extrait les éléments à la même extrémité de
la suite linéaire. Pour la file d'attente, l'insertion se fait à un bout et l'extraction à l'autre.

7.6.1. Piles
Les piles ont la propriété que le dernier élément inséré est le premier à pouvoir en être extrait. On utilise
souvent le terme de LIFO (de l'anglais "Last In First Out") Exemple d'utilisation Traitement des
structures emboîtées (par exemple, analyse d'un programme : boucles imbriquées, procédures locales;
parcours d'arbres, etc.) pour lesquelles il faut que les sous-structures soient traitées avant la structure qui
les contient. Si on traite une structure de niveau "n" et que l'on rencontre une sous-structure S, on
interrompt le traitement au niveau "n" en plaçant les informations indispensables sur la pile qui nous
permettront de reprendre le traitement de "n" après avoir terminé celui de S. Ainsi à tout moment la pile
contient, dans l'ordre inverse, les informations relatives aux niveaux momentanément interrompus.

Description en terme de type abstrait :


● le rôle d'une pile est de permettre la mise en attente d'informations dans le but de les récupérer plus
tard. La première information à être récupérée est celle qui a été mise en attente en dernier (la plus
récente).
● la déclaration d'une pile doit spécifier le type de base des éléments qu'elle pourra contenir.

● primitives de manipulation :

1. initialiser une pile à vide.


2. ajouter un nouvel élément sur la pile.

http://cuisung.unige.ch/std/07.6.html (1 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 7.6

3. récupérer (ôter) l'élément le plus récent de la pile. Diagnostiquer l'absence d'un tel élément
(pile vide).
4. indiquer si la pile est vide.
Si l'on compare cette description avec celle qui a été faite au début de ce chapitre concernant les chaînes,
on peut facilement se convaincre qu'une structure de chaîne peut être utilisée pour implanter une
structure de pile. Il suffit en effet de se restreindre à des manipulations en début de chaîne (insertion,
suppression, accès) pour respecter les contraintes de la structure de pile.

7.6.2. Files d'attente


Une file d'attente est caractérisée par le fait que les insertions se font à un bout de la suite et les
extractions à l'autre. En anglais, on parle de FIFO (First In First Out).

Dans une file d'attente l'ordre d'arrivée des éléments est préservé.
Exemples d'utilisation
● mise en attente des programmes pour exécution

● accès à un dispositif périphérique (p. ex. imprimante)

Description en terme de type abstrait :


● le rôle d'une file d'attente est de permettre la mise en attente d'informations dans le but de les
récupérer plus tard. La première information à être récupérée est celle qui a été mise en attente en
premier (la plus ancienne).
● la déclaration d'une file d'attente doit spécifier le type de base des éléments qu'elle pourra contenir.

● primitives de manipulation :

1. initialiser une file d'attente à vide.


2. ajouter un nouvel élément dans la file d'attente.
3. récupérer (ôter) l'élément le plus ancien de la file d'attente. Diagnostiquer l'absence d'un tel
élément (file d'attente vide).
4. indiquer si la file d'attente est vide.
Comme pour les piles, la comparaison de la description qui précède avec celle des chaînes nous montre
qu'une file d'attente peut être implantée à l'aide d'une structure de chaîne. Du fait que l'on a besoin
d'accéder aux deux extrémités de la structure (en début pour la suppression et en fin pour l'insertion), il
faut gérer la chaîne avec un pointeur Début et un pointeur Fin, ou alors utiliser un anneau.

http://cuisung.unige.ch/std/07.6.html (2 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 7.6

Notation
Indépendamment de la représentation et de l'implantation d'une structure de pile ou de file d'attente, on
peut exprimer les actions d'insertion et d'extraction comme :
P x insérer l'élément x dans la pile P

x P transférer dans la variable x le contenu de l'élément au sommet de P.

F x insérer, dans la file F, l'élément représenté par la variable x.

x F extraire et mettre dans x l'élément le plus ancien de F.


Exemple
si F est une file d'attente initialement vide, F = ( )
si F 3 alors F = (3)
si F 17 alors F = (3,17)
si F 9 alors F = (3,17,9)
si x F alors F = (17,9) et x = 3
Si P est une pile dont les éléments sont des paires de valeurs numériques entières ordonnées :
si P (3,7) alors P = ((3,7))
si P (2,5) alors P = ((2,5), (3,7))
si x P alors P = ((3,7)) et x = (2,5)
x est une variable structurée.

7.6.3. Représentation et implantation


Il y a plusieurs manières de représenter les piles et les files d'attente. Entre autres la représentation
séquentielle (par tableaux) et la représentation chaînée (par pointeurs). Etant donné que nous avons déjà
pas mal détaillé les algorithmes de gestion de chaînes, nous nous contenterons ici de voir la
représentation par tableau. L'implantation à l'aide de tableaux impose une contrainte supplémentaire par
rapport à ce qui peut être réalisé à l'aide de chaînes. Cette contrainte consiste à imposer un quota
maximum au nombre d'éléments qui peuvent être stockés dans la structure, ceci à cause de la nature
statique de la structure de tableau.

7.6.3.1. Pile : Représentation par tableau

déclaration
const Max=...; {hauteur max. de la pile}

type Objet = ...;


Pile = record
Element : array [1..Max] of Objet;
Sommet : 0..Max;
end; { Pile }

http://cuisung.unige.ch/std/07.6.html (3 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 7.6

var P : Pile;
initialisation
P.Sommet := 0; {pile vide}
opérations
. test si vide
function Vide (P:Pile) : boolean;
begin
Vide := P.Sommet = 0;
end; { Vide }
b. extraction
procedure Pop (var P: Pile; var Valeur: Objet);
begin
if Vide (P) then Erreur ('pile vide')
else begin
Valeur := P.Element[P.Sommet];
P.Sommet := pred(P.Sommet);
end;
end; { Pop }
c. insertion
procedure Push(var P: Pile; Valeur: Objet);
begin
if P.Sommet = Max then Erreur('pile pleine')
else begin
P.Sommet := succ(P.Sommet);
P.Element [P.Sommet] := Valeur;
end;
end; { Push }

7.6.3.2. File d'attente : représentation par tableau

Comme nous allons pouvoir le remarquer, l'organisation la plus simple d'une file d'attente à l'intérieur
d'un tableau n'est pas très efficace. C'est pourquoi une solution de file circulaire sera aussi proposée.
déclaration
const Max = ...;

type Objet = ...;


FileDAttente = record
Element: array[1..Max] of Objet;
NbElements: 0..Max;
end; { FileDAttente }

var F: FileDAttente;

http://cuisung.unige.ch/std/07.6.html (4 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 7.6

initialisation
F.NbElements := 0;
opérations
. test si vide
function Vide (F: FileDAttente): boolean;
begin
Vide := F.NbElements=0;
end; { Vide }
b. extraction
procedure Ote(var F: FileDAttente; var Valeur: Objet);
var i: integer;
begin
if Vide(F) then Erreur('file vide')
else with F do begin
Valeur := Element[1];
for i := 2 to NbElements do
Element[pred(i)] := Element[i];
NbElements := pred(NbElements);
end; { with }
end; { Ote }
c. insertion
procedure Met(var F: FileDAttente; Valeur: Objet);
begin
with F do begin
if NbElements = Max then Erreur('file pleine')
else begin
NbElements := succ(NbElements);
Element[NbElements] := Valeur;
end; { else }
end; { with }
end; { Met }

7.6.3.3. File circulaire

déclaration
const Max = ...;

type Objet = ...;


FileCirculaire = record
Element: array[0..Max] of Objet;
IndexIn,
IndexOut: 0..Max;
Plein:boolean;
end; { FileCirculaire }

http://cuisung.unige.ch/std/07.6.html (5 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 7.6

var B: FileCirculaire;
initialisation

B.IndexIn := 0;
B.IndexOut := 0;
B.Plein := false;
opérations
. test si vide
function Vide(B: FileCirculaire): boolean;
begin
with B do Vide := (IndexIn = IndexOut) and not Plein;
end; { Vide }
b. insertion
procedure Met( var B: FileCirculaire; Valeur: Objet);
begin
if B.Plein then Erreur('buffer plein')
else with B do begin
Element[IndexIn] := Valeur;
IndexIn:=succ(IndexIn) mod succ(Max);
Plein := IndexIn=IndexOut;
end; { with }
end; { Met }
c. extraction
procedure Ote( var B: FileCirculaire; var Valeur: Objet);
begin
with B do begin
if Vide(B) then Erreur('file vide')
else begin
Valeur := Element[IndexOut];
IndexOut := succ(IndexOut) mod succ(Max);
Plein := false;
end; { else }
end; { with }
end; { Ote }

8. Structures récursives non linéaires : les arbres

Table des matières.

http://cuisung.unige.ch/std/07.6.html (6 of 6) [07-06-2001 12:18:58]


Structuration des Données Informatiques - 8.0

8. Structures récursives non-linéaires


:les arbres
Un arbre est une structure homogène dont chaque élément, appelé noeud, contient de l'information et
plusieurs liens (pointeurs) vers des éléments du même type.
D'une manière plus formelle, on peut dire qu'un arbre de type de base T est :
● soit la structure vide

● soit un noeud de type T (racine) auquel est associé un nombre fini de sous-arbres disjoints
(descendants). Une structure d'arbre est souvent illustrée sous la forme suivante :

Fig.8.1. Illustration d'une structure d'arbre


Définitons
Le niveau d'un noeud est le nombre d'arcs qu'il faut parcourir pour arriver à ce noeud. La racine est de
niveau 1.
La profondeur d'un arbre (ou hauteur) est le nombre maximum d'arcs qu'il faut parcourir pour aller de la
racine à une feuille.
Les noeuds terminaux (qui n'ont pas de descendants) sont appelés feuilles. Les noeuds non-terminaux
sont appelés noeuds intérieurs.
Un noeud Y, situé immédiatement sous un noeud X, est appelé le descendant (direct) de X. Inversement,
X est appelé l'ancêtre (direct) de Y.
Le nombre de sous-arbres associés à un noeud (nombre de descendants directs) est appelé le degré du
noeud. Le degré d'un arbre correspond au degré le plus élevé de ses noeuds. Une chaîne (liste linéaire) est
un arbre de degré 1.
Le chemin d'un noeud est la suite d'arcs qui mènent de la racine à ce noeud. La longueur du chemin d'un
noeud est donc égale au niveau de ce noeud.
Un arbre est dit équilibré si, à chaque niveau, la profondeur des différents sous-arbres ne varie pas de
plus d'un niveau.

http://cuisung.unige.ch/std/08.0.html (1 of 2) [07-06-2001 12:19:25]


Structuration des Données Informatiques - 8.0

Un arbre ordonné est un arbre où la position respective des sous-arbres reflète une relation d'ordre.
Un arbre de degré 2 est appelé arbre binaire. Un arbre de degré supérieur à 2 est appelé arbre multiple.
Propriétés
● Le nombre maximum de noeuds qu'un arbre de profondeur "p" et de degré "d" peut contenir, si
tous les noeuds internes ont "d" descendants, est :

Nd(p)= di

car, à la racine, on a 1 noeud,


au niveau 1, on a d noeuds,
au niveau 2, on a d2 noeuds,
au niveau p, on a dp-1 noeuds,

en particulier, pour un arbre binaire, on a : N2(P)= 2i=2p-1

● La profondeur d'un arbre binaire équilibré, ayant N noeuds, est toujours comprise entre
log2(N+1) et *log2(N+2) -( -2)

8.1. Les arbres binaires ordonnés

Table des matières.

http://cuisung.unige.ch/std/08.0.html (2 of 2) [07-06-2001 12:19:25]


Structuration des Données Informatiques - 8.1

8.1. Les arbres binaires ordonnés


Parmi les différentes structures d'arbres possibles, une des plus intéressantes à utiliser est la structure
d'arbre binaire ordonné (arbre de tri, arbre syntaxique, arbre généalogique...).
Exemple
type Noeud = record
Info : InfoType;
Gauche, Droite : ^Noeud;
end; { Noeud }
var Racine : ^Noeud;

{initialisation:}Racine:=nil;{arbre vide}
Description en terme de type abstrait :
● le rôle d'une structure d'arbre binaire ordonné est de stocker des informations en conservant, en
plus, une hiérarchie entre ces informations. Cette hiérarchie reflète des liens de dépendance entre
les données. Il peut y avoir au plus deux types de liens différents (lien gauche et lien droite).
● la déclaration d'un arbre binaire ordonné doit spécifier le type de bases des éléments qu'il
contiendra (structure homogène).
● primitives de manipulation:

1. initialiser un arbre à vide.


2. définir la racine d'un arbre vide.
3. regrouper un élément (racine) et deux sous-arbres (un des sous-arbres peut être vide)
4. ajouter un élément (éventuellement tout un sous-arbre) à gauche (ou à droite) d'un élément
ne possédant pas de descendant gauche (ou droit, respectivement).
5. indiquer si l'arbre est vide.
6. indiquer l'absence éventuelle de descendant gauche (ou droit) pour un élément auquel on a
accès.
7. accéder à la racine de l'arbre.
8. accéder à la racine du sous-arbre gauche (ou droit) d'un élément auquel on a accès.
9. décomposer un arbre en un élément (ancienne racine) et un ou deux sous-arbres (suivant que
la racine avait un ou deux descendants).
10. détruire un (sous-)arbre (l'arbre peut n'être composé que d'un seul élément).
11. remplacer un sous-arbre par un autre sous-arbre.
Les primitives 3,9,10 et 11 peuvent être combinées pour supprimer un élément quelconque d'un arbre.

http://cuisung.unige.ch/std/08.1.html (1 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

8.1.1. Arbres syntaxiques


Un arbre syntaxique est une structure chargée de refléter la précédence existant entre les différents
opérateurs et symboles d'un langage. Ils sont souvent utilisés pour représenter des expressions
algébriques. Dans ce cas, chaque noeud intérieur contient un opérateur et les feuilles contiennent les
opérandes. La hiérarchie des sous-arbres reflète la précédence des opérateurs et des parenthèses.

type Genres = (Operateur, Constante,


Variable);
VersNoeud = ^Noeud;
Noeud = record
Gauche,Droite: VersNoeud;
case Genre: Genres of
Operateur,
Variable : (Car: char);
Constante : (Valeur: integer);
end; { Noeud }
var Racine : VersNoeud;
Exemple : (a+b)*c-d/e

Fig.8.2. Arbre syntaxique d'une expression arithmétique.


La construction d'un arbre syntaxique se fait souvent en combinant un élément (opérateur) et deux arbres
(opérandes) pour former un nouvel arbre.

8.1.2. Arbres de tri


Un arbre de tri est une structure chargée de refléter une relation d'ordre existant entre différentes
informations. Ces informations doivent comporter un champ (clé de tri) sur lequel on puisse appliquer
une fonction permettant de savoir si un élément doit être avant ou après un autre. La plupart du temps, ce
champ sera de type numérique ou alphabétique sur lequel on pourra utiliser les opérateurs de
comparaison: <, > ou =. Un arbre de tri reflète un séquencement (linéaire) des éléments qu'il contient. Le
même ensemble de valeurs peut donc être représenté par différentes configurations d'arbres binaires
suivant l'ordre selon lequel les valeurs auront été traitées.

http://cuisung.unige.ch/std/08.1.html (2 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

Exemple

type VersNoeud = ^Noeud;

Noeud = record
Cle: integer;
Petits, Grands: VersNoeud;
end; { Noeud }
var Racine: VersNoeud; i: integer;

procedure Insere(Valeur: integer;


var P: VersNoeud);
begin { Insere }
if P = nil then begin
new(P);
with P^ do begin
Cle := Valeur;
Petits := nil;
Grands := nil;
end; { with }
end { then }
else if Valeur < P^.Cle then
Insere(Valeur, P^.Petits)
else if Valeur > P^.Cle then
Insere(Valeur, P^.Grands);
end; { Insere }

procedure Imprime(P: VersNoeud);


begin { Imprime }
if P^.Petits <> nil then
Imprime(P^.Petits);
writeln(P^.Cle);
if P^.Grands <> nil then
Imprime(P^.Grands);
end; { Imprime }

begin { programme }
Racine := nil; { arbre vide }
while not eof(input) do begin
{ construction }
read(i);
Insere(i,Racine);
end; { while }
Imprime(Racine); { parcours }
end.

http://cuisung.unige.ch/std/08.1.html (3 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

8.1.3. Opérations sur les arbres


Les opérations les plus courantes que l'on effectue sur une structure d'arbre sont les suivantes :
● insertion : à gauche ou à droite d'un noeud terminal.

● suppression : d'une feuille, d'un noeud intérieur.

● recherche d'un élément.

● parcours : en ordre, en pré-ordre, en post-ordre.

Il est bien entendu possible d'effectuer bien d'autres opérations sur des arbres. Par exemple combiner 2
arbres, extraire un sous-arbre d'un arbre, etc... Ces opérations dépendent étroitement du problème qu'elles
sont sensées résoudre et nous n'en donnerons pas d'exemple ici.
Comme on a pu le voir précédemment, dans un arbre de tri, un élément est toujours inséré en position
terminale (feuille).
La suppression d'un noeud pose généralement plus de problèmes que l'insertion. Pour un arbre de tri, il
faut distinguer trois cas :
● suppression d'une feuille.

● suppression d'un noeud n'ayant qu'un seul descendant.

● supression d'un noeud ayant deux descendants.

La suppression d'une feuille ne pose pas de problème; il suffit de mettre à nil le pointeur vers l'élément à
détruire et de récupérer la place occupée par l'élément détruit.

Fig. 8.3. Suppression d'une feuille


La suppression d'un noeud n'ayant qu'un seul descendant est à peine plus compliquée. Il suffit de faire
pointer l'ancêtre directement vers le descendant.

http://cuisung.unige.ch/std/08.1.html (4 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

Fig. 8.4. Suppression d'un noeud à un seul descendant.


La suppression d'un noeud ayant deux descendants est plus délicate. Il faut d'abord trouver l'élément le
plus à droite du sous-arbre gauche (le plus grand élément inférieur à celui que l'on veut détruire); ce sera
une feuille ou un noeud à un seul descendant gauche. On transfère alors le contenu de la feuille (ou du
noeud à un seul descendant gauche) dans le noeud que l'on voulait détruire et l'on détruit la feuille (ou le
noeud à un seul descendant gauche) à la place.
Le cas où l'élément le plus à droite du sous-arbre gauche est une feuille est illustré à la figure 8.5, alors
que celui où l'élément le plus à droite du sous-arbre gauche est un noeud à un seul descendant est illustré
à la figure 8.6.

Fig. 8.5. Suppression d'un noeud à deux descendant, échange avec une feuille.

Fig. 8.6. Suppression d'un noeud à 2 descendants, échange avec un noeud à un descendant.
On peut compléter l'algorithme du paragraphe 1.2. pour ajouter une procédure de suppression d'élément
comme suit :

http://cuisung.unige.ch/std/08.1.html (5 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

procedure Supprime(var P: VersNoeud);

var T1,T2: VersNoeud;

begin { Supprime }

if (P^.Petits=nil) and
(P^.Grands=nil) then
dispose(P) { une feuille }
else if P^.Petits=nil then begin
T1 := P; { 1 seul descendant }
P := P^.Grands;
dispose(T1);
end

else if P^.Grands=nil then begin


T1 := P; { 1 seul descendant }
P := P^.Petits
dispose(T1);
end
else begin { 2 descendants }
T1 := P^.Petits;
{ dans le sous-arbre des éléments
plus petits, y a-t-il des éléments
plus grands que la racine locale?
Dans la négative, échanger puis
supprimer la racine locale }
if T1^.Grands = nil then begin
P^.Cle := T1^.Cle;
Supprime(P^.Petits);
end
else begin
{ recherche du plus grand élément
du sous-arbre gauche }
while T1^.Grands <> nil do begin
T2 := T1;
T1 := T1^.Grands;
end; { while }
{ à la fin de la boucle, T1 pointe
vers le plus grand des éléments
du sous-arbre et T2 vers son
ancêtre immédiat }
P^.Cle := T1^.Cle;
Supprime(T2^.Grands);
end;

http://cuisung.unige.ch/std/08.1.html (6 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

end; { 2 descendants }
end; { Supprime }

La recherche d'un élément dans un arbre de tri est très similaire à l'insertion telle que nous l'avons vue en
8.1.2. Nous verrons donc ici une variante non récursive :

procedure Recherche( Racine: VersNoeud;


Valeur: integer;
var Emplacement: VersNoeud);

{ Si la valeur recherchée ne se trouve


pas dans l'arbre, Emplacement sera mis à nil. }

begin { Recherche }
Emplacement := Racine;
while (Emplacement <> nil) and
{$R-} (* suppression contrôle *)
(Emplacement^.Cle <> Valeur)
{$R^} do
if Emplacement^.Cle > Valeur then
Emplacement:=Emplacement^.Petits
else
Emplacement:=Emplacement^.Grands;
end; { Recherche }
On parcourt généralement un arbre pour appliquer un traitement à chacun de ses noeuds. On distingue
plus particulièrement trois méthodes de parcours d'arbres binaires :
● en pré-ordre : on traite la racine puis le sous-arbre gauche et enfin le sous-arbre droit.
● en ordre : on traite le sous-arbre gauche puis la racine et enfin le sous-arbre droit.
● en post-ordre : on traite le sous-arbre gauche puis le sous-arbre droit et enfin la racine.
L'impression d'un arbre de tri se fait à l'aide d'un parcours en ordre puisque la valeur stockée dans le
noeud racine se situe entre les valeurs du sous-arbre gauche et celles du sous-arbre droit. L'évaluation
d'un arbre syntaxique se fait avec un parcours en post-ordre puisqu'il faut évaluer les deux opérandes
avant de pouvoir traiter l'opérateur. A titre d'exemple, le parcours en ordre d'un arbre binaire peut se faire
de la manière suivante :

procedure ParcoursEnOrdre(Racine: VersElement);


begin { ParcoursEnOrdre }
if Racine^.Gauche <> nil then
ParcoursEnOrdre(Racine^.Gauche);
Traite(Racine);
if Racine^.Droite <> nil then

http://cuisung.unige.ch/std/08.1.html (7 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1

ParcoursEnOrdre(Racine^.Droite);
end; { ParcoursEnOrdre }

8.2. Arbres multiples

Table des matières.

http://cuisung.unige.ch/std/08.1.html (8 of 8) [07-06-2001 12:20:11]


Structuration des Données Informatiques - 8.1, exercice 1

Exercice suivant

Arbre de tri
Question posée au conctrôle continu du 17 juin 1996
a) Etant donné l'arbre de tri suivant, donnez une séquence possible des données fournies en entrée qui
aurait permis d'aboutir à l'arbre de tri en question:

b) idem pour l'arbre suivant:

Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/8/1a.html [07-06-2001 12:20:43]


Structuration des Données Informatiques - 8.2

8.2. Arbres multiples


Bien que cela soit rarement utilisé, il est tout à fait possible de construire un arbre de degré n à l'aide
d'une structure telle que :
const n = . . . ;
type Element = record
Info: ...;
Descendants:array[1..n]
of ^Element;
end; { Element }

8.2.1. Représentation d'arbre multiple sous forme d'arbre binaire


Il arrive souvent que l'on ait besoin de construire des arbres multiples dont on ne connaît pas à l'avance le
degré. A moins de réserver pour chaque noeud un suffisamment grand nombre de pointeurs vers des
descendants pour être sûr qu'il y en ait toujours assez, on est amené à représenter les arbres multiples à
l'aide d'arbres binaires.
On utilise alors un pointeur fils, pointant vers le premier des descendants, et un pointeur frère, pointant
vers le prochain noeud ayant le même ancêtre. La différence principale avec un arbre multiple réel est
que l'on n'a pas accès directement à tous les descendants d'un noeud; on est obligé de passer par le
premier descendant pour atteindre le deuxième, et ainsi de suite. Il faut aussi bien se rendre compte que
les deux pointeurs de chaque noeud n'ont pas la même signification que pour un arbre binaire habituel.

Fig. 8.7. Passage d'un arbre multiple à un arbre binaire.

8.3. Représentation d'arbres à l'aide de tableaux

Table des matières.

http://cuisung.unige.ch/std/08.2.html [07-06-2001 12:21:14]


Structuration des Données Informatiques - 8.2, exercice 1

exercice suivant

Arbre lexicographique
Question posée au contrôle continu du 20 juin 1995
On veut construire un arbre multiple d'ordre 26 permettant de stocker des mots d'un dictionnaire de sorte
que les chemins partant de la racine représentent des mots du dictionnaire, de la façon suivante

Les arcs sont étiquettés avec les lettres de l'alphabet (on suppose ici que l'on ne tient pas compte des
accents sur les lettres) et les noeuds contiennent un booléen indiquant si le chemin de la racine à ce
noeud représente un mot complet ou pas (sur le dessin - = faux et * = vrai).
En utilisant la possibilité que donne le langage Pascal d'indexer un tableau de pointeurs à l'aide d'un
intervalle de caractères afin de représenter les étiquettes des arcs, donner:
a) les déclarations de types et de variables nécessaires à la construction d'un tel dictionnaire,
b) une fonction booléenne ayant pour paramètre le dictionnaire et un string et indiquant si le mot contenu
dans le string se trouve dans le dictionnaire,
c) une procédure imprimant touts les mots du dictionnaire dans l'ordre alphabétique.
Solution

exercice suivant

http://cuisung.unige.ch/std/ex/8/2a.html [07-06-2001 12:21:19]


Structuration des Données Informatiques - 8.3

8.3. Représentation d'arbres à l'aide de tableaux


Pour les langages n'offrant pas la possibilité de faire de l'allocation dynamique, c'est la structure de
tableau qui est généralement employée en remplacement. Les pointeurs vers des éléments sont alors
remplacés par des indices dans le tableau :
const TailleMax = ...;
INil = 0;

type VersElement = 0..TailleMax;

Element = record
Info: ...;
Gauche,
Droit: VersElement;
end; { Element }
var Elements : array [1..TailleMax]
of Element;
Racine : VersElement;
Il arrive souvent (pour les arbres de tri, par exemple) que la racine corresponde systématiquement à la
première position du tableau. Dans ces cas, il arrive que l'on omette d'utiliser explicitement un pointeur
racine. Il serait toutefois plus élégant de déclarer Racine comme une constante valant 1.
Si l'on examine l'algorithme de parcours d'une structure d'arbre, on peut se rendre compte qu'il est
indispensable d'utiliser une structure de pile pour pouvoir effectuer le parcours. Si l'on utilise un langage
offrant la récursivité, c'est la pile d'activation des procédures, gérée par le système, qui est utilisée. Si l'on
emploie un langage de programmation n'offrant pas la récursivité, c'est à l'utilisateur de gérer
explicitement une pile des ancêtres mis en attente pour traiter leurs sous-arbres gauches. Cela peut être
fait de la manière suivante (il faut déclarer une structure de pile en plus des déclarations vues plus haut) :

type PileDef = record


Sommet: 0..TailleMax;
Contenu: array [1..TailleMax]
of VersElement;
end; { PileDef }
var Pile: PileDef;

{ Pour la définition des primitives de


manipulation de piles, se rapporter à
la section 6.3.1. du chap. précéd. }
procedure ParcoursNonRecursif(
Racine: VersElement);
var Courant: VersElement;
begin { ParcoursNonRecursif }
Courant := Racine;

http://cuisung.unige.ch/std/08.3.html (1 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3

while (Courant <> INil) or


not Vide(pile) do begin
if Courant=INil then Pop(Pile,Courant)
else while Elements[Courant].Gauche
<> INil do begin
Push(Courant,Pile);
Courant:= Elements[Courant].Gauche;
end; { while Elements... }
Traite(Courant);
Courant := Elements[Courant].Droit;
end; { while }
end; { ParcoursNonRecursif }

8.3.1. Arbres dépliables


Plutôt que d'utiliser une structure de pile pour retrouver les ancêtres, lors du parcours en ordre d'un arbre
représenté à l'aide d'un tableau, on peut tirer profit de ce que le pointeur Droit de l'élément le plus à droite
d'un sous-arbre gauche est inutilisé pour le faire pointer vers l'ancêtre immédiat de la racine de ce
sous-arbre.
Exemple

Fig. 8.8. Ajout d'arcs "ancêtre"


L'intérêt d'une telle structure est de permettre l'utilisation d'un algorithme de parcours ne nécessitant
l'utilisation ni d'une pile ni de la récursivité. L'utilisation mémoire d'un tel algorithme sera donc plus
réduite que les algorithmes de parcours d'arbres "normaux".
Le parcours d'une telle structure ne peut toutefois pas se faire sans précaution. On peut en effet remarquer
que les pointeurs qui ont été rajoutés font apparaître des boucles dans la structure. Il faut donc s'assurer,
lorsque l'on suit un pointeur gauche qui mène à un sous-arbre, que l'on n'utilisera pas ce même pointeur
gauche à nouveau quand le dernier pointeur Droit du sous-arbre nous aura ramené vers l'ancêtre
immédiat.
De prime abord, on pourrait envisager de "casser" le pointeur gauche (le mettre à nil) après l'avoir utilisé,
afin d'être sûr de ne pas l'utiliser à nouveau. Cela aurait toutefois le désavantage de modifier la structure
lors du parcours et, par conséquent, de ne permettre qu'un seul parcours.

http://cuisung.unige.ch/std/08.3.html (2 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3

Le fait que l'on utilise des nombres entiers en guise de pointeur nous permet d'envisager une autre
solution relativement simple. Cette solution consiste à changer le signe du pointeur gauche que l'on vient
d'utiliser et d'ignorer, ensuite, tout pointeur négatif. A la fin du parcours, il ne reste plus qu'à remettre
tous les pointeurs du tableau à des valeurs positives pour retrouver la structure d'origine. Ces différentes
manipulations peuvent se faire à l'aide des algorithmes suivants :

program ArbreDepliable;

const TailleMax = ...;


Racine = 1;
PseudoNil = 0;

type Information = ...;


Noeud = record
Info: Information;
Gauche, Droit: integer;
end; { Noeud}

var Noeuds: array[1..TailleMax] of Noeud;


Libre: 1..TailleMax;
Insere: boolean;
Valeur: Information;

procedure InsereGauche(Valeur: Information;


Parent: integer);
{ Cette procédure insère un nouvel
élément juste à gauche de l'élément
"Parent" dont la position est passée
en paramètre.
La valeur de ce nouvel élément est
passé en paramètre à la procédure.
La structure est mise à jour par effet
de bord.
La procédure met aussi à TRUE la
variable globale "Insere" qui est
utilisée dans la procédure "Ajout". }

begin { InsereGauche }
with Noeuds[Libre] do begin
Info := Valeur;
Gauche := PseudoNil;
Droit := Parent;
end; { with }
Noeuds[Parent].Gauche := Libre;
Libre := succ(Libre);

http://cuisung.unige.ch/std/08.3.html (3 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3

Insere := true;
end; { InsereGauche }

procedure InsereDroite(Valeur: Information;


Parent: integer);

{ Cette procédure insère un nouvel


élément juste à droite de l'élément
"Parent" dont la position est passée
en paramètre.
La valeur de ce nouvel élément est
passé en paramètre à la procédure.
La structure est mise à jour par effet
de bord.
La procédure met aussi à TRUE la
variable globale "Insere" qui est
utilisée dans la procédure "Ajout".}

begin { InsereDroite }
with Noeuds[Libre] do begin
Info := Valeur;
Gauche := PseudoNil;
Droit := Noeuds[Parent].Droit;
end; { with }
Noeuds[Parent].Droit := Libre;
Libre := succ(Libre);
Insere := true;
end; { InsereDroite }

procedure Ajout(Valeur: Information);


{ Cette procédure ajoute un nouvel
élément dans la structure.
La valeur de ce nouvel élément est
passé en paramètre à laprocédure.
La structure est mise à jour par effet
de bord.Cette procédure utilise la variable
globale "Insere" qui est modifiée par
effet de bord dans les procédures
"InsereGauche" et "InsereDroite" pour
indiquer que le nouvel élément a
été placé. }

var Courant: 1..TailleMax;

begin { Ajout }
Courant := Racine;

http://cuisung.unige.ch/std/08.3.html (4 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3

Insere := false;
repeat
with Noeuds[Courant] do
{ faut-il aller àdroite? }
if Info < Valeur then
{ élém.à droite pas ancêtre? }
if Droit >Courant then
{ si oui, on va à droite }
Courant := Droit
else
{sinon insère nouveau }
InsereDroite(Valeur, Courant)
{ y a-t-il un descendant à gauche?}
else if Gauche<> PseudoNil then
{ si oui, on va à gauche }
Courant := Gauche
else InsereGauche(Valeur, Courant);
until Insere;
end; { Ajout }

procedure Parcours;
var Courant, Temp: PseudoNil..TailleMax;
begin
Courant := Racine;
{ boucle de parcours des noeuds }
repeat
{ va le plus à gauche possible }
while Noeuds[Courant].Gauche>
PseudoNil do
{ après chaque déplacement à
gauche, on "casse" le pointeur utilisé }
with Noeuds[Courant] do begin
Courant := Gauche;
Gauche := -Gauche;
end ;{ while-with }
Traite(Courant);
{ La procédure Traite pourrait, par
exemple, correspondre à :
writeln(Noeuds[Courant].Valeur) }
Courant := Noeuds[Courant].Droit;
until Courant = PseudoNil;
{ remise en état des pointeurs Gauche }
for Courant := 1 to pred(Libre) do
with Noeuds[Courant] do
if Gauche < PseudoNil then
Gauche := -Gauche;

http://cuisung.unige.ch/std/08.3.html (5 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3

end; { Parcours }

begin { ArbreDepliable }
{ on lit le premier élémentséparément}
with Noeuds[Racine] do begin
Lire(Info); {on suppose que cette pro-
cédure lit une valeur }
Gauche := PseudoNil;
Droit := PseudoNil;
end; { with }
Libre := 2;
{ lecture+insertion desélém. suivants}

while not eof(input) do begin


Lire(Valeur);
Ajout(Valeur);
end; { while}
Parcours;

end. { ArbreDepliable }

8.4. Rééquilibrage d'arbres

Table des matières.

http://cuisung.unige.ch/std/08.3.html (6 of 6) [07-06-2001 12:21:33]


Structuration des Données Informatiques - 8.3, exercice 1

Exercice suivant

Arbres dépliables
Question posée à l'examen écrit du 25 juin 1998
. Dessiner un arbre dépliable de tri, qui sera construit à partir des mots suivants, en prenant la
séquence de gauche à droite :
la, lumiere, du, jour, etait, si, forte, qu, une, illumination, soudaine, traversa, son,
esprit.
b. Détruire l'élément qui se trouve à la racine et dessiner l'arbre dépliable résultant.
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/8/3a.html [07-06-2001 12:21:35]


Structuration des Données Informatiques - 8.4

8.4. Rééquilibrage d'arbres


Nous avons vu au début de ce chapitre la définition d'un arbre équilibré ainsi que les formules permettant
d'évaluer la profondeur d'un tel arbre équilibré. Pour les arbres de tri, nous avons aussi vu que la forme
finale de la structure dépendait non seulement des valeurs que l'on y mettait, mais aussi de l'ordre dans
lequel on mettait ces valeur dans l'arbre. Par exemple, la suite de nombres 6, 3, 1, 9, 7, 5, 10 nous
amènera à construire l'arbre de tri suivant :

Fig. 8.9. Données aboutissant à un arbre équilibré.


alors que la suite des mêmes nombres 9, 7, 6, 5, 10, 3, 1 produirait l'arbre suivant :

Fig. 8.9. Données aboutissant à un arbre très déséquilibré


Au pire, l'arbre peut dégénérer en une simple chaîne. Au vu de ces exemples, on se rend bien compte que
l'intérêt d'utiliser une structure d'arbre reflétant une relation d'ordre pour diminuer le nombre de
comparaisons à effectuer peut dépendre fortement de l'ordre dans lequel ces données sont traitées.
C'est la raison pour laquelle on envisage souvent d'appliquer des algorithmes de rééquilibrage aux
structures d'arbres. Nous n'en verrons toutefois pas ici car il existe d'autres structures, telles que les
b-arbres, qui ont des propriétés similaires aux structures d'arbres, mais qui, de par leurs algorithmes de
manipulation, ont la propriété supplémentaire de rester assez équilibrées.

http://cuisung.unige.ch/std/08.4.html (1 of 2) [07-06-2001 12:22:07]


Structuration des Données Informatiques - 8.4

9. Structures récursives non linéaires :


les listes
Table des matières.

http://cuisung.unige.ch/std/08.4.html (2 of 2) [07-06-2001 12:22:07]


Structuration des Données Informatiques - 9.0

9. Structures récursives non linéaires:


les listes
La seule propriété structurelle des chaînes est l'ordonnancement de ses éléments, tous de type identique.
On peut généraliser la notion de chaîne en élargissant la restriction d'identité de type des éléments de la
manière suivante :
Définition : Une liste est une chaîne non-homogène dont chaque élément contient soit une information
(atome), soit une autre liste (sous-liste).
La définition ci-dessus est récursive car le terme à définir (la liste) est utilisée à l'intérieur de la
définition. Cela peut sembler être un cercle vicieux, mais c'est en fait une façon très puissante de définir
et de décrire toutes les structures de listes possibles.
Exemple
A=()
liste vide (longueur 0).
B=(a, (b, c))
liste de longueur 2, le 1er élément est un atome "a", le 2ème élément est une liste "(b,
c)".
C=(B, B, ())
liste de longueur 3 dont les 2 premiers éléments sont identiques et correspondent
chacun à la liste B, le 3ème élément est la liste vide.
D=(a, D)
liste récursive de longueur 2. D correspond à la liste infinie (a, (a, (a, (. . . )))). Cette
possibilité de déclarer des listes récursives est souvent utilisée pour représenter la
grammaire d'un langage de programmation.
Déclaration

type Contenus = (Entier, Reel, Booleen,


Symbole, Liste);
VersObjet = ^Objet;
Objet = record
Suivant: VersObjet;
case Contenu: Contenus of
Entier : (ValEnt : integer);
Reel : (ValReel : real);
Booleen : (ValBool : boolean);
Symbole : (ValSym : string);
Liste : (Tete, Fin: VersObjet);
end; { Objet }

http://cuisung.unige.ch/std/09.0.html (1 of 3) [07-06-2001 12:22:11]


Structuration des Données Informatiques - 9.0

var Obj: Objet;


Cet exemple est extrait d'un interpréteur LOGO, qui est un langage où les listes sont des structures
privilégiées. Un programme LOGO est lui-même une liste. Dans cet exemple de déclaration, nous avons
volontairement déclaré une variable "Obj" de type "Objet", au lieu d'une variable "Debut" de type
"VersObjet" pour bien montrer la récursivité des déclarations : une liste est un objet qui contient une
suite d'objets qui, eux-mêmes, peuvent être des listes . . . Il va de soi que la déclaration d'une liste,
comme pour une chaîne, pourrait se limiter à une variable de type pointeur (VersObjet).
On peut envisager une variante qui mette plus en évidence la distinction entre atome et sous-liste :

type GenreAtomes = (Entier, Reel,


Booleen, Symbole);
Contenus = (Atome, Liste);
VersObjet = ^Objet;

Atomes = record
case Genre: GenreAtomes of
Entier: (ValEnt : integer);
Reel: (ValReel : real);
Booleen: (ValBool: boolean);
Symbole: (ValSym : string);
end; { Atomes }

Objet = record
Suivant: VersObjet;
case Contenu: Contenus of
Atome: (Valeur: Atomes);
Liste: (Tete, Fin: VersObjet);
end; { Objet }

var Obj: Objet;


Cette variante complique toutefois l'accès à la valeur d'un atome.
Description en terme de type abstrait :
● le rôle d'une liste est de stocker un nombre indéterminé d'éléments de types quelconques dans un
certain ordre. Un élément peut être lui-même une liste. L'ordre de placement des éléments dans la
liste peut dépendre de la chronologie d'insertion, du contenu des éléments insérés ou d'un
quelconque autre critère fixé par l'utilisateur.
● primitives de manipulation :

On peut reprendre quasi in-extenso les primitives énumérées pour les chaînes en rajoutant un
prédicat permettant de savoir si un élément est un atome ou une liste.

http://cuisung.unige.ch/std/09.0.html (2 of 3) [07-06-2001 12:22:11]


Structuration des Données Informatiques - 9.0

9.1. Operations sur les listes

Table des matières.

http://cuisung.unige.ch/std/09.0.html (3 of 3) [07-06-2001 12:22:11]


Structuration des Données Informatiques - 9.1

9.1. Opérations sur les listes


● insertion : en début, en fin.
● extraction : du premier élément (car), de tout sauf le premier élément (cdr).
● construction : combinaison d'un élément et d'une liste.
● comparaison : = ou <>.
● prédicat de liste vide : revient à comparer à la liste vide.
L'insertion est similaire à ce que l'on a vu pour les chaînes. A noter que le pointeur "Fin" est là pour
accélérer les insertions en fin de liste. Comme pour les chaînes, il est possible de construire des variantes
à la structure de liste, en rendant la liste circulaire et/ou bidirectionnelle. Ceci apportera les mêmes
avantages que pour les chaînes, c'est-à-dire une plus grande facilité pour effectuer certaines opérations
telles que insertion avant un élément donné, suppression du dernier élément, . . .
"car" et "cdr" sont, par des raisons historiques, les abrégés, respectivement, de "Content of A Register" et
"Content of D Register". "car" fournit un atome si le premier élément est un atome, et une liste si le
premier élément est une sous-liste. "cdr" fournit toujours une liste correspondant à la liste de départ
moins le premier élément. "car" ou "cdr" d'une liste vide donnent un résultat indéterminé (en LISP cela
donne à nouveau la liste vide).
La combinaison de "car" et "cdr" permet d'isoler n'importe quel élément d'une liste. Pour abréger, on ne
note souvent que les lettres du milieu (a ou d), le tout entouré de c et r.
Exemple
car(cdr(cdr(L))) peut s'abréger caddr(L)
L'opération de construction consiste à créer une nouvelle liste à partir d'un élément et d'une liste de façon
à avoir la relation suivante :
L = cons( car(L), cdr(L) )
La comparaison de deux listes tient compte des niveaux d'imbrication des sous-listes ainsi que du nombre
d'éléments. Ainsi, (a) est différent de (a ()) et de ((a)) , de même (()) est différent de ().
Exemple d'algorithme de comparaison (cet algorithme utilise la première variante de déclaration vue au
début de ce chapitre) :

function Egale(Obj1,Obj2:Objet): boolean;

function ListesEgales(Tete1,
Tete2:VersObjet)
: boolean;
begin { ListesEgales }
if (Tete1=nil) and (Tete2=nil) then
ListesEgales := true
else if (Tete1=nil) or
(Tete2=nil) then

http://cuisung.unige.ch/std/09.1.html (1 of 2) [07-06-2001 12:22:40]


Structuration des Données Informatiques - 9.1

ListesEgales := false
else ListesEgales:=
Egale(Tete1^, Tete2^) and
ListesEgales(Tete1^.Suivant,
Tete2^.Suivant);
end; { ListesEgales }

begin { Egale }
if Obj1.Contenu<>Obj2.Contenu then
Egale := false
else case Obj1.Contenu of
Entier: Egale:= (Obj1.ValEnt =
Obj2.ValEnt);
Reel: Egale:= (Obj1.ValReel =
Obj2.ValReel);
Booleen: Egale:= (Obj1.ValBool =
Obj2.ValBool);
Symbole: Egale:= (Obj1.ValSym =
Obj2.ValSym);
Liste: Egale:= ListesEgales
(Obj1.Tete,Obj2.Tete);
end; { case }
end; { Egale }
Il est clair que le parcours des éléments de listes dans la fonction "ListesEgales" pourrait se faire sans
utiliser la récursivité.

9.2. Détermination de l'appartenance à une liste

Table des matières.

http://cuisung.unige.ch/std/09.1.html (2 of 2) [07-06-2001 12:22:40]


Structuration des Données Informatiques - 9.1, exercice 1

Exercice suivant

Listes
Question posée à l'examen du 11 octobre 1999
Indiquez ligne après ligne ce qu'imprime le programme suivant:
program listes;
var gDe,gA:integer;

procedure compter1(inDe,inA : integer);


begin
if(inDe<inA) then begin
writeln(inDe);
compter1(inDe+1,inA);
end; (* if *)
end;

procedure compter2(inDe,inA : integer);


begin
if(inDe<inA) then begin
compter2(inDe+1,inA);
writeln(inDe);
end; (* if *)
end;

procedure compter3(inDe,inA : integer);


begin
if(inDe<inA) then begin
writeln(inDe);
compter3(inDe+1,inA);
writeln(inDe);
end; (* if *)
end;

procedure compter4(var inDe,inA : integer);


begin
if(inDe<inA) then begin
writeln(inDe);
inDe:=inDe+1;
compter4(inDe,inA);
writeln(inDe);
end; (* if *)
end;

begin

http://cuisung.unige.ch/std/ex/9/1a.html (1 of 2) [07-06-2001 12:22:42]


Structuration des Données Informatiques - 9.1, exercice 1

gDe:=1;
gA:=4;
writeln('Avant compter1');
compter1(gDe,gA);

gDe:=1;
gA:=4;
writeln('Avant compter2');
compter2(gDe,gA);

gDe:=1;
gA:=4;
writeln('Avant compter3');
compter3(gDe,gA);

gDe:=1;
gA:=4;
writeln('Avant compter4');
compter4(gDe,gA);
end.
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/9/1a.html (2 of 2) [07-06-2001 12:22:42]


Structuration des Données Informatiques - 9.2

9.2. Détermination de l'appartenance à une liste


Si nous reprenons l'exemple de l'interpréteur LOGO, nous avons dit qu'un programme LOGO était stocké
sous forme de liste. Avec un tel programme LOGO contenant une constante de type "liste", cette
constante apparaîtra, dans la liste constituant le programme, comme une sous-liste.
Exemple :
REPEAT 4 [PRINT "HELLO]
peut être représenté de la manière suivante

Fig.9.1. Représentation d'instructions LOGO sous forme de listes


L'interpréteur LOGO gère, pour l'exécution du programme, une pile d'objets sur laquelle il met les
paramètres des procédures et des instructions qu'il est en train d'interpréter (on peut considérer les
instructions du langage comme des procédures prédéfinies). La syntaxe du LOGO est telle qu'il n'est pas
possible, à l'intérieur d'une procé dure, de modifier les arguments qui lui sont passés en paramètre. Il est
donc plus efficace de fournir aux procédures l'adresse des objets qui leurs sont passés en paramètres
plutôt qu'une copie de ces objets. La pile de l'interpréteur est donc organisée comme une pile de
pointeurs plutôt que comme une pile d'objets.
Il n'y aurait pas de problème si les procédures ne recevaient comme paramètres que des constantes ou des
variables simples (auquel cas il n'y aurait rien à détruire). Les choses se compliquent du fait que les
paramètres spécifiés dans l'appel à une procédure peuvent être des expressions. Les résultats de ces
expressions sont calculés à l'exécution, lors de l'activation de la procédure et constituent donc des objets
qui devront être détruits lors de la terminaison de la procédure (contrairement aux autres objets passés en
paramètre).
Exemple :
REPEAT SUM 2 2 [PRINT "HELLO]
peut être représenté de la manière suivante :

http://cuisung.unige.ch/std/09.2.html (1 of 2) [07-06-2001 12:22:52]


Structuration des Données Informatiques - 9.2

Fig. 9.2. Illustration de la pile d'activation utilisée par l'interprète.


L'illustration ci-dessus montre l'état des données de l'interpréteur lors de l'activation de l'instruction
REPEAT. Nous voyons donc que, lorsque l'instruction REPEAT sera terminée, les divers éléments de la
pile d'activation ne devront pas tous subir le même traitement. La sous-liste [PRINT "HELLO] ne devra
pas être détruite alors que l'objet "4" devra être éliminé.
Si l'on regarde la déclaration du type "Objet", on remarque que le champ "Suivant" n'est utilisé que si
l'objet en question fait partie d'une liste. Nous pouvons donc utiliser ce champ pour indiquer que l'objet
ne fait pas partie d'une liste. Il nous faut donc une autre valeur que la constante prédéfinie "nil" qui se
distingue des valeurs légales de pointeurs. La solution consiste à déclarer une variable :
var Sentinelle: VersObjet;
qui sera initialisée au début de l'exécution de l'interpréteur. L'objet pointé par cette variable ne sera
jamais utilisé, mais son adresse servira de constante permettant de distinguer les objets "destructibles"
des autres. Autrement dit, tout objet dont le pointeur Suivant a la même valeur que le pointeur Sentinelle
est considéré comme pouvant être détruit.

9.3. Partage d'élément entre listes

Table des matières.

http://cuisung.unige.ch/std/09.2.html (2 of 2) [07-06-2001 12:22:52]


Structuration des Données Informatiques - 9.3

9.3. Partage d'éléments entre listes


Lorsque l'on a plusieurs listes à gérer simultanément et que ces listes peuvent contenir des éléments en
commun, se pose le problème de traiter ce partage si l'on veut éviter de simplement duplifier
l'information à mettre en commun. L'approche la plus souple et qui ne limite pas le nombre de listes
auxquelles un élément peut appartenir consiste à mettre le contenu des atomes en dehors de la structure
de liste. Chaque noeud de la liste pointe soit vers une sous-liste, soit vers un atome. Cela peut s'illustrer
de la manière suivante :

Fig. 9.3 Partage d'éléments par plusieurs listes


Dans l'exemple ci-dessus, les deux listes ont deux éléments en commun : un atome et une sous-liste.
Le problème principal lié à cette approche a trait à la suppression d'éléments dans une des listes. En effet,
la suppression d'un élément d'une liste ne doit s'accompagner de la destruction de cet élément que si cet
élément n'est pas partagé par une autre liste. Dans le même ordre d'idée, la destruction d'une liste ne doit
impliquer que la destruction des éléments de cette liste qui ne sont pas partagés par d'autres.
Pour ce faire, il faut associer à chaque objet susceptible d'être partagé (atome ou sous-liste) un compteur
de références qui indique le nombre de références à cet objet. Chaque fois qu'un objet est supprimé d'une
liste, son compteur de références est décrémenté. Seul un objet dont le compteur de références est à zéro
sera effectivement détruit.
La solution consistant à gérer un compteur de références n'est pas toujours suffisante. Dans le cas de
listes récursives, le compteur de références de la liste ne sera jamais à zéro puisque la liste fait référence

http://cuisung.unige.ch/std/09.3.html (1 of 2) [07-06-2001 12:23:22]


Structuration des Données Informatiques - 9.3

à elle-même. Si le cas de listes récursives peut se présenter, il faudra, lors du ramassage des miettes,
parcourir à nouveau toutes les listes existantes et marquer les objets référencés pour ensuite dé truire tous
les objets qui n'ont pas été marqués

9.4. Isomorphisme liste-arbre

Table des matières.

http://cuisung.unige.ch/std/09.3.html (2 of 2) [07-06-2001 12:23:22]


Structuration des Données Informatiques - 9.4

9.4. Isomorphisme liste-arbre


Il est possible de représenter des listes à l'aide de structures d'arbres et vice-versa. Les deux structures ont
en effet des caractéristiques suffisamment proches pour cela. Cela n'a bien entendu d'intérêt que si l'une
de ces deux structures est privilégiée par le langage de programmation utilisé.
Pour représenter une liste sous forme d'arbre, on utilise généralement une structure d'arbre binaire dont
les noeuds non-terminaux ne comportent pas d'informations particulières. La transformation que l'on peut
adopter sera de mettre le premier élément de la liste (car) comme sous-arbre gauche de la racine et le
reste de la liste (cdr) comme sous-arbre droit de la racine. Le processus est réitéré pour chaque morceau
de liste qui reste. Un atome de la liste deviendra une feuille de l'arbre et une sous-liste deviendra un
sous-arbre droit (algorithme récursif).
Exemple

Fig. 9.4. Transformation d'une liste en arbre binaire.


Il serait possible de représenter une liste sous forme d'arbre multiple. Toutefois, comme on l'a vu au
chapitre sur les structures d'arbre, il est difficile de représenter un arbre multiple dont on ne connaît pas
par avance le degré.
Pour représenter un arbre multiple sous forme de liste, on peut adopter la convention selon laquelle le
contenu du noeud racine est mis en première position de la liste et chacun de ses sous-arbres constituera
une sous-liste de la liste principale (algorithme récursif).
Exemple

Fig. 9.5. Transformation d'un arbre multiple en liste.


Pour représenter un arbre binaire ordonné sous forme de liste, on peut d'une manière similaire adopter la
convention selon laquelle le contenu du noeud racine est mis en première position de la liste et chacun de
ses sous-arbres constituera une sous-liste de la liste principale (algorithme récursif). Si un sous-arbre est

http://cuisung.unige.ch/std/09.4.html (1 of 2) [07-06-2001 12:23:30]


Structuration des Données Informatiques - 9.4

inexistant, on lui fera correspondre une liste vide.


Exemple

Fig. 9.6. Transformation d'un arbre binaire en liste.

10. Structures récursives non linéaires


: les graphes
Table des matières.

http://cuisung.unige.ch/std/09.4.html (2 of 2) [07-06-2001 12:23:30]


Structuration des Données Informatiques - 10.

10. Structures récursives non linéaires:


les graphes
Beaucoup de problèmes sont naturellement formulés en termes d'objets et de connexions entre ces objets.
Ces "connexions" peuvent aussi bien être réelles (liens physiques) que virtuelles (p.e. relation d'ordre).
Par exemple, pour un réseau routier, on peut se demander :
Quel est le chemin le plus rapide pour aller de Genève à Nantes ?
Quelle est la manière la plus économique d'aller de Genève à Nantes ?
Pour un circuit électrique :
Tous les éléments sont-ils interconnectés ?
Si un tel circuit est construit, fonctionnera-t-il ?
Pour l'ordonnancement de tâches d'un processus :
Est-ce que toutes les tâches du processus de fabrication seront exécutables? Dans quel ordre?
Un graphe est un objet mathématique qui modélise parfaitement de telles situations.

10.1. Nomenclature

Table des matières.

http://cuisung.unige.ch/std/10.0.html [07-06-2001 12:23:32]


Structuration des Données Informatiques - 10.1

10.1. Nomenclature
Un graphe est une collection de sommets et d'arcs. Un sommet est un objet simple qui peut avoir un nom
et d'autres propriétés. Un arc est une connexion entre deux sommets. Il existe plusieurs manières de
représenter les graphes. Entre autres, sous forme graphique ou sous forme de listes de sommets et d'arcs.
La représentation graphique d'un graphe n'est toutefois utilisable que par un humain et uniquement si le
graphe est peu dense ou contient relativement peu de sommets. La figure 10.1 ci-dessous en illustre un
exemple :

Un chemin du sommet x au sommet y est une liste de sommets dans laquelle chaque sommet successif
est relié au précédent par un arc.
Un graphe est connexe s'il existe un chemin de chaque sommet vers chaque autre sommet; sinon, il est
formé de composantes connexes.
Un chemin simple est un chemin dans lequel aucun arc n'est répété.
Un cycle est un chemin simple ayant le même sommet aux deux extrémités. Il est à noter que dans
l'exemple ci-dessus, le chemin AGA n'est pas considéré comme un cycle puisque ce n'est pas un chemin
simple.
Si un graphe, à S sommets, a moins de S-1 arcs il ne peut pas être connexe; s'il a plus de S-1 arcs, il doit
avoir un cycle.
Les graphes tels que définis jusqu'ici sont des graphes non-orientés. Un graphe est orienté si on associe
un sens unique à chaque arc.
Un graphe est pondéré si, à chaque arc, on associe une valeur représentant par exemple une distance, un
coût, un débit, etc. Les graphes orientés et pondérés sont appelés des réseaux.
Description en terme de types abstraits :

http://cuisung.unige.ch/std/10.1.html (1 of 2) [07-06-2001 12:24:06]


Structuration des Données Informatiques - 10.1

● le rôle d'un graphe non-orienté est de stocker un ensemble d'objets (sommets) entre lesquels
existent des liens (arcs). Un lien est défini par la paire de sommets qu'il relie.
● la déclaration d'un graphe doit spécifier le contenu des sommets.
● primitives de manipulation :
1. initialiser un graphe à vide.
2. ajouter un sommet à un graphe (indiquer le nom et le contenu du sommet).
3. ajouter un arc à un graphe (indiquer les deux sommets qu'il relie).
4. ôter un sommet du graphe et tous les arcs qui y sont rattachés.
5. ôter un arc.
6. permettre l'accès à un sommet, étant donné son nom.
7. parcourir les voisins d'un sommet.
8. indiquer si un sommet n'a pas de voisins

10.2. Représentation

Table des matières.

http://cuisung.unige.ch/std/10.1.html (2 of 2) [07-06-2001 12:24:06]


Structuration des Données Informatiques - 10.2

10.2. Représentation
On considérera deux principales méthodes de représentation des graphes : les matrices et les listes de
connectivité. Le choix entre elles se fera en fonction de la densité du graphe.
Pour ces deux méthodes, il faut pouvoir faire correspondre au nom d'un sommet une valeur entière
unique entre 1 et NbSommets. Ceci afin d'accélérer l'accès à l'information propre à un sommet :
● par "hash-coding" (cf. chapitre 13)

● par "arbre binaire de tri" : les sommets et leur information associée sont stockés dans un arbre
avec, en plus, un numéro que chaque sommet reçoit au moment où ce sommet est rencontré pour la
première fois.
Pour simplifier, nous considérerons dans les exemples qui suivent des sommets dont les noms sont
formés d'un seul caractère. Ainsi, à la i-ème lettre de l'alphabet, l'on fera correspondre l'entier i.

10.2.1. Matrice de connectivité


Une matrice de valeurs booléennes (Relie) de dimension NbSommets * NbSommets indique qu'il y a un
arc entre un sommet x et un sommet y si Relie[x,y] est vrai. Il est à remarquer que chaque arc est
représenté deux fois (Relie[x,y] et Relie[y,x]). On peut ainsi, pour un graphe non-orienté, économiser de
l'espace en ne stockant que le triangle supérieur de la matrice symétrique.
Cette méthode de représentation est indiquée si le graphe est dense car il faut NbSommets2 booléens pour
stocker le graphe et NbSommets2 étapes pour initialiser la matrice. Par densité, on entend la proportion
d'arcs effectifs par rapport au nombre maximum d'arcs possibles (NbArcs / NbSommets2).
Pour le graphe que nous avons déjà vu auparavant dans la figure 10.1, la matrice de connectivité (Relie)
correspondante est donnée par la figure 10.2.

A B C D E F G H I J K L M
A f v v f f v v f f f f f f
B v f f f f f f f f f f f f
C v f f f f f f f f f f f f
D f f f f v v f f f f f f f
E f f f v f v v f f f f f f
F v f f v v f f f f f f f f
G v f f f v f f f f f f f f
H f f f f f f f f v f f f f
I f f f f f f f v f f f f f
J f f f f f f f f f f v v v
K f f f f f f f f f v f f f
L f f f f f f f f f v f f v
M f f f f f f f f f v f v f
Fig. 10.2. Matrice de connectivité.

http://cuisung.unige.ch/std/10.2.html (1 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

Cette matrice peut être construite à l'aide de l'algorithme suivant

program MatriceGraphe;

const MaxNbSommets = ...;

type UnSommet = ...;

var j, x, y, NbSommets, NbArcs : integer;


Sommet1, Sommet2: UnSommet;
Relie : array[1..MaxNbSommets, 1..MaxNbSommets] of boolean;
Sommets : array[1..MaxNbSommets] of UnSommet;

function Index(Sommet: UnSommet):integer;


{ fonction qui associe une valeur numérique entière
à un objet de type "UnSommet" et qui stocke le "Sommet"
dans le tableau "Sommets" si ce sommet ne s'y trouvait pas déjà. }
begin
...
end; { Index }

begin { MatriceGraphe }
{ lecture du nombre de données }
readln(NbSommets,NbArcs);
{ initialisation de la matrice }
for x := 1 to NbSommets do
for y := 1 to NbSommets do
Relie[x,y] := false;
for j := 1 to NbArcs do begin
{ pour chaque arc, on lit les sommets
de départ et d'arrivée }
readln (Sommet1,Sommet2);
x := Index (Sommet1);
y := Index (Sommet2);
{ crée un arc dans chaque direction }
Relie[x,y] := true;
Relie[y,x] := true;
end; { for }
end. { MatriceGraphe }

10.2.2. Liste de connectivité


Si le graphe n'est pas dense, on peut opter pour un autre mode de représentation : celui de listes de
connectivité (il s'agit en fait de chaînes) dont les éléments sont de la forme :

http://cuisung.unige.ch/std/10.2.html (2 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

type Ref = ^Noeud;

Noeud = record
IndexVoisin : integer;
Suivant : Ref;
end; { Noeud }
Un vecteur de NbSommets composantes contient les pointeurs de tête de chaque liste :

L'algorithme de lecture et de construction de cette structure est le suivant :

program ChaineGraphe;

const MaxNbSommets = ... ;

type UnSommet = ...;


Ref = ^Noeud;
Noeud = record
IndexVoisin : integer;
Suivant : Ref;
end; { Noeud }

var j, x, y, NbSommets, NbArcs: integer;


Voisin: Ref;
Sommet1, Sommet2: UnSommet;
PremierVoisin: array[1..MaxNbSommets] of Ref;
Sommets : array[1..MaxNbSommets] of UnSommet;

function Index(Sommet: UnSommet): integer;


begin
...
end; { Index }

http://cuisung.unige.ch/std/10.2.html (3 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

begin
{ lecture du nombre de données }
readln (NbSommets, NbArcs);
{ initialisation }
for j := 1 to NbSommets do
PremierVoisin[j] := nil;

{ pour chaque arc lit sommet de départ


et d'arrivée }
for j := 1 to NbArcs do begin
readln (Sommet1,Sommet2);
x := Index (Sommet1);
y := Index (Sommet2);

{ crée l'arc de y vers x }


new(Voisin);
Voisin^.IndexVoisin := x;
Voisin^.Suivant := PremierVoisin[y];
PremierVoisin[y] := Voisin;

{ crée l'arc de x vers y }


new(Voisin);
Voisin^.IndexVoisin := y;
Voisin^.Suivant := PremierVoisin[x];
PremierVoisin[x] := Voisin;

end; { for }
end. { ChaineGraphe }
Ce qui produit dans notre exemple la structure de données suivante :
Liste
A
:FCGB
B
:A
C
:A
D
:FE
E
:GDF
F
:EDA

http://cuisung.unige.ch/std/10.2.html (4 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

G
:AE
H
:I
I
:H
J
:KLM
K
:J
L
:MJ
M
:JL
Fig. 10.4. Listes de connectivité correspondant au graphe de la figure 10.1.

A noter que les arcs apparaissent deux fois. Par exemple B se trouve dans la liste de connectivité de A et
A se trouve dans la liste de connectivité de B.
Si le langage utilisé admet les structures paquetées, l'occupation mémoire d'une matrice de connectivité
est inférieure à l'occupation mémoire de la liste de connectivité correspondante dès que la densité des
arcs dépasse 3%. En effet, il faut 1 bit par arc pour une matrice de connectivité et généralement 32 bits
par arc pour la liste de connectivité correspondante (1 pointeur+1 numéro de sommet).
En fait, ici, c'est plus le nombre d'étapes d'initialisation et de traitement que l'occupation en mémoire qu'il
faut prendre en considération pour choisir entre les deux types de structures. La structure de liste
nécessite moins d'étapes d'initialisation et de traitement mais cet avantage devient de moins en moins
déterminant au fur et à mesure que la densité des arcs augmente. Il y a donc un compromis à trouver
entre gain de temps et gain de place.
L'utilisation de listes présente une particularité que la matrice de connectivité n'avait pas; en effet, ce qui
caractérise une chaîne de connectivité, c'est l'ordre dans lequel les éléments apparaissent dans la liste. Par
conséquent, quel que soit l'ordre des éléments, donc quelle que soit la façon dont on voit le graphe, il faut
que les algorithmes qui manipulent ces représentations produisent des résultats équivalents.
Des opérations simples ne sont pas simplement réalisables sur ce type de représentation. Par exemple
l'élimination d'un sommet doit se faire dans toutes les listes. On peut partiellement y remédier en
rajoutant à chaque élément de la liste de connectivité d'un sommet un pointeur vers l'élément d'une autre
liste de connectivité qui décrit le même arc. Par exemple, l'élément référençant B dans la liste de
connectivité de A aura un pointeur vers l'élément référençant A de la liste de connectivité de B car, dans
les deux cas, il s'agit de l'arc AB :

http://cuisung.unige.ch/std/10.2.html (5 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

10.2.3. Structure entièrement dynamique


Les deux types de représentation de graphes vus précédemment sont les plus couramment utilisés. En
effet, même si l'on ne connaît pas à l'avance le nombre de sommets d'un graphe, la plupart des langages
de programmation offrent la possibilité de déclarer des tableaux conformants (tableaux dont les bornes
d'indices ne sont pas fixées à la compilation). Il est ainsi possible d'ajuster la taille de la structure statique
(matrice de booléens ou tableau de pointeurs) en fonction des données du problème. Cela ne permet
cependant pas d'augmenter facilement le nombre de sommets en cours d'exécution si cela s'avérait
nécessaire pour les besoins de l'application.
L'on peut toutefois envisager d'utiliser une structure entièrement dynamique en créant une chaîne
décrivant les sommets du graphe, où chaque élément contiendrait le nom d'un sommet, un pointeur vers
le sommet suivant (dans l'ordre alphabétique, par exemple) ainsi qu'un pointeur vers une chaîne décrivant
les arcs partant de ce sommet. On aurait ainsi une chaîne des sommets et, pour chaque élément de cette
chaîne, une chaîne des arcs. Cela pourrait donner la structure suivante :

Cet exemple indique qu'il y a deux arcs partant du sommet Nom1, (dont un vers Nom2), trois arcs partant

http://cuisung.unige.ch/std/10.2.html (6 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

de Nom2, ... et un arc partant de NomN. Cette structure peut être construite à l'aide des déclarations et de
l'algorithme suivants :

type VersSommet = ^Sommets;


VersArc = ^Arcs;
Sommets = record
Nom: string;
PremierArc: VersArc;
SommetSuivant: VersSommet;
end; { Sommets }
Arcs = record
PointeVers: VersSommet;
ArcSuivant: VersArc;
end; { Arcs }

var Graphe: VersSommet;

procedure LireSommet;
var Nom: string; P:VersSommet;
begin
read(Nom);
while Nom <> 'FIN' do begin
new(P);
P^.Nom := Nom;
P^.PremierArc := nil;
P^.SommetSuivant := Graphe;
Graphe := P;
readln; read(Nom);
end; { while }
readln;
end; { LireSommet}

procedure LireArcs;
var Dep, Arr: VersSommet;
A: VersArc;
Depart, Arrivee: string;
begin
while not eof(input) do begin
readln(Depart, Arrivee);
{ recherche des 2 sommets dans la
chaîne des sommets }
P := Graphe;
Arr := nil;
Dep := nil;
repeat

http://cuisung.unige.ch/std/10.2.html (7 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.2

if P = nil then Erreur


else if P^.Nom = Depart then Dep:=P
else if P^.Nom=Arrivee then Arr:=P;
P := P^.SommetSuivant
until (Arr <> nil) and (Dep <> nil);
new(A);
A^.PointeVers := Arr;
A^.ArcSuivant := Dep^.PremierArc;
Dep^.PremierArc := A;
end; { while }
end; { LireArcs }
Au besoin, il est possible de remplacer, dans l'exemple qui précède, la chaîne des sommets par un arbre
binaire de tri. Les sommets deviennent ainsi plus rapidement localisables, au cas où l'on désire y accéder
sans passer par un arc.

10.3. Recherche en profondeur

Table des matières.

http://cuisung.unige.ch/std/10.2.html (8 of 8) [07-06-2001 12:24:21]


Structuration des Données Informatiques - 10.3

10.3. Recherche en profondeur


La méthode de recherche en profondeur d'abord ("depth-first search") est la manière naturelle de parcourir un
graphe, de visiter chaque sommet et d'emprunter chaque arc de manière systématique. De plus elle permet de
répondre aux questions qui ont été soulevées en début de ce chapitre :
● Un graphe est-il connexe ?

● S'il ne l'est pas, quelles sont ses composantes ?

● A-t-il des cycles ?

L'algorithme de recherche en profondeur se présente de la manière suivante : Comme son nom l'indique, l'on
part d'un sommet et l'on essaie de s'éloigner le plus possible de ce sommet en cheminant le long des arcs. Si
l'on rencontre un cul de sac (sommet n'ayant pas de voisin non encore visité), on revient en arrière et on repart
en avant à la première occasion.
L'implantation de cette méthode que nous allons illustrer plus loin utilise un vecteur
NumeroDOrdre[i], i [1,NbSommets],
qui indique, pour chaque sommet, avec quel numéro d'ordre il a été visité. Si NumeroDOrdre[j] = 0, cela
indique que le sommet j n'a pas encore été visité.
Le programme suivant utilise une procédure récursive pour le parcours du graphe à partir d'un sommet donné;
c'est la procédure "Visite". Visiter un sommet consiste à déterminer si, parmi les arcs qui y sont rattachés, il en
existe qui mènent à des sommets pas encore visités. Si c'est le cas, l'on progresse le long d'un de ces arcs et, si
cela n'aboutit pas, l'on progressera alors le long d'un autre de ces arcs.

procedure DFS( NbSommets: integer );

{ Algorithme de parcours d'un graphe en profondeur avec représentation


par liste de connectivité.
Les déclarations globales ainsi que la lecture du graphe sont
décrites au paragraphe 10.2.2 }

var CmptrVisite,
IndexSommet : 0..MaxNbSommets;
NumeroDOrdre: array[1..MaxNbSommets] of integer;

procedure Visite (IndexSommet : integer);


var Voisin : Ref;
begin
CmptrVisite := CmptrVisite + 1;
NumeroDOrdre[IndexSommet]:=CmptrVisite;
Voisin := PremierVoisin[IndexSommet];
while Voisin <> nil do begin
if NumeroDOrdre[Voisin^.IndexVoisin]=0 then
Visite (Voisin^.IndexVoisin);
Voisin := Voisin^.Suivant;
end; { while }

http://cuisung.unige.ch/std/10.3.html (1 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3

end; { Visite }

begin { DFS }
CmptrVisite := 0;
for IndexSommet:= 1 to NbSommets do
NumeroDOrdre[IndexSommet]:=0;
for IndexSommet := 1 to NbSommets do
if NumeroDOrdre[IndexSommet] = 0 then
Visite (IndexSommet)
end; { DFS }
La meilleure manière de suivre la progression de l'algorithme dans le parcours d'un graphe donné est de
construire l'arbre des appels récursifs à la procédure Visite. On obtient ainsi un arbre unique ou une forêt dont
chaque composante est appelée arbre de recherche en profondeur :

Les traits pleins dans cette figure indiquent que les sommets de niveau inférieur ont été trouvés par
l'algorithme comme faisant partie de la liste de connectivité du sommet de niveau supérieur et n'avaient pas
encore été visités; c'est donc par un appel récursif à la procédure Visite qu'il sont été atteints.
Les traits en pointillés correspondent à des arcs vers des sommets qui avaient déjà été visités; donc le test if ...
then de la procédure Visite s'était révélé négatif et la récursivité s'était arrêtée là.
Pour un graphe représenté par une liste de connectivité, les structures de données produites sont :

IndexSommet 1 2 3 4 5 6 7 8 9 10 11 12 13
nom(IndexSommet) A B C D E F G H I J K L M
NumeroDOrdre[IndexSommet] 1 7 6 5 3 2 4 8 9 10 11 12 13
La complexité en temps de la procédure DFS, pour une représentation du graphe par liste de connectivité, est
proportionnelle à NbSommets + NbArcs.
Si le graphe est représenté par une matrice de connectivité, on obtient un algorithme similaire où seule la
procédure Visite a changé pour tenir compte de la différence de structure de donnée utilisée. Cet algorithme se
présente de la manière suivante :

http://cuisung.unige.ch/std/10.3.html (2 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3

procedure DFS( NbSommets: integer );

{ Algorithme de parcours en profondeur d'un graphe non-orienté


avec représentation par matrice de connectivité.
Les déclarations globales ainsi que la lecture du graphe sont
décrites au paragraphe 10.2.1 }

var CmptrVisite,
IndexSommet : 0..MaxNbSommets;
NumeroDOrdre: array[1..MaxNbSommets] of integer;

procedure Visite (IndexSommet : integer);


var AutreSommet : integer;
begin
CmptrVisite := CmptrVisite + 1;
NumeroDOrdre[IndexSommet]:=CmptrVisite;
for AutreSommet := 1 to NbSommets do
if Relie[IndexSommet,AutreSommet] and
{ la condition qui suit permet d'éviter de boucler au cas où
il y aurait un cycle dans le graphe ou, tout simplement pour
éviter d'utiliser à nouveau un arc déjà parcouru en sens inverse }
(NumeroDOrdre[AutreSommet]=0) then
Visite (AutreSommet);
end; { Visite }

begin { DFS }
CmptrVisite := 0;
for IndexSommet:= 1 to NbSommets do
NumeroDOrdre[IndexSommet]:=0;
for IndexSommet := 1 to NbSommets do
if NumeroDOrdre[IndexSommet] = 0 then
Visite (IndexSommet)
end; { DFS }
La forêt de parcours que l'on obtient avec cet algorithme est légèrement différente de celle obtenue pour les
listes de connectivité puisque les arcs partant d'un même sommet sont éventuellement traités dans un ordre
différent.

http://cuisung.unige.ch/std/10.3.html (3 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3

La complexité en temps des DFS pour une représentation par matrice de connectivité est alors :
t + O(NbSommets ),
ce qui confirme le choix intuitif qui avait été conseillé pour la matrice pour des graphes denses dans la mesure
où la complexité en temps est indépendante du nombre d'arcs.

10.3.1. Détection de cycles


Un graphe a peut-être un cycle si l'on découvre dans la procédure "Visite" une composante non nulle dans le
tableau NumeroDOrdre. Il faut donc pouvoir faire la distinction entre un cycle (par exemple A-B-C-D-A) et le
parcours en sens inverse d'un arc déjà traité (par exemple A-B-A). Pour cela, il suffit de fournir un paramètre
supplémentaire à la procédure Visite indiquant l'index du sommet précédant le sommet courant. Cela donne la
variante suivante :

procedure DFS( NbSommets: integer );

{ Algorithme de parcours en profondeur d'un graphe non-orienté


avec représentation par liste de connectivité.
Version modifiée pour détecter les cycles. }

var CmptrVisite,
IndexSommet : 0..MaxNbSommets;
NumeroDOrdre: array[1..MaxNbSommets] of integer;

procedure Visite (IndexSommet,


Precedent : integer);
var Voisin : Ref;
begin
CmptrVisite := CmptrVisite + 1;
NumeroDOrdre[IndexSommet]:=CmptrVisite;
Voisin := PremierVoisin[IndexSommet];
while Voisin <> nil do

http://cuisung.unige.ch/std/10.3.html (4 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3

with Voisin^ do begin


if NumeroDOrdre[IndexVoisin]=0 then
Visite(IndexVoisin,IndexSommet)
else if IndexVoisin<>Precedent then (* cycle détecté *);
Voisin := Suivant;
end; { whith }
end; { Visite }

begin { DFS }
CmptrVisite := 0;
for IndexSommet:= 1 to NbSommets do
NumeroDOrdre[IndexSommet]:=0;
for IndexSommet := 1 to NbSommets do
if NumeroDOrdre[IndexSommet] = 0 then
Visite (IndexSommet,0)
end; { DFS }

10.3.2. Détermination des composantes connexes


Dans les deux algorithmes de parcours en profondeur vus au paragraphe 3 (pour les deux principales
représentations d'un graphe), chaque appel non récursif à la procédure "Visite" détermine une nouvelle
composante connexe du graphe. Nous allons voir ici une variante de l'algorithme DFS qui énumère, pour
chaque sommet du graphe, les sommets vers lesquels il existe un chemin, c'est-à-dire la composante connexe
dont il fait partie :

procedure SommetsAccessibles(NbSommets: integer);

{ cette procédure énumère, pour chaque sommet du graphe,


les sommets vers lesquels il existe un chemin }

var IndexSommet,
j : 0..MaxNbSommets;
AVisiter : array[1..MaxNbSommets] of boolean;

procedure Visite (IndexSommet : integer);


{ cette procédure va parcourir toute une composante connexe }
var AutreSommet : integer;
begin
AVisiter[IndexSommet] := false;
write(Nom(IndexSommet));
for AutreSommet :=1 to NbSommets do
if Relie[IndexSommet,AutreSommet] and AVisiter[AutreSommet] then
Visite (AutreSommet);
end; { Visite }

begin { SommetsAccessibles }
for IndexSommet:=1 to NbSommets do begin
for j := 1 to NbSommets do AVisiter[j] := true;

http://cuisung.unige.ch/std/10.3.html (5 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3

Visite (IndexSommet)
writeln;
end; { for }
end; { SommetsAccessibles }

10.4. Recherche en largeur

Table des matières.

http://cuisung.unige.ch/std/10.3.html (6 of 6) [07-06-2001 12:24:49]


Structuration des Données Informatiques - 10.3, exercice 1

exercice suivant

Structures de graphes
Question posée à l'examen écrit du 13 octobre 1997
En s'inspirant de la description de graphes non-orientés en termes de type abstrait vue au cours, nous
allons supposer que les types "Graphe" et "SerieDeSommets" ont été définis, sans que vous ne
connaissiez les détails de ces structures. Nous allons aussi supposer que les primitives suivantes sont à
votre disposition:
function Index(NomSommet: string; LeGraphe: Graphe): integer;
{ retourne un numero compris entre 1 et MaxNbSommets }
function NomSommet(IndexSommet: integer; LeGraphe: Graphe)
:string;
{ retourne le nom du sommet dont l'index est fourni }
function NombreDeSommets(LeGraphe: Graphe): integer;
{retourne le nombre de sommets effectifs composant le graphe}
function SommetsVoisins(IndexSommet: integer;
LeGraphe: Graphe): SerieDeSommets;
{ retourne une structure permettant de retrouver l'index des
voisins immédiats }
function PremierSommet(var UneSerie: SerieDeSommets): integer;
{ extrait le premier des sommets de la série et retourne son
index }
function SommetSuivant(var UneSerie: SerieDeSommets): integer;
{ extrait le prochain sommet de la serie & retourne son index}
function FinDeSerie(UneSerie: SerieDeSommets): boolean;
{ vrai s'il n'y a plus de sommet suivant dans la serie }
En utilisant ces primitives, tout en ignorant de quelle façon la structure de graphe est implantée, écrivez
une procédure de parcours en profondeur qui imprime les noms des sommets du graphe fourni en
paramêtre:
procedure DFS(LeGraphe: Graphe);
Solution

exercice suivant

http://cuisung.unige.ch/std/ex/10/3a.html [07-06-2001 12:24:58]


Structuration des Données Informatiques - 10.4

10.4. Recherche en largeur


L'algorithme de recherche en profondeur ("Depth-First-Search") permet d'explorer un graphe, d'en
déterminer les composantes connexes et de découvrir s'il contient des cycles. Cependant, cet algorithme
n'est pas le seul algorithme fondamental des graphes. Il faut mentionner un autre algorithme de parcours
qui est à la base de la résolution de problèmes tels que celui du chemin minimum. Cet algorithme est dit
de recherche en largeur ("Breadth-First-Search").
Considérons le graphe de la figure 10.9 comme support à la description de l'algorithme de parcours en
largeur. Ce graphe est connexe, c'est-à-dire formé d'une seule composante, il est obtenu en ajoutant à
l'exemple de la figure 10.1 les arcs : GC, GH, JG, LG.
NB : Ce graphe contient quatre points d'articulation, c'est-à-dire un sommet qui, s'il est éliminé, "casse"
le graphe en deux ou plusieurs morceaux.
Ce sont :
A qui déconnecte B
H qui déconnecte I
J qui déconnecte K
G qui produit 3 composantes.

Nous obtenons pour ce graphe les chaînes de connectivité suivantes :


A
:FCGB
B
:A
C
:GA
D
:EF
E
:GFD

http://cuisung.unige.ch/std/10.4.html (1 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

F
:DEA
G
:LJHACE
H
:GI
I
:H
J
:LMKG
K
:J
L
:MJG
M
:JL
Fig. 10.10. Chaînes de connectivité correspondant au graphe de la figure 10.9.
Si l'on fait l'analogie avec le parcours d'un labyrinthe, à chaque fois que l'on se trouve seul face à un
choix de plusieurs sommets possibles vers lesquels se diriger, on ne peut qu'emprunter un seul chemin
vers l'un d'entre eux et laisser les autres chemins en attente afin de les explorer plus tard (parcours en
profondeur : DFS). Si l'on suppose, par contre, que tout un groupe de personnes entreprennent
l'exploration, à chaque carrefour, le groupe pourra se partager pour aller dans toutes les directions à la
fois (parcours en largeur : BFS).

10.4.1. Méthode générale de parcours de graphe


Afin de décrire un algorithme de parcours en largeur, nous allons étudier une méthode plus générale de
parcours de graphe que l'on puisse utiliser pour implanter le DFS aussi bien que le BFS. Cette méthode
consiste à classer les sommets en 3 catégories :
● La catégorie A des sommets déjà visités (ceux qui apparaissent dans l'arbre de parcours).

● La catégorie B des sommets adjacents à ceux de la catégorie A mais pas encore visités (sommets
qui peuvent être atteints).
● La catégorie C des sommets invisibles qui n'ont pas encore été rencontrés du tout (qui ne peuvent
pas être atteints depuis un sommet déjà visité).
Les différentes variantes de parcours dépendront de la manière dont on fait passer les sommets de la
catégorie C à la catégorie B et de la B dans la A. Sur la base de cette généralisation et dans le but
d'implanter la procédure "Visite" vue précédemment, on construit l'état initial suivant :
● placer le sommet de départ dans la catégorie B et mettre tous les autres dans la catégorie C

http://cuisung.unige.ch/std/10.4.html (2 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

On répète alors les opérations suivantes :


● faire passer un sommet x de B dans A;

● mettre dans B tous les sommets de C adjacents à x.

Plusieurs méthodes de parcours de graphes peuvent ainsi être envisagées en fonction de :


1. Quel sommet de B va dans A (si B en contient plusieurs).
2. Comment place-t-on les sommets dans B.

10.4.2. Méthode générale appliquée au DFS


Pour l'algorithme de "DFS", on choisira toujours le sommet qui a été mis le plus récemment dans la
catégorie B pour l'ajouter à l'arbre de parcours (c.à.d. le faire passer en A). Ceci peut être réalisé en :
a)
faisant passer le premier sommet (x) de B dans A;
b)
insérant au début de B les sommets de C adjacents à x;
c)
déplaçant dans B vers le début les sommets adjacents à x qui se trouvaient déjà dans B.
NB : Sans ce dernier point, on obtient un algorithme de parcours tout à fait différent qui n'est plus le
DFS.
La figure 10.11 qui suit montre le contenu des catégories A et B au fur et à mesure de l'avancement de
l'algorithme.

catégorie A catégorie B
1-ère étape - A
2-ème étape A B G C F
3-ème étape AB G C F
4-ème étape ABG E C H J L F
5-ème étape ABGE D F C H J L
6-ème étape ABGED F C H J L
7-ème étape ABGEDF C H J L
8-ème étape ABGEDFC H J L
9-ème étape ABGEDFCH I J L
10-ème étape ABGEDFCHI J L
11-ème étape ABGEDFCHIJ K M L
12-ème étape ABGEDFCHIJK M L
13-ème étape ABGEDFCHIJKM L
14-ème étape ABGEDFCHIJKML -
Fig. 10.11. Etapes du parcours en profondeur.
Dans cet exemple la catégorie B est essentiellement organisée "comme une pile" à ceci près que certains
éléments déjà dans B sont changés de place (ramenés vers l'avant de la pile). Sont marqués en pointillés

http://cuisung.unige.ch/std/10.4.html (3 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

dans l'arbre de parcours de la figure 10.12 les arcs inutilisés car aboutissant à un sommet déjà traité.
Il faut remarquer que l'arbre de parcours est différent de celui qu'on obtiendrait en exécutant la procédure
récursive "DFS" vue précédemment. Il y a en effet plusieurs manières de parcourir un graphe en
profondeur (en largeur aussi) puisqu'il n'y a pas d'ordre particulier pour le traitement des arcs partant d'un
même sommet.

Cependant la structure représentant les sommets de catégorie B ne contient pas suffisamment


d'informations pour permettre la construction de l'arbre de parcours; pour cela, il faut accompagner
chaque élément entré dans la structure du nom de son "père", c'est-à-dire du nom du sommet qui
l'introduit parce qu'il figure dans sa liste de connectivité. Il faut éventuellement modifier cette
information si le sommet est déplacé à l'intérieur de B.

10.4.3. Méthode générale appliquée au BFS


Une autre manière classique d'organiser les sommets de la catégorie B est d'avoir recours à une structure
de file d'attente. Dans ce cas, on extrait de B l'élément le plus ancien et on insère les nouveaux éléments
en fin de liste. Cette méthode est celle qui conduit à l'algorithme de parcours en largeur
(Breadth-First-Search) :
a)
on visite un sommet x;
b)
on visite tous les sommets qui lui sont adjacents;
c)
ensuite tous les sommets adjacents à ceux-ci; etc.
Sur la base de notre graphe-exemple, ceci produit l'effet suivant :

http://cuisung.unige.ch/std/10.4.html (4 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

catégorie A catégorie B
1-ère étape - A
2-ème étape A F C B G
3-ème étape AF C B G D E
4-ème étape AFC B G D E
5-ème étape AFCB G D E
6-ème étape AFCBG D E L J H
7-ème étape AFCBGD E L J H
8-ème étape AFCBGDE L J H
9-ème étape AFCBGDEL J H M
10-ème étape AFCBGDELJ H M K
11-ème étape AFCBGDELJH M K I
12-ème étape AFCBGDELJHM K I
13-ème étape AFCBGDELJHMK I
14-ème étape AFCBGDELJHMKI -
Fig. 10.13. Etapes du parcours en largeur.
On extrait un sommet x du début de B (à gauche), on explore la liste de connectivité de x en insérant à la
fin de B (à droite) les sommets encore invisibles. Cela donne l'arbre de parcours suivant :

L'algorithme correspondant est le suivant:

procedure BFS(NbSommets: integer);

{ On suppose, pour cet algorithme, que


l'on a accès à des primitives de gestion
de file d'attente, telles qu'on les a
vues au chapitre sur les structures
dynamiques linéaires. }

const Inexplore = -1;

http://cuisung.unige.ch/std/10.4.html (5 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

EnAttente = 0;

var File: FileDAttenteDEntiers;


Index: integer:
CmptrVisite,
IndexSommet : 0..MaxNbSommets;
NumeroDOrdre: array[1..MaxNbSommets]
of integer;

procedure Visite (IndexSommet : integer);


var Voisin : Ref;
begin { Visite }
{ attribue un numéro d'ordre au sommet }
CmptrVisite := CmptrVisite + 1;
NumeroDOrdre[IndexSommet]:=CmptrVisite;
{ parcoure la chaîne de connectivité }
Voisin := PremierVoisin[IndexSommet];
while Voisin <> nil do
with Voisin^ do begin
{ met chaque voisin inexploré sur la
file d'attente }
if NumeroDOrdre[IndexVoisin]=Inexplore
then begin
Met(File,IndexVoisin);
NumeroDOrdre[IndexVoisin]:=EnAttente;
end; { if }
Voisin := Suivant;
end; { with }
end; { Visite }

begin { BFS }
{ initialisations }
CmptrVisite := 0;
Initialise(File);

for IndexSommet:= 1 to NbSommets do


NumeroDOrdre[IndexSommet]:=Inexplore;

{ visite chaque sommet non exploré }


for IndexSommet := 1 to NbSommets do
if NumeroDOrdre[IndexSommet]=Inexplore
then begin
{ visite toute la composante connexe
liée au sommet non-exploré. }
Visite (IndexSommet);
while not Vide(File) do begin

http://cuisung.unige.ch/std/10.4.html (6 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4

Ote(File,Index);
Visite(Index);
end; { while }
end; { if }
end; { BFS }

10.5. Parcours d'un labyrinthe

Table des matières.

http://cuisung.unige.ch/std/10.4.html (7 of 7) [07-06-2001 12:25:23]


Structuration des Données Informatiques - 10.4, exercice 1

Arbre lexicographique
Question posée au contrôle continu du 16 juin 1997
On veut construire un arbre multiple d'ordre 26 permettant de stocker des mots d'un dictionnaire de sorte
que les chemins partant de la racine représentent des mots du dictionnaire, de la façon suivante

Les arcs sont étiquettés avec les lettres de l'alphabet (on suppose ici que l'on ne tient pas compte des
accents sur les lettres) et les noeuds contiennent un booléen indiquant si le chemin de la racine à ce
noeud représente un mot complet ou pas (sur le dessin - = faux et * = vrai).
Sur la base des déclarations suivantes, écrivez une procédure imprimant touts les mots du dictionnaire
dans l'ordre croissant de longueur de mot et, pour des mots d'une même longueur, par ordre alphabétique:

type VersNoeud= ^Noeud;


Noeud= record
Desc: array['a'..'z'] of VersNoeud;
Complet: boolean;
end; { Noeud }

var Dico: VersNoeud;

procedure Imprime(LeDico: VersNoeud);


Note: vous avez intérêt à considérer cet arbre lexicographique comme un graphe orienté et appliquer une
des méthodes de parcours de graphe. Vous pouvez aussi considérer, si vous en avez besoin, que vous
disposez d'un type "Pile" ou "FileDAttente" avec leurs procédures de manipulation. Vous pouvez aussi
utiliser des strings pour réconstituer une séquence de lettres formant un mot.
Solution

http://cuisung.unige.ch/std/ex/10/4a.html [07-06-2001 12:25:30]


Structuration des Données Informatiques - 10.5

10.5. Parcours d'un labyrinthe


Pour mieux illustrer la différence entre "BFS" et "DFS", considérons un labyrinthe décrit par le plan de la
figure 10.15.

Le graphe correspondant est obtenu en plaçant un sommet en chaque point du labyrinthe où il y a plus
d'un chemin possible et, ensuite, en connectant les sommets conformément à l'architecture du labyrinthe.
On obtient alors le graphe de la figure 10.16.

http://cuisung.unige.ch/std/10.5.html (1 of 5) [07-06-2001 12:26:25]


Structuration des Données Informatiques - 10.5

Dans la figure 10.17, on voit les sommets et les arcs parcourus alors que l'algorithme de
Depth-First-Search est à mi-chemin dans le graphe du labyrinthe, ayant pris son départ dans le coin
supérieur gauche.

http://cuisung.unige.ch/std/10.5.html (2 of 5) [07-06-2001 12:26:25]


Structuration des Données Informatiques - 10.5

La figure 10.18 représente la même situation mais obtenue par l'algorithme de Breadth-First-Search.

http://cuisung.unige.ch/std/10.5.html (3 of 5) [07-06-2001 12:26:25]


Structuration des Données Informatiques - 10.5

L'algorithme "DFS" explore le graphe en considérant les sommets les plus éloignés du point de départ et
ne reprenant les sommets plus proches que lorsqu'un cul-de-sac est rencontré; c'est le type de parcours
que fait un individu dans un labyrinthe car la prochaine place à explorer est toujours proche de l'endroit
où il se trouve à un instant donné.
L'algorithme "BFS" couvre toute la région proche du point de départ, poursuivant plus loin seulement
lorsque toutes les possibilités avoisinantes ont été explorées; c'est le type de parcours que fait un groupe
de personnes qui se dirigent simultanément dans toutes les directions.
Mises à part ces considérations opérationnelles, il est intéressant de faire ressortir les différences
fondamentales des deux méthodes :
● DFS est très simplement exprimé par une procédure récursive car la structure de données
sous-jacente est une pile.
● BFS admet une réalisation simple non-récursive car sa structure de données sous-jacente est une
file d'attente.

http://cuisung.unige.ch/std/10.5.html (4 of 5) [07-06-2001 12:26:25]


Structuration des Données Informatiques - 10.5

11. Structures récursives non


linéaires: les graphes orientés
Table des matières.

http://cuisung.unige.ch/std/10.5.html (5 of 5) [07-06-2001 12:26:25]


Structuration des Données Informatiques - 11.

11. Structures récursives non linéaires:


les graphes orientés
Les graphes orientés sont des graphes dont les arcs ne peuvent être parcourus que dans un seul sens. Ceci
ajoute une difficulté pour vérifier certaines propriétés du graphe. Pour mieux comprendre le concept, on
peut faire les analogies suivantes :
● Déplacement dans une ville où il y a de nombreux sens interdits.

● Déplacement à l'intérieur d'un pays où les lignes aériennes assument rarement l'aller-retour entre
deux villes.
Le sens associé à un arc peut aussi traduire une relation de précédence dans le modèle de l'application
(relation d'ordre partielle). Par exemple, pour la modélisation d'une chaîne de production :
sommet = tâche à exécuter
arc
= précédence des tâches les unes par rapport aux autres. Un arc de X vers Y signifie que la tâche Y
ne peut commencer que quand la tâche X est terminée.
Ainsi, pour la production d'une voiture, on peut avoir les relations de précédence suivantes :
● le pare-brise doit être monté après que le châssis ait été peint.

● l'installation électrique doit être faite après que le châssis ait été peint.

Il peut très bien ne pas y avoir de relation directe entre le montage du pare-brise et l'installation
électrique.
Dans ce chapitre, nous examinerons diverses variantes de l'algorithme de parcours en profondeur (DFS)
permettant de vérifier les propriétés de connectivité d'un graphe ou d'en sérialiser les sommets.

11.1. Représentation d'un graphe orienté

Table des matières.

http://cuisung.unige.ch/std/11.0.html [07-06-2001 12:26:30]


Structuration des Données Informatiques - 11.1

11.1. Représentation d'un graphe orienté


Les différentes représentations utilisées pour les graphes non-orientés se prêtent bien à la modélisation
d'un graphe orienté :
. Représentation matricielle : on ne fait figurer l'arc qu'une fois en établissant une convention pour
les sommets d'origine (lignes) et les sommets extrémités (colonnes). La matrice de connectivité
cesse alors d'être symétrique.
b. Représentation par chaîne de connectivité : on n'insère un arc qu'une seule fois, dans la chaîne du
sommet d'origine.
Dans un graphe orienté, un chemin à double sens entre deux sommets doit être explicitement indiqué par
deux arcs distincts.

Les arcs étant définis par les couples de sommets de la forme


"X Y"
indiquant un arc allant du sommet X vers le sommet Y (l'ordre dans lequel on énumère les arcs n'a pas
d'importance).

11.2. Depth-First Search

Table des matières.

http://cuisung.unige.ch/std/11.1.html [07-06-2001 12:26:35]


Structuration des Données Informatiques - 11.2

11.2. Depth-First Search


L'algorithme de recherche en profondeur qui a été décrit auparavant reste entièrement valable pour un
graphe orienté.

Pour le graphe de la figure 11.1, l'arbre de parcours est illustré à la figure 11.2 et donne l'ordre de
parcours suivant:
AFEDBGJKLMCHI
On peut distinguer trois types de liaisons dans cet arbre de parcours :

● les liaisons "en haut" vers un sommet ancêtre (D F ou L G),

● les liaisons "en bas" vers un sommet descendant (J K, J M,

● les liaisons "en travers" vers un sommet qui n'est ni ancêtre ni descendant (G E, H G).
Comme pour les graphes non-orientés, on est intéressé par les propriétés de connectivité. On veut être
capable de répondre à des questions du genre:
● Y a-t-il un chemin orienté du sommet x vers le sommet y ?

● Quels sont les sommets accessibles à partir du sommet x ?

● Y a-t-il un chemin de x vers y et un chemin de y vers x ?

On obtient les réponses à ces diverses questions en modifiant l'algorithme de "depth-first search" de
différentes manières.

http://cuisung.unige.ch/std/11.2.html (1 of 2) [07-06-2001 12:27:13]


Structuration des Données Informatiques - 11.2

11.3. Fermeture transitive

Table des matières.

http://cuisung.unige.ch/std/11.2.html (2 of 2) [07-06-2001 12:27:13]


Structuration des Données Informatiques - 11.3

11.3. Fermeture transitive


Dans les graphes non-orientés il était possible de connaître tous les sommets accessibles à partir d'un
sommet donné : on obtenait alors une composante connexe.
De manière identique, dans un graphe orienté il est intéressant de connaître l'ensemble des sommets
accessibles à partir d'un sommet donné en suivant les arcs dans le sens indiqué.
Nous avons montré dans le chapitre précédent que la procédure Visite de l'algorithme DFS visite tous les
sommets accessibles depuis le sommet de départ, pour autant qu'ils n'aient pas déjà été visités. Ainsi, si
l'on modifie cette procédure de façon à imprimer le nom du sommet visité lors de chaque appel, par
exemple en insérant :
write (Nom(IndexSommet))
juste à l'entrée de la procédure, on obtient la liste de tous les sommets accessibles à partir du sommet
donné.
N.B. Du fait que l'algorithme de recherche en profondeur (DFS) vu au paragraphe 3 du chapitre 10 est
prévu pour ne pas traiter un sommet deux fois, cette procédure DFS ne peut pas être utilisée telle quelle
(depuis le sommet H on peut accéder à tous les sommets du graphe, pas seulement à I). Cela signifie qu'il
ne faut pas ignorer les arcs en pointillé dans l'arbre de parcours. Il faut donc remettre le tableau
"NuméroDOrdre" à zéro avant de traiter chaque sommet de départ, c'est-à-dire avant chaque appel non
récursif à la procédure Visite.
Pour obtenir tous les sommets accessibles depuis chaque sommet, on peut donc utiliser la même
procédure SommetsAccessibles que celle décrite au paragraphe 3.2 du chapitre 10 :
procedure SommetsAccessibles(NbSommets: integer );

{ Variante de l'algorithme de parcours


d'un graphe en profondeur avec repré-
sentation par matrice de connectivité.
Les déclarations globales ainsi que la
lecture du graphe sont décrites au
paragraphe 10.2.1 du chapitre 10 }

var j,
CmptrVisite,
IndexSommet : 0..MaxNbSommets;
NumDOrdre : array[1..MaxNbSommets] of integer;

procedure Visite (IndexSommet : integer);


var AutreSommet : integer;
begin
CmptrVisite := CmptrVisite + 1;
NumDOrdre[IndexSommet] := CmptrVisite;
write (Nom[IndexSommet]);

http://cuisung.unige.ch/std/11.3.html (1 of 4) [07-06-2001 12:28:24]


Structuration des Données Informatiques - 11.3

for AutreSommet := 1 to NbSommets do


if Relie[IndexSommet,AutreSommet] and
{ la condition qui suit permet
d'éviter de boucler au cas où il
y aurait un cycle dans le graphe}
(NumDOrdre[AutreSommet] = 0) then
Visite (AutreSommet);
end; { Visite }

begin { SommetsAccessibles }
for IndexSommet := 1 to NbSommets do begin
CmptrVisite := 0;
for j := 1 to NbSommets do NumDOrdre[j] := 0;
Visite (IndexSommet);
writeln;
end; { for IndexSommet }
end; { SommetsAccessibles }
L'exécution du programme précédent produira le résultat suivant :

A B C D E F G J K L M
B
C A B D E F G J K L M
D E F
E D F
F D E
G A B C D E F J K L M
H A B C D E F G I J K L M
I A B C D E F G H J K L M
J A B C D E F G K L M
K
L A B C D E F G J K M
M A B C D E F G J K L
Pour un graphe non-orienté, ce traitement produisait un tableau ayant la propriété que chaque ligne
correspondait à une composante connexe du graphe.
Il est à remarquer que certaines lignes du tableau présentent les mêmes sommets.
Comme souvent, il est possible d'ajouter des instructions supplémentaires pour faire plus que simplement
imprimer le tableau ci-dessus. Par exemple : ajouter un arc direct de x vers y, s'il existe un moyen d'aller
de x vers y.
Le graphe résultant de l'opération qui consiste à ajouter tous les arcs de ce type à un graphe orienté est
appelé la fermeture transitive du graphe donné. Dans la plupart des cas, la fermeture transitive est un
graphe très dense. Il convient donc d'adopter une représentation matricielle.
Cette opération est analogue à celle qui consiste à déterminer les composantes connexes d'un graphe. Il

http://cuisung.unige.ch/std/11.3.html (2 of 4) [07-06-2001 12:28:24]


Structuration des Données Informatiques - 11.3

est alors très facile de répondre à la question :


"Est-il possible d'aller du sommet x vers le sommet y ?"
puisqu'il suffit d'examiner le contenu de la xème ligne, yème colonne pour obtenir la réponse.
Etant donné que l'algorithme de DFS, pour un seul sommet de départ, a une complexité de l'ordre de
NbSommets2, utiliser l'algorithme DFS pour déterminer la fermeture transitive exige NbSommets3 étapes
dans le cas le plus défavorable, dans la mesure où il faut examiner chaque élément de la matrice pour la
recherche DFS déclenchée pour chaque sommet.
Il y a une façon beaucoup plus simple, bien que nécessitant à peu près autant d'étapes, pour déterminer la
fermeture transitive d'un graphe orienté représenté par matrice :

for y := 1 to NbSommets do
for x := 1 to NbSommets do
if Relie[x,y] then
for j := 1 to NbSommets do
if Relie[y,j] then Relie[x,j] := true;
{ cette dernière instruction conditionnelle
est équivalente à:
Relie[x,j]:=Relie[x,j] or Relie[y,j]}
Cette méthode due à S. Warshall en 1962, repose sur la simple observation suivante :
"S'il existe un chemin allant de x vers y et un chemin allant de y vers j, alors il existe un chemin allant de
x vers j".
L'idée est de rendre cette observation un peu plus forte de manière à faire le calcul en un seul passage, à
savoir :
"S'il existe (dans la fermeture transitive) un chemin pour aller du sommet x vers le sommet y en
traversant uniquement des sommets d'indices plus petits que x et qu'il existe un chemin allant de y vers j,
alors il existe un chemin allant de x vers j n'employant que des sommets d'indices inférieurs ou égaux à
x"
Dans notre exemple (graphe de la figure 11.1), la méthode de Warshall convertit la matrice de
connectivité de la figure 11.3 en la matrice de la figure 11.4.

http://cuisung.unige.ch/std/11.3.html (3 of 4) [07-06-2001 12:28:24]


Structuration des Données Informatiques - 11.3

11.4. Le tri topologique

Table des matières.

http://cuisung.unige.ch/std/11.3.html (4 of 4) [07-06-2001 12:28:24]


Structuration des Données Informatiques - 11.3, exercice 1

Exercice suivant

Plus courts chemins dans un graphe


Question posée au conctrôle continu du 20 juin 1995
En s'inspirant de la méthode de Warshall (page 157 du livre) pour déterminer la fermeture transitive d'un
graphe représenté par une matrice de connectivité, dériver un algorithme construisant une matrice de
valeurs numériques indiquant la longueur du plus court chemin entre tous les divers noeuds d'un graphe.
Une approche simple consiste à d'abord construire la matrice résultat à partir de la matrice de
connectivité, en mettant la valeur 1 là où il y a des arcs dans la matrice de connectivité. Puis, selon la
méthode de Warshall, examiner toutes les combinaisons X->Y->Z telles qu'il existe un chemin de X vers
Y et de Y vers Z et comparer la longueur de X->Y + Y->Z avec le contenu de la matrice résultat pour la
case (X,Z). Si la somme est plus petite, elle remplacera l'ancienne valeur de la case (X,Z). (Faites bien
attention à l'initialisation de la matrice résultat!)
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/11/3a.html [07-06-2001 12:28:40]


Structuration des Données Informatiques - 11.4

11.4. Le tri topologique


Pour bien des applications impliquant des graphes orientés, on doit tenir compte des graphes cycliques.
Si, par exemple, un graphe orienté représente un processus de production, que les noeuds sont les
opérations à effectuer et que les arcs sont les indications de l'ordre dans lequel ces opérations sont à faire,
alors la présence d'un cycle traduit une incohérence du modèle. En effet, dire que l'opération A précède
l'opération G qui précède l'opération C qui précède l'opération A n'a aucun sens (cf. graphe précédent en
11.2.).
Il faut donc que l'on utilise, pour de telles applications, des graphes orientés acycliques.
Définition : un graphe orienté est acyclique si, pour n'importe quel sommet de départ, il est impossible de
retomber sur ce sommet en suivant le sens des arcs.
Ainsi, en faisant quelques simplifications (suppression de certains arcs et inversion du sens de certains
autres) il est possible de transformer notre graphe-exemple en un graphe acyclique (figure 11.5).

Vu de n'importe quel sommet, en suivant le sens des arcs, un graphe orienté acyclique se présente comme
un arbre. Cela ne signifie pas qu'un graphe orienté acyclique est un arbre, mais plutôt que les arcs et
sommets accessibles depuis un sommet donné se présentent comme une structure d'arbres (figure 11.6).

Cela implique que les arbres de parcours par DFS d'un graphe orienté acyclique ne possèdent pas de
pointeurs "vers le haut", ce qui est le critère utilisé pour vérifier qu'un graphe orienté est acyclique ou

http://cuisung.unige.ch/std/11.4.html (1 of 3) [07-06-2001 12:28:52]


Structuration des Données Informatiques - 11.4

pas. Ainsi, pour notre graphe exemple, l'algorithme DFS produit (à partir du sommet A) l'arbre de
parcours de la figure 11.7. Il n'y a pas de pointeurs vers un noeud ancêtre. Il n'y a donc pas de cycle.

Une opération importante pour les graphes orientés acycliques est de considérer ses sommets dans un
ordre tel qu'aucun sommet n'est traversé avant que les sommets qui pointent vers lui n'aient été déjà
traversés. Pour notre graphe-exemple, la suite de sommets
JKLMAGHIFEDBC
satisfait une telle condition. Si on voulait représenter les arcs sur une telle liste, ils iraient tous de gauche
à droite (figure 11.8.).

Comme indiqué précédemment une telle représentation a des applications évidentes dans les domaines de
séquencement d'opération.
L'opération qui consiste à établir une telle représentation est appelée le tri topologique.
Il faut relever qu'il y a souvent plusieurs solutions pour le tri topologique d'un graphe orienté acyclique.
Par exemple A J L G F K E M B H C I D est aussi un ordre topologique pour notre graphe-exemple.
Nous ne donnerons pas d'exemple d'algorithme de tri topologique car il est généralement plus facile de
faire un tri topologique inverse.

http://cuisung.unige.ch/std/11.4.html (2 of 3) [07-06-2001 12:28:52]


Structuration des Données Informatiques - 11.4

11.5. Tri topologique inverse

Table des matières.

http://cuisung.unige.ch/std/11.4.html (3 of 3) [07-06-2001 12:28:52]


Structuration des Données Informatiques - 11.5

11.5. Tri topologique inverse


Dans un graphe orienté où les arcs reflètent une relation de précédence entre les sommets, le sens des
arcs dépend directement de l'interprétation qu'on leur donne. Ainsi, un arc allant de x vers y peut être
interprété comme signifiant que x doit être traité avant y ou bien, au contraire, que le sommet x "dépend"
du sommet y et doit, par conséquent être traité après y.
Exemple : pour un programme, on aurait un arc partant d'un symbole vers les autres symboles utilisés
dans sa définition. Ceci permet d'indiquer l'ordre dans lequel les symboles doivent être introduits afin
qu'aucun ne soit utilisé avant d'avoir été défini.
Dans ce cas il faut représenter les sommets dans l'ordre inverse; c'est le tri topologique inverse. Pour
notre exemple on a alors :
DEFCBIHGAKMLJ
Ici les arcs iraient de droite à gauche.
Cependant la distinction entre tri topologique direct et inverse n'est pas fondamentale car :
Faire le tri topologique inverse d'un graphe orienté acyclique est équivalent à faire le tri topologique
direct du graphe obtenu en inversant le sens des arcs. Pour une matrice de connectivité, inverser les arcs
revient à faire une transposition de matrice.
Si on considère l'algorithme de recherche "en profondeur", on peut le modifier pour lui faire faire le tri
topologique inverse simplement en insérant l'instruction :
write(nom(IndexSommet))
juste avant de terminer la procédure Visite. Cette modification a pour effet d'imprimer la liste
correspondant au tri topologique inverse lorsque le graphe traité par DFS est un graphe orienté acyclique.
En effet, on imprime le nom d'un sommet après (ou à la fin de la procédure) avoir imprimé les noms des
sommets vers lesquels il pointe.
Le mécanisme de la récursivité insère dans la pile les sommets à l'entrée de la procédure Visite et les
ressort pour les imprimer à la sortie : l'ordre est donc l'inverse de l'ordre d'entrée et comme, partant d'un
sommet, on suit sa "filiation" jusqu'à rencontrer un sommet déjà visité ou jusqu'à rencontrer un
"cul-de-sac", on obtient bien une liste représentant l'ordre topologique inverse.
Si l'on n'est pas sûr que le graphe est bien acyclique, on peut modifier l'algorithme de parcours de façon à
détecter les cycles de la manière suivante :

procedure TriTopoInverse(NbSommets: integer );


{
Algorithme de tri topologique inverse basé sur
le DFS avec représentation par matrice de connectivité.
Les déclarations globales ainsi que la lecture
du graphe sont décrites au paragraphe 11.2.2.

http://cuisung.unige.ch/std/11.5.html (1 of 3) [07-06-2001 12:29:24]


Structuration des Données Informatiques - 11.5

Cette version détecte la présence de cycles éventuels.


}
var i,
CmptrVisite,
IndexSommet : 0..MaxNbSommets;

NumDOrdre : array[1..MaxNbSommets] of integer;


Parcouru:array[1..MaxNbSommets] of boolean;

procedure Visite (IndexSommet : integer);


var AutreSommet : integer;
begin
CmptrVisite := CmptrVisite + 1;
NumDOrdre[IndexSommet] := CmptrVisite;
Parcouru[IndexSommet] := true;
for AutreSommet := 1 to NbSommets do
if Relie[IndexSommet, AutreSommet] then
if Parcouru[AutreSommet] then
writeln(Nom[AutreSommet], ' fait partie d''un cycle')
else if NumDOrdre[AutreSommet] = 0
then Visite (AutreSommet);
Parcouru[IndexSommet] := false;
write(Nom[IndexSommet]);
end; { Visite }

begin { TriTopoInverse }
CmptrVisite := 0;
for IndexSommet := 1 to NbSommets do
NumDOrdre[IndexSommet]:=0;
for IndexSommet := 1 to NbSommets do
if NumDOrdre[IndexSommet]=0 then begin
for i := 1 to NbSommets do Parcouru[i] := false;
Visite (IndexSommet);
end; { if }
end; { TriTopoInverse }
Si un graphe orienté contient un cycle, c'est-à-dire que, partant d'un sommet, on y revient après avoir
parcouru plusieurs arcs dans le sens indiqué, ce n'est pas un graphe acyclique et il ne peut donc pas faire
l'objet d'un tri topologique.

Exercices

http://cuisung.unige.ch/std/11.5.html (2 of 3) [07-06-2001 12:29:24]


Structuration des Données Informatiques - 11.5

11.6. Composante fortement connexe d'un


graphe orienté

Table des matières.

http://cuisung.unige.ch/std/11.5.html (3 of 3) [07-06-2001 12:29:24]


Structuration des Données Informatiques - 11.5, exercice 1

Exercice suivant

Tri topologique inverse


Question posée au conctrôle continu du 17 juin 1996
En s'inspirant de la description de graphes non-orientés en termes de type abstrait vue au cours, nous
allons supposer que les types "Graphe" et "SerieDeSommets" ont été définis, sans que vous ne
connaissiez les détails de ces structures. Nous allons aussi supposer que les primitives suivantes sont à
votre disposition:

function Index(NomSommet: string; LeGraphe: Graphe): integer;


{ retourne un numero compris entre 1 et MaxNbSommets }

function NomSommet(IndexSommet: integer; LeGraphe: Graphe) :string;


{ retourne le nom du sommet dont l'index est fourni }

function NombreDeSommets(LeGraphe: Graphe): integer;


{retourne le nombre de sommets effectifs composant le graphe}

function SommetsVoisins(IndexSommet: integer;


LeGraphe: Graphe): SerieDeSommets;
{ retourne une structure permettant de retrouver l'index des
voisins immédiats }

function PremierSommet(var UneSerie: SerieDeSommets): integer;


{ extrait le premier des sommets de la série et retourne son
index }

function SommetSuivant(var UneSerie: SerieDeSommets): integer;


{ extrait le prochain sommet de la serie & retourne son index}

function FinDeSerie(UneSerie: SerieDeSommets): boolean;


{ vrai s'il n'y a plus de sommet suivant dans la serie }
En utilisant ces primitives, tout en ignorant de quelle façon la structure de graphe est implantée, écrivez
une procédure "TriTopologiqueInverse(LeGraphe: Graphe)" qui imprime les sommets du
graphe dans l'ordre topologique inverse.
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/11/5a.html [07-06-2001 12:29:26]


Structuration des Données Informatiques - 11.6

11.6. Composante fortement connexe d'un graphe


orienté
Pour les graphes non-orientés nous avions défini une composante connexe comme étant un ensemble de
sommets tel qu'il existait toujours un chemin entre deux sommets de cet ensemble. Deux sommets reliés
par un arc faisait forcément partie de la même composante connexe.
D'une manière similaire, pour un graphe orienté, on peut définir une composante fortement connexe
comme étant un ensemble de sommets tel qu'il existe toujours un chemin entre deux sommets de cet
ensemble. Cette définition implique que tous les sommets d'une composante connexe font partie d'un
même cycle. Deux sommets reliés par un arc ne font donc pas forcément partie de la même composante.
En fait, les sommets se subdivisent en composantes fortement connexes, qui ont la propriété que tous les
sommets d'une composante soient accessibles depuis les autres et réciproquement. Mais il est impossible
d'aller d'un sommet d'une composante à un sommet d'une autre composante et retour. Deux composantes
fortement connexes d'un graphe sont soit entièrement disjointes, soit reliées par un ou plusieurs arcs qui
vont tous dans le même sens (les arcs partent tous de la même composante pour arriver dans l'autre)
Les composantes fortement connexes du graphe-exemple du début de ce chapitre (figure 11.1.) sont :
B
K
HI
DEF
ACGJLM
Dans cet exemple, le sommet A est dans une autre composante connexe que F car il y a bien un chemin
menant de A à F mais il n'y a aucun moyen d'aller de F à A.
A l'aide de la matrice de fermeture transitive (appelons la M), il est possible de déterminer si deux
sommets x et y font partie de la même composante fortement connexe : il suffit que M[x,y]=M[y,x]=1.
Comme on peut s'y attendre, la méthode pour déterminer les composantes fortement connexes d'un
graphe orienté repose sur l'algorithme de DFS. La variante de l'algorithme de DFS proposée plus loin est
due à R.E. Tarjan (1972) dont l'article entier mérite d'être étudié.
Référence:
R.E. Tarjan
"Depth-first search and linear graph algorithms"
SIAM Journal on Computing, vol. 1, No. 2 (1972).
Cette version modifiée de DFS se présente comme une fonction et, dans la réalisation considérée ici, elle
travaille sur une représentation par chaîne de connectivité avec les déclarations suivantes :

const MaxNbSommets = ...;

http://cuisung.unige.ch/std/11.6.html (1 of 5) [07-06-2001 12:29:36]


Structuration des Données Informatiques - 11.6

type Lien = ^Sommet;


Sommet = record
v: integer; {v=numéro du sommet}
Next: Lien; { par hash-coding}
end; { Sommet }

var PremierVoisin:array[1..MaxNbSommets] of Lien;


CmptrVisite : 0..MaxNbSommets; {Nb. sommets déjà visités}
NumDOrdre : array[1..MaxNbSommets] of integer;
Nom : array[1..MaxNbSommets] of char; {noms des sommets}
Pile : array[1..MaxNbSommets] of 1..MaxNbSommets;
SommetDePile : integer;
NbSommets : 0..MaxNbSommets; {nombre de sommets}

function Visite(IndexSommet:integer) : integer;


{retourne le numéro d'ordre du sommet le
plus ancien rencontré dans le cadre du
parcours récursif des successeurs du
sommet dont l'index est passé en param.}

var Courant: Lien;


Anciennete,LePlusAncien : integer;

begin { Visite }
CmptrVisite := CmptrVisite+1;
NumDOrdre[IndexSommet] := CmptrVisite;
LePlusAncien := CmptrVisite;
Pile [SommetDePile] := IndexSommet;
SommetDePile := SommetDePile+1;
Courant:= PremierVoisin[IndexSommet];

while Courant <> nil do begin

if NumDOrdre[Courant^.v]=0 then begin


Anciennete := Visite (Courant^.v);
if Anciennete <LePlusAncien then
LePlusAncien := Anciennete;
end { then }
else if NumDOrdre[Courant^.v] < LePlusAncien then
LePlusAncien:=NumDOrdre[Courant^.v];
Courant := Courant^.Next;
end; { while }
if LePlusAncien = NumDOrdre[IndexSommet] then begin

{ on arrive ici s'il n'y a pas eu de changement dans

http://cuisung.unige.ch/std/11.6.html (2 of 5) [07-06-2001 12:29:36]


Structuration des Données Informatiques - 11.6

la boucle while, ou si l'on est revenu du traitement


récursif des suivants vers le plus ancien }

repeat
SommetDePile := SommetDePile-1;
write (Nom[Pile[SommetDePile]]);
NumDOrdre[Pile[SommetDePile]] :=NbSommets+1;
until Pile[SommetDePile]=IndexSommet;

writeln;
end; { if }

Visite := LePlusAncien;

end; { Visite }
Explication de la fonction Visite :
. Le nouveau sommet IndexSommet est mis sur la pile et on explore ensuite la chaîne de
connectivité associée au sommet IndexSommet (Courant := PremierVoisin[IndexSommet])
B. On poursuit la chaîne jusqu'à la fin.
C. Pour chaque élément de la chaîne, on détermine s'il a déjà été visité (NumDOrdre[Courant^.v] <>
0), sinon on appelle récursivement la fonction Visite qui associe à chaque sommet une valeur
entière Anciennete. Si cette valeur est plus petite que le minimum courant (LePlusAncien) courant,
elle devient le nouveau minimum courant.
D. A la fin du traitement d'une chaîne de connectivité on détermine si le plus petit numéro d'ordre
rencontré est égal à la valeur du numéro d'ordre de visite du sommet IndexSommet. Si oui, cela
signifie que tous les sommets qui ont été rencontrés (et visités) depuis le sommet IndexSommet
font partie de la même composante fortement connexe que IndexSommet. On procède alors aux
actions suivantes (E, F et G) :
E. On désempile ces sommets que l'on imprime les uns après les autres, jusqu'à revenir au sommet
IndexSommet et on met le numéro d'ordre de chacun de ces sommets à une valeur impossible
(NbSommets+1) pour être sûr de ne pas les retraiter.
F. On imprime une ligne blanche lorsqu'on a fini d'imprimer tous les sommets d'une composante
fortement connexe.
G. On affecte la dernière valeur de LePlusAncien à la fonction Visite et on insère dans le tableau
NumDOrdre[IndexSommet] une valeur normalement impossible: NbSommets+1.
Pour mieux comprendre le fonctionnement de cet algorithme, il faut se rappeler que les sommets d'une
composante fortement connexe font partie d'un même cycle voire de plusieurs (par exemple, dans la
figure 11.1, GJL et AGC forment deux cycles faisant partie de la même composante fortement connexe).
C'est donc en revenant sur un sommet déjà rencontré le long du chemin courant que l'on s'aperçoit de ces
cycles et que l'on peut ainsi déterminer les composantes fortement connexes du graphe.
Comme il peut y avoir plusieurs cycles dans la même composante, la fonction Visite retourne le plus
ancien des noeuds rencontrés le long d'un chemin. Cette valeur de retour n'a d'intérêt que lorsque la

http://cuisung.unige.ch/std/11.6.html (3 of 5) [07-06-2001 12:29:36]


Structuration des Données Informatiques - 11.6

fonction Visite est appelée récursivement (dans la partie C).


Si la fonction Visite retourne un numéro plus ancien (plus petit) que le numéro de noeud courant, c'est
que ce noeud courant fait partie d'un cycle sans en être le point de départ (c'est-à-dire que ce noeud n'est
pas le plus ancien noeud de la composante à être traité).
Si la fonction Visite retourne un numéro égal au numéro de noeud courant, c'est que ce noeud courant est
le point de départ d'une composante fortement connexe.
Si la fonction Visite retourne un numéro supérieur au numéro de noeud courant, c'est que ce noeud
courant fait partie d'un chemin qui ne constitue pas un cycle, le noeud courant constitue donc une
composante fortement connexe à lui tout seul.
Prenons pour exemple un graphe comportant deux cycles imbriqués :

En commençant le parcours par le sommet A, l'on est amené à traiter le sommet B puis le sommet C.
Lors du traitement du sommet C, l'on se rend compte que C fait partie d'un cycle car le plus ancien de ses
successeurs (A) a un numéro d'ordre plus petit (1) que le sien (3). On ne fera pourtant rien encore.
Après avoir traité C, l'on revient au traitement des successeurs de B. On arrive donc à D pour lequel le
plus ancien successeur est C. Là encore, l'on se rend compte que D fait partie d'un cycle car le plus
ancien de ses successeurs (C) a un numéro d'ordre plus petit (3) que le sien (4). On ne fera une fois de
plus rien d'autre.
Le traitement des successeurs de B est terminé et le plus ancien sommet atteint est A. B fait donc aussi
partie d'un cycle dont il n'est pas le point de départ. C'est en revenant au traitement des successeurs de A
que l'on se rend compte que le plus ancien sommet que l'on peut atteindre depuis A est A lui-même. On a
donc détecté une composante fortement connexe partant de A et comprenant B, C et D.
Bien évidemment, la fonction Visite peut faire beaucoup plus que simplement imprimer les noms des
sommets d'une composante fortement connexe. Pour cela il suffit de remplacer, dans la partie E,
l'instruction
write(Nom[Pile[SommetDePile]])
par l'appel à une procédure de traitement P; on obtient alors :
if LePlusAncien = NumDOrdre(IndexSommet) then
repeat
SommetDePile := SommetDePile-1;
P(Nom[Pile[SommetDePile]])
until Pile[SommetDePile] = IndexSommet;

http://cuisung.unige.ch/std/11.6.html (4 of 5) [07-06-2001 12:29:36]


Structuration des Données Informatiques - 11.6

12. Structures récursives non


linéaires: les réseaux
Table des matières.

http://cuisung.unige.ch/std/11.6.html (5 of 5) [07-06-2001 12:29:36]


Structuration des Données Informatiques - 11.6, exercice 1

Graphe orienté
Question posée au conctrôle continu du 22 juin 2000
Il est aisément démontrable que lorsque l'on élève un nombre N au carré, les deux derniers chiffres du
résultat ne dépendent que des deux derniers chiffres de N. Ainsi, un nombre se terminant par 05 aura
toujours son carré se terminant par 25 ( 105->11025, 1905->3629025 ).
L'on peut donc définir une application reliant chacun des nombres de 00 à 99 à un autre de ces nombres,
correspondant à la valeur de son carré modulo 100. Ceci compose bien entendu un graphe orienté...
1. Considérant que chacun des noeuds est l'origine d'un et un seul arc, déterminez la structure de données
la plus simple adaptée à la représentation des arcs de ce graphe.
La propriété précédente amène logiquement ceci :
● puisqu'un noeud a au plus un successeur (en fait, exactement un), l'on peut définir en suivant les
arcs (c'est-à-dire en réitérant le calcul) un chemin unique qui en est issu;
● puisque tous les noeuds ont au moins un successeur, ce chemin aboutit obligatoirement à un cycle
(le nombre de noeuds étant limité).
L'on aimerait donc associer à chaque noeud la "séquence terminale" (cycle) qui lui correspond.
2. Écrivez une fonction imprimant, pour un noeud passé en paramètre, les noeuds composant le cycle
terminal qui lui est associé.
Pensez que chaque composante du graphe ne comporte qu'un seul cycle; rappelez-vous qu'un cycle est un
cas particulier de composante fortement connexe...
Solution

http://cuisung.unige.ch/std/ex/11/6a.html [07-06-2001 12:29:38]


Structuration des Données Informatiques - 11. exercice 1

Exercice suivant

Le chemin le plus court


Question posée l'examen écrit du 30 juin 1999
Imaginez un graphe orienté qui est donné par sa matrice de connectivité:
sommets d'arrivée
1 2 3 4 5 6 7
1 x x
2 x
sommets 3 x
de 4 x
départ
5 x
6 x
7
. Dessinez ce graphe
b. Trouvez maintenant le chemin le plus court entre les noeuds 1 et 6. Pour ça vous avez besoin d'une
file d'attente FIFO permettant de stocker des chaînes Li. Chaque Li=(Vi,1,...,Vi,n_i) contient une
séquence de sommets. L'algorithme à utiliser pour trouver le chemin le plus court entre deux
noeuds Vs et Vf est le suivant:
1. Videz la FIFO
2. Insérez la liste (Vs) dans la FIFO.
3. Tirez la première liste de la FIFO. Appelez cet élément L1
4. Si le premier noeud V1,1 contenu dans L1 est égal à Vf, affichez L1 de la fin au début: La
séquence dans L1 est un chemin de longueur minimale entre Vs et Vf (il peut y en avoir
plusieurs). L'algorithme est terminé.
5. Sinon (le premier noeud V1,1 contenu en L1 n'est pas égal à Vf): visitez V1,1 de la façon
suivante: Ajoutez pour chaque voisin v de V1,1 la liste résultant de (cons v L1) .
6. Continuez avec le pas 3, tant que la FIFO n'est pas vide et le chemin le plus court n'a pas été
trouvé au pas 4.
Les limitations de cet algorithme sont claires. Il doit être possible d'atteindre Vf a partir de Vs. La
solution doit donner le contenu de la FIFO à chaque étape de l'algorithme!
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/11/a.html [07-06-2001 12:29:41]


Structuration des Données Informatiques - 12.0

12. Structures récursives non linéaires :


les réseaux
Un réseau est un graphe orienté et pondéré, c'est-à-dire que chaque arc, en plus d'un sens, possède une
valeur associée qui représente la "capacité" de liaison de l'arc. Par exemple :
une distance, un coût, un débit maximum, etc . . .
Le problème qui consiste à déterminer quel est le flot optimum que l'on peut obtenir à travers un réseau
est un problème non trivial dont l'étude détaillée est du domaine de la recherche opérationnelle.
Pour définir un problème de flot dans un réseau, on fera les hypothèses suivantes :
1. il n'y a qu'une source (ou point de départ)
2. il n'y a qu'une seule destination vers laquelle tous les arcs convergent;
3. on suppose que les sommets intermédiaires sont des vannes qui, par réglages adéquats, permettent
d'optimiser le flot à travers l'ensemble du réseau (flot : le long d'un arc, à travers un sommet).
De par ces hypothèses, on se limitera donc dans ce chapitre à n'aborder qu'une certaine catégorie de
problèmes liés aux graphes orientés pondérés. Au nombre des utilisations de telles structures, on peut
citer:
● la distribution d'eau, de gaz, ...

● le transport de pétrole d'un gisement vers une raffinerie

● le trafic sur un réseau routier

● les mouvements de matériel dans une entreprise

● le flot d'informations dans un réseau d'ordinateurs .


etc.

12.1. Capacité et écoulement

Table des matières.

http://cuisung.unige.ch/std/12.0.html [07-06-2001 12:29:43]


Structuration des Données Informatiques - 12.1

12.1. Capacité et écoulement


On supposera que la source et la destination ne sont pas limitées en ce qui concerne la "production" et la
"consommation". La limite du système entier est constituée par la capacité des arcs à transporter le
"liquide".
On définit une fonction de capacité c(x,y) où x et y sont des sommets du réseau comme :

c(x,y)=
{ valeur de l'arc reliant x et y
0 si aucun arc de x à ys

On définit une fonction d'écoulement (ou flux) f(a,b) comme :

f(a,b)=
{ 0 si pas d'arc entre a et b ou si pas d'écoulement
quantité de liquide s'écoulant de a vers b

On aura donc :
f(x,y) c(x,y)

Soit v la quantité de liquide qui s'écoule de la source S vers la destination T, alors la quantité totale de
liquide quittant S est égale à la quantité totale de liquide atteignant T, c'est-à-dire
f(S,x) = v = f(x,T)
x sommets x sommets

Cela signifie qu'aucun autre sommet que S ne peut produire de liquide et qu'aucun sommet autre que T
ne peut absorber de liquide.
donc : f(x,y) = f(y,x) x S,T
y sommets y sommets

On peut écrire ces relations en définissant des fonctions "entrée" et "sortie" pour chaque sommet du
réseau comme:
sortie (S) = entrée (T) = v
entrée (x) = sortie (x) x S,T

Plusieurs fonctions d'écoulement peuvent exister pour un réseau don né.

http://cuisung.unige.ch/std/12.1.html (1 of 2) [07-06-2001 12:29:50]


Structuration des Données Informatiques - 12.1

12.2. Représentation d'un réseau

Table des matières.

http://cuisung.unige.ch/std/12.1.html (2 of 2) [07-06-2001 12:29:50]


Structuration des Données Informatiques - 12.2

12.2. Représentation d'un réseau


Un réseau est représenté, par exemple, par une matrice de connectivité doublée d'une matrice des
capacités, ou par une matrice de connectivité de valeurs non-logiques indiquant simultanément
l'existence d'un arc et sa valeur.
La figure 12.1 définit un réseau et illustre ensuite deux fonctions d'écoulement possibles. L'objectif est de
trouver une fonction d'écoulement qui maximise la quantité v du liquide allant de S à T.
La stratégie à mettre en oeuvre pour obtenir une fonction d'écoulement optimale est de commencer avec
une fonction d'écoulement nulle et de l'améliorer successivement jusqu'à produire un écoulement
optimal.

Fig. 12.1. Réseau et fonctions d'écoulement

12.3. Amélioration d'une fonction

Table d'une fonction d'écoulement matières.

http://cuisung.unige.ch/std/12.2.html [07-06-2001 12:30:24]


Structuration des Données Informatiques - 12.3

12.3. Amélioration d'une fonction d'écoulement


Soit une fonction d'écoulement f. Une possibilité pour l'améliorer est de trouver un chemin
S = x1 x2 x3 ... xn = T

allant de S vers T, tel que le flux sur chaque arc soit inférieur à sa capacité. C'est-à-dire
f(xk-1, xk) < c(xk-1,xk) k [2,n]

Le flux peut être augmenté sur chaque arc d'un tel chemin par le minimum de la différence entre capacité
et écoulement le long du chemin
c(xk-1,xk) - f(xk-1,xk) k [2,n]

Ainsi lorsque l'écoulement est augmenté sur la totalité du chemin, il y a au moins un arc qui travaille à sa
capacité maximum. C'est-à- dire :
f(xi-1,xi) = c(xi-1,xi) i [2,n]

et pour lequel il n'est pas possible d'augmenter le flux. Ceci est illustré par l'exemple de la figure 12.2 où
l'on indique la capacité et le flux réel pour chaque arc. Il y a deux chemins de S vers T avec un
écoulement positif. Ce sont :
(S,A,C,T) et (S,B,D,T)
Chacun de ces chemins contient un arc qui travaille à sa capacité maximum, en l'occurence les arcs :
(A,C) et ((B,D)

Fig. 12.2. Exemple de réseau avec indication de la capacité et de l'écoulement.


Mais le chemin (S,A,D,T) est tel que le flux traversant chaque arc est inférieur à la capacité nominale de
cet arc. Il est donc possible d'augmenter l'écoulement total d'une valeur 1 puisque l'arc DT a une
différence entre capacité et écoulement de 1. Ceci est représenté par la figure 12.3. L'écoulement total à
travers tout le réseau passe donc de 5 à 6.
Cependant à partir du graphe on aurait pu améliorer l'écoulement sur le chemin (S,B,A,D,T), ce qui
aurait produit la figure 12.4.

http://cuisung.unige.ch/std/12.3.html (1 of 4) [07-06-2001 12:31:06]


Structuration des Données Informatiques - 12.3

Fig. 12.3. Amélioration de l'écoulement par découverte d'un chemin non saturé

Fig. 12.4. Autre amélioration possible (par S,B,A,D,T)


Même en l'absence de chemin le long duquel on puisse augmenter le flux, il y a un moyen d'améliorer la
fonction d'écoulement de la source à la destination. Cette méthode est illustrée par la figure 12.5.

Fig. 12.5. Autre méthode pour améliorer un écoulement.


Dans la figure 12.5a il n'y a aucun chemin de S à T qui puisse donner lieu à une amélioration de la
fonction d'écoulement. Mais si le flux de x vers y est diminué, le flux de x vers T peut être augmenté.
Pour compenser la diminution du flux d'entrée en Y, le flux de S vers Y peut être augmenté de sorte à
augmenter le flux total de S vers T (figure 12.5b).
En résumé :
● diminution flux X -> Y

● augmentation flux S -> Y

● flux total augmenté

● la perte de flux de X vers Y est compensée vers T afin de respecter:

http://cuisung.unige.ch/std/12.3.html (2 of 4) [07-06-2001 12:31:06]


Structuration des Données Informatiques - 12.3

entrée(x) = sortie(x).
Il est possible de généraliser cette dernière méthode de la manière suivante :
● Soit un chemin de S à un sommet u

● Soit un chemin d'un sommet v à T

● Soit un arc de v à u avec un flux positif (non nul)

● il est alors possible de réduire le flux entre u et v et d'augmenter de la même quantité le flux de S
vers u et de v vers T
● la quantité est déterminée comme étant le minimum entre :

. le flux de u vers v
b. les différences entre flux et capacité le long des chemins de S vers u et de v vers T.
soit : min( f(v,u), c(xi,xi+1) - f(xi,xi+1) ) pour i le long du chemin de S vers u et de v vers T.

Cela peut s'illustrer de la manière suivante :

Fig. 12.6. Généralisation


N.B. Ne sont schématisées ici que les parties du graphe qui nous intéressent, c'est-à-dire le chemin de S
vers u, le chemin de v vers T et l'arc de v vers u.
Les deux méthodes peuvent se combiner et l'on procède alors de S vers T de la manière suivante :
● la production de liquide fournie par S n'est limitée que par la capacité des liaisons reliant S à T.

● Si la capacité de S vers x permet une augmentation du flux entrant en x d'une quantité a.

● Si la capacité de y vers T peut absorber cette augmentation.

● Alors, si y est adjacent à x (c'est-à-dire qu'il existe un arc (x,y) ) le flux issu de y en direction de T
peut être augmenté d'une quantité :
m = min (a, capacité inutilisée de (x,y))
c'est une application de la première méthode.

http://cuisung.unige.ch/std/12.3.html (3 of 4) [07-06-2001 12:31:06]


Structuration des Données Informatiques - 12.3

Fig. 12.7. Schématisation de la première méthode d'amélioration


● D'une manière semblable, si x est adjacent à un noeud y (c'est-à- dire s'il existe un arc (y,x)), alors
le flux s'écoulant de y en direction de T peut être augmenté d'une quantité
m = min (a, flux de y vers x)
en réduisant de m le flux de y vers x.
C'est une application de la deuxième méthode.

Fig. 12.7. Schématisation de la deuxième méthode d'amélioration.

12.4. Ecoulement optimal

Table des matières.

http://cuisung.unige.ch/std/12.3.html (4 of 4) [07-06-2001 12:31:06]


Structuration des Données Informatiques - 12.4

4. Ecoulement optimal
Définissons un pseudo-chemin de S à T comme étant une suite de sommets
S = x1, x2, x3, ... xn = T telle que

" i OE [2,n], soit (xi-1 , xi), soit (xi , xi-1) est un arc.
(c'est-à-dire qu'on autorise dans un pseudo-chemin l'inclusion d'arcs "à l'envers")
En utilisant la méthode décrite ci-dessous, on peut construire un pseudo-chemin de S vers T de sorte que
le flux en chaque sommet puisse être augmenté.
Algorithme de Ford-Fulkerson :
1. soit "a" le dernier sommet d'un pseudo-chemin partiel (au départ ce sera S),
2. on étend le pseudo-chemin vers un sommet b si et seulement si
❍ il existe un arc (a,b) ou (b,a)

❍ dans le cas a->b, l'arc de a vers b n'est pas saturé; dans le cas b->a, l'arc de b vers a a un flux
non nul.
3. une fois un pseudo-chemin partiel étendu au sommet b, ce dernier est exclu de la liste des sommets
possibles par l'extension d'autres pseudo-chemins partiels
4. les processus 2) et 3) se répètent jusqu'à atteindre le sommet T;
5. en revenant en arrière le long du pseudo-chemin on ajuste les flux jusqu'à la source S (selon les
méthodes vues en 12.3.).
6. le processus complet (1 à 5) recommence pour découvrir un autre pseudo-chemin
7. si aucun autre pseudo-chemin n'est trouvé, alors l'écoulement existant est optimal.
Exemple : Considérons le réseau décrit par la figure qui suit

Construction du premier pseudo-chemin :


● de S on peut aller vers X (S,X) ou vers Z (S,Z);

● augmentation de S vers X : 4
augmentation de S vers Z : 6
● envisageons l'extension de S vers X (arbitrairement)

❍ le pseudo-chemin (S,X) peut être étendu à :


(S,X,W) avec une augmentation de 3

http://cuisung.unige.ch/std/12.4.html (1 of 3) [07-06-2001 12:31:44]


Structuration des Données Informatiques - 12.4

(S,X,Y) avec une augmentation de 4


❍ envisageons l'extension de X vers Y
❍ Le pseudo-chemin (S,X,Y) peut être étendu à (S,X,Y,T) avec une augmentation de 4.
❍ Le pseudo-chemin de S vers T est complet et représente une augmentation nette de 4 des
flux, on augmente alors de 4 le flux le long de chaque arc.
Le résultat est représenté par la figure suivante :

Construction d'un second pseudo-chemin :


● En partant de la figure 12.10, on ne peut s'étendre que de S vers Z car (S,X) est utilisé à sa capacité
maximum. (S,Z): augmentation de 6.
● de (S,Z) on peut s'étendre vers Y avec une augmentation de 4.(S,Z,Y): augmentation de 4.

● de Y on ne peut aller vers T car (Y,T) travaille déjà à sa capacité maximum, mais on peut s'étendre
vers X en sens inverse. (S,Z,Y,X): augmentation de 4.
● de X on peut s'étendre vers W. (S,Z,Y,X,W): augmentation de 3

● de W on va vers T. (S,Z,Y,X,W,T): augmentation de 3.

On a ainsi un pseudo-chemin offrant une augmentation nette de 3. On parcourt donc ce pseudo-chemin


en augmentant les arcs directs de 3 et en diminuant les arcs inverses de 3. La figure 12.11 illustre cette
situation :

Si l'on essaie de répéter encore une fois le processus, on obtient (à partir de la figure 12.11) :
● de S on peut s'étendre vers Z. (S,Z) augmentation de 3

● de Z on peut s'étendre vers Y. (S,Z,Y) augmentation de 1

● de Y on peut s'étendre vers X. (S,Z,Y,X) augmentation de 1

● à partir de X on ne peut plus s'étendre car . (S,X) (X,W) et (Y,T) travaillent à leur capacité
maximum

http://cuisung.unige.ch/std/12.4.html (2 of 3) [07-06-2001 12:31:44]


Structuration des Données Informatiques - 12.4

L'écoulement obtenu dans la figure 12.11 était donc optimal. Il est à noter qu'en considérant les
pseudo-chemins (S,X,W,T) et (S,Z,Y,T), un autre écoulement optimal de même valeur mais
différemment réparti aurait été obtenu, il est illustré par la figure 12.12:

Table des matières.

http://cuisung.unige.ch/std/12.4.html (3 of 3) [07-06-2001 12:31:44]


Structuration des Données Informatiques - 13.

13. Structures adressables par leur


contenu :
adressage associatif.
Sous certaines conditions les structures de données statiques, comme les tableaux et les fichiers, peuvent
présenter des avantages aussi intéressants, voire plus intéressants, que les structures d'arbres lorsqu'il
s'agit de la recherche d'une valeur particulière. L'idée consiste à placer une information dans une
structure en fonction d'une partie de cette information. Cela devrait permettre de retrouver plus
rapidement la totalité de l'information à partir de la partie qui aura servi au placement. Cela s'appelle
l'adressage associatif.

13.1 Notion de clé

Table des matières.

http://cuisung.unige.ch/std/13.0.html [07-06-2001 12:32:12]


Structuration des Données Informatiques - 13.1

13.1. Notion de clé


Lorsqu'on manipule une série d'objets du même type, il faut pouvoir distinguer ces objets les uns des
autres. La distinction entre ces objets se fait souvent grâce à une partie ou à la totalité de leur contenu.
Par exemple, si l'on travaille sur une série d'enregistrements d'étudiants, le numéro d'étudiant nous
permettra de retrouver les informations associées à un étudiant donné. Le nom et le prénom pourrait aussi
servir à retrouver l'enregistrement d'un étudiant donné, mais il y a toujours le risque d'avoir deux
étudiants ayant le même nom et le même prénom.
Définition : une clé est une partie d'un objet (enregistrement) qui permet de désigner le contenu de cet
objet de manière non-ambiguë.
D'une manière générale un tableau pourra être formé de composantes de la forme
(K, I)
avec :
k = valeur de la clé
I = information associée.
En Pascal :
type Composante = record
K : TypeClef;
I : TypeInfo;
end; { Composante }

Tableau=array[0..max] of composante;
La recherche d'une information dans le tableau peut se faire sur la base de sa clé étant donné qu'il n'a
alors pas d'ambiguïté.
N.B. Il arrive que l'on fasse référence au terme de clé pour désigner la partie d'un élément sur laquelle on
se base pour effectuer une recherche dans une suite d'éléments même si cette partie de l'élément n'est pas
suffisante pour désigner un élément précis sans ambiguïté. On parlera alors de la recherche de la
première occurrence, ou de la dernière, ou même de toutes les occurrences.
Description en terme de type abstrait :
● le rôle d'une structure adressable par son contenu est de stocker un ensemble d'informations de
manière à pouvoir les retrouver le plus rapidement possible à partir d'une partie du contenu de ces
informations appelée "clé". Ces clés doivent toutes être différentes les unes des autres.
● la déclaration d'une telle structure doit spécifier le type de la clé ainsi que celui de l'information
associée à cette clé.
● primitives de manipulation :

1. initialiser la structure à vide,


2. ajouter un élément (insertion d'un couple (K,I)),
3. prédicat indiquant la présence d'un élément avec une clé donnée,

http://cuisung.unige.ch/std/13.1.html (1 of 2) [07-06-2001 12:32:15]


Structuration des Données Informatiques - 13.1

4. accès à l'information associée à une clé donnée pour utilisation ou modification,


5. parcours séquentiel des éléments de la structure dans un ordre quelconque.
N.B. certaines implantations pourront imposer une taille limite à la structure pour raison d'efficacité.

13.2 Recherche séquentielle

Table des matières.

http://cuisung.unige.ch/std/13.1.html (2 of 2) [07-06-2001 12:32:15]


Structuration des Données Informatiques - 13.2

13.2. Recherche séquentielle


Quand on a un tableau T formé de paires :
(K1,I1) (K2,I2) ..... (Kn,In)

et une valeur K qu'il faut rechercher dans le tableau, on peut envisager la méthode la plus simple qui soit
: la recherche séquentielle.
1. initialisation : i 1
2. si i = n+1 alors pas trouvé (échec)
3. si clé (T[i]) = K alors trouvé (succès)
4. si ni 2 ni 3 applicables, continuer la recherche : i i+1 et retour en 2.
Cette méthode est la plus simple mais aussi la plus inefficace car elle comporte deux tests à chaque
itération. On peut l'améliorer de la manière suivante :
1. initialisation clé(T[0]) K, i n
2. trouvé ? si clé(T[i]) = K alors aller à 3 sinon i i-1 et répéter 2
3. fin. Si i = 0 alors pas trouvé sinon trouvé
Cette version présente une amélioration (un seul test par itération) qui se traduit par un temps d'exécution
réduit de 20 % en moyenne (un test de moins à évaluer).
Autre amélioration : la recherche séquentielle peut être grandement améliorée si l'on range, dans le
tableau, les composantes dans l'ordre de fréquence de recherche, c'est-à-dire en mettant dans les
premières places les composantes les plus fréquemment recherchées.

13.3 Recherche dans un tableau ordonné

Table des matières.

http://cuisung.unige.ch/std/13.2.html [07-06-2001 12:32:18]


Structuration des Données Informatiques - 133

13.3 Recherche dans un tableau ordonné


13.3.1. Recherche binaire (ou dichotomique, ou logarithmique)
Le principe de cette méthode est de déterminer dans quelle moitié du tableau ordonné pourrait se trouver
la valeur recherchée et de recommencer ce procédé dans la moitié en question jusqu'à ce que la fraction
du tableau soit vide ou qu'elle ne contienne que la valeur recherchée.
Soit un tableau T de n composantes (triées) tel que :
clé(T[1]) <clé(T[2]) < ..... < clé(T[n])
Les variables g et d sont des indices désignant les limites gauche et droite de la portion de T dans laquelle
se fait la recherche. Ainsi, à n'importe quel moment, la position i de la clé k recherchée est comprise
entre g et d : g <I < d.
1. initialisation g 0, d n+1
2. point milieu i (g+d)/2
x = infimum de x : le plus grand entier qui soit plus petit ou égal à x.

3. si i = g alors pas trouvé (échec).


4. comparaison (à ce point g < i < d) il faut envisager les 3 cas suivants :
cas 1 : si K < clé(T[i]) alors d i et aller 1 à 2.
cas 2 : si k = clé(T[i]) alors trouvé (succès).
cas 3 : si k > clé(T[i]) alors g i et aller à 2.

13.3.2. Recherche par interpolation


Si on recherche un nom commençant par "C" dans un annuaire téléphonique on prend un point de départ
proche du début de l'annuaire. Si on recherche un nom commençant par V on prendra un point de départ
proche de la fin de l'annuaire.
En généralisant on peut déterminer la distance entre deux valeurs x et y comme étant la valeur absolue de
la différence (|x-y|). Il est alors possible, lorsque l'on recherche une clé K entre 2 limites Kinf et Ksup, de
diviser l'intervalle [Kinf, Ksup] proportionnellement à |K - Kinf| et |Ksup - K| si l'on suppose que les
valeurs possibles sont réparties uniformément dans l'intervalle [Kinf,Ksup].

Dans le cas où la répartition ne serait pas uniforme, ce qui est le cas pour des noms dans un annuaire, on
peut utiliser un tableau supplémentaire précisant la répartition. Par exemple, pour le cas de l'annuaire, on
pourrait avoir un tableau (indicé par une lettre) de 26 composantes de type entier. La composante
correspondant à une certaine lettre indique, dans le tableau principal, l'indice du premier nom dont
l'initiale est la lettre en question. Pour rechercher un nom donné, on se base alors sur son initiale pour
déterminer à quelle position du tableau des noms l'on commencera la recherche.

http://cuisung.unige.ch/std/13.3.html (1 of 2) [07-06-2001 12:32:23]


Structuration des Données Informatiques - 133

13.4 Hash-coding ("transformation de clés")

Table des matières.

http://cuisung.unige.ch/std/13.3.html (2 of 2) [07-06-2001 12:32:23]


Structuration des Données Informatiques - 13.3, exercice 1

Recherche dichotomique
Question posée à l'examen de juin 1998
Etant donné les déclarations suivantes:
const ListMax = ...;
type
Status = (Ok, PlusDePlace, DejaPresent);
Tableau = record
List: array[1..ListMax] of Integer;{Tableau trié}
NbEltList: Integer; {Nombre d'éléments contenus dans List}
end;

procedure Trouver(Elt: integer; DansTableau: Tableau;


var Indice: integer; var EltTrouve: boolean);
{Procédure permettant de déterminer si l'élément se trouve dans
le tableau trié en utilisant la recherche dichotomique.}

procedure Inserer(Elt: integer; var DansTableau: Tableau;


var EtatInsertion: Status);
{Procédure permettant d'insérer un élément dans le tableau trié.
La procédure retourne l'état de l'insertion.}

procedure Supprimer(Elt: integer; var DansTableau: Tableau;


var SupprimerOK: boolean);
{Procédure permettant de supprimer un élément dans le tableau trié
List. La suppression est possible si l'élément existe dans le
tableau}
Ecrire le corps des procédures déclarées ci-dessus:
1. Procedure Trouver en tenant compte que la recherche est dichotomique et que la variable Indice fournit
la position de l'élément trouvé dans le tableau, ou à la limite, le dernier indice où pourra se faire une
éventuelle insertion ultérieure de l'élément.
2. Procedure Inserer en considérant tous les cas possibles de l'insertion. La variable
EtatInsertion retourne Ok si l'insertion s'est parfaitement effectuée sinon PlusDePlace ou
DejaPresent, selon la cause respective de l'échec de l'insertion.
3. Procedure Supprimer en considérant que l'on ne laisse pas un trou dans le tableau quand on élimine un
élément.

http://cuisung.unige.ch/std/ex/13/3a.html [07-06-2001 12:32:27]


Structuration des Données Informatiques - 13.4

13.4. Hash-coding ("transformation de clés")


Dans les deux méthodes de recherche examinées jusqu'ici, la recherche s'effectuait en parcourant le
tableau selon une méthode systématique (en éliminant à chaque fois la moitié des éléments possibles, ou
en parcourant tous les éléments dans l'ordre).
Dans le but de construire un algorithme encore plus efficace, il serait très intéressant de pouvoir
déterminer l'adresse où se trouve (ou devrait se trouver) la clé K recherchée, en fonction de K elle-même.
Une telle méthode éviterait d'examiner des positions inintéressantes du tableau et localiserait
immédiatement la clé recherchée.
Cependant, on a généralement un nombre N de valeurs de clés possibles beaucoup plus grand que le
nombre de composantes du tableau. Dans un tel cas il ne peut y avoir de correspondance une à une entre
valeur d'une clé et composante du tableau, c'est-à-dire avoir une position différente du tableau pour
chaque valeur possible de la clé. Par conséquent, quelques sous- ensembles de deux ou de plusieurs clés
possibles correspondront forcément à la même position du tableau.
Ainsi, si on a deux clés K1 et K2 et une adresse A dans le tableau telle que :
H(K1) = A = H(K2)

On dira que K1 et K2 sont en collision sur l'adresse A par la fonction de transformation H.

Pour réaliser un bon algorithme de stockage et de recherche d'information par une méthode de
"hash-coding", il faut déterminer deux choses :
a)
une fonction de hash-code H(K), qui produira le moins de collisions possibles étant donné une
distribution des clés K possibles et un espace d'adresses (ou d'indices) 0 .. M-1 dans un tableau;
b)
une méthode de traitement des collisions permettant de placer et de retrouver efficacement une clé
K parmi toutes celles qui collisionnent à la même position du tableau.
Il n'existe pas de fonction de hash-code universelle qui soit bonne dans n'importe quel contexte. Si l'on ne
sait rien de la distribution des clés K, on choisira une fonction H qui donne une distribution uniforme des
valeurs H(k), avec l'hypothèse que toutes les clés K soient équiprobables.
D'un autre côté, si l'on connaît la distribution des clés K, on pourra alors déterminer une fonction H
dépendante de cette distribution et qui assigne des positions séparées à des groupes de clés
De plus, si l'on connaît à l'avance toute les clés qui doivent être stockées dans le tableau, il existe des
techniques pour déterminer une fonction de hash-code parfaite, qui ne provoquera donc aucune collision.
Ces techniques sont relativement complexes et coûteuses en temps de calcul, mais les fonctions ainsi
produites sont généralement simples et rapides.
Ces fonctions de hash-code parfaites sont parfois employées dans les compilateurs pour retrouver très
rapidement si un identificateur est un mot réservé du langage ou si c'est un identificateur défini par
l'utilisateur. Les mots réservés d'un langage sont en effet fixés une fois pour toutes et sont donc connus à

http://cuisung.unige.ch/std/13.4.html (1 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

l'avance.
Même si l'on a des fonctions H parfaitement uniformes, la probabilité d'une collision est grande, sauf
pour des tableaux très faiblement occupés. Par exemple, on peut faire un parallèle avec le paradoxe de
l'anniversaire, qui dit que parmi 23 personnes ou plus, la probabilité que 2 personnes aient leur
anniversaire le même jour est supérieure à 50 %, et que si l'on a plus de 88 personnes la probabilité est la
même que 3 personnes aient leur anniversaire le même jour.
Transposé à notre problème, cela signifie que, pour un tableau de 365 positions, contenant 88 clés ou
plus, dont les adresses ont été calculées uniformément et aléatoirement, on aura plus de 50 % de chance
d'avoir une collision de deux ou trois clés à la même adresse.
Parmi les nombreuses méthodes de traitement des collisions, nous en retiendrons deux :
● le chaînage externe,

● l'adressage ouvert.

13.4.1. Chaînage externe


Cette méthode est la plus simple. Elle consiste à déclarer un tableau de pointeurs, de façon à pouvoir
construire, à partir de chaque position du tableau, une chaîne qui contient toutes les clés qui ont été
envoyées par la fonction H sur cette adresse. Il est possible d'organiser la chaîne par ordre croissant ou
décroissant des clés, ce qui permet d'optimiser la recherche ultérieure d'une clé donnée.
La procédure suivante permet d'initialiser un tel tableau :
const MaxElements = ...;
MaxIndice = MaxElements-1;

type Lien = ^Noeud;


Noeud = record
Cle : integer;
Info : integer;
Suivant : Lien;
end; { Noeud }

var Tableau: array [0..MaxIndice] of Lien;

procedure Initialisation;
var i : integer;
begin
for i := 0 to MaxIndice do
Tableau[i] := nil;
end; { Initialisation }

http://cuisung.unige.ch/std/13.4.html (2 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

La figure 13.1 illustre la structure utilisée pour le chaînage externe. On peut noter que certaines entrées
du tableau correspondent à une chaîne vide.
Les procédures d'insertion et de recherche vues dans le chapitre sur les chaînes s'appliquent sans autre
dans le cas du traitement des collisions par chaînage externe. Dans l'exemple qui suit, nous supposerons
que les clés sont de simples caractères et que chaque occurrence d'une même clé doit être stockée
séparément (pour simuler les collisions). La chaîne associée à chaque entrée du tableau est indiquée
verticalement en dessous de l'entrée correspondante.
k : C E C I E S T U N E X E M P L E
ord(k) : 3 5 3 9 5 19 20 21 14 5 24 5 13 16 12 5
H(k) : 3 5 3 9 5 8 9 10 3 5 2 5 2 5 1 5
H(k) = ord(k) mod M et M = 11 (dimension du tableau)
La structure de donnée correspondante peut être illustrée de la manière suivante :

D'autres alternatives existent quant à l'organisation d'un tableau de h-coding avec chaînage externe. Par
exemple :

http://cuisung.unige.ch/std/13.4.html (3 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

Ce qui donnerait pour notre exemple la structure de la figure 13.4.

13.4.2. L'adressage ouvert


Si le nombre d'éléments à placer dans le tableau est déterminé par avance, il est alors possible d'éviter de
mettre une partie des éléments à l'extérieur du tableau (par chaînage). Plusieurs méthodes ont été
proposées pour stocker par hash-coding N éléments dans un tableau de dimension M N, en utilisant les
positions inoccupées pour recevoir les éléments qui collisionnent. Ce sont les méthodes dites de
l'adressage ouvert.
Les méthodes d'adressage ouvert pour résoudre des collisions peuvent se représenter à l'aide de
l'algorithme général suivant :

var T : array[0 .. M] of Element;


Etat : (Trouve, PasTrouve, Recherche);

h0 := H(k);
i := 0;
hi := h0;
Etat := Recherche;
repeat
if T[hi].Cle = k then Etat := Trouve
else if T[hi].Cle = Vide then
Etat := PasTrouve
else begin {traitement d'une collision}
i := i+1;
hi := (h0 + G(i)) mod (M+1)
end { else }

http://cuisung.unige.ch/std/13.4.html (4 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

until (Etat <> Recherche) or (i = M);

case Etat of
Trouve : { modif. de T[hi] possible };
PasTrouve: { insertion en T[hi] };
Recherche: { insertion impossible, table pleine };
end; { case }
avec:
h0 = indice primaire
G(i) = fonction de déplacement dans le tableau
i = ordre de la collision
Plusieurs façons de réaliser l'adressage ouvert sont possibles.

13.4.2.1. solution linéaire

On inspecte les positions suivantes en partant de h0 et en supposant le tableau circulaire avec :


G(i) = i
ce qui donne :
h0 = H(k)
hi = (h0 + i) mod (M+1) ( mod : pour rendre le tableau circulaire)

Cette solution présente l'inconvénient de favoriser la création d'amas autour des indices primaires (cf.
exemple de la figure 13.5).
On peut envisager une variante sous la forme :
G(i) = i * Constante
avec M et Constante étant des nombres premiers (différents) pour obtenir de meilleurs résultats.

13.4.2.2. solution quadratique

Dans le cas idéal il faudrait que la fonction G(i) répartisse les indices secondaires hi aussi uniformément
que possible sur les autres positions du tableau; mais cela est trop complexe et parfois trop coûteux. Un
bon compromis est la solution quadratique qui vise à éloigner les points de collision le plus loin possible
de l'indice primaire en fonction de l'ordre de la collision.
On choisit alors : G(i) = i2
d'où:
h0 = H(k)
hi = (h0 + i2) mod (M+1)

Il faut noter que cette solution ne permet de parcourir que la moitié des positions du tableau. Il y a donc
le risque de ne pas pouvoir placer un nouvel élément, même s'il existe une place libre dans le tableau. Il

http://cuisung.unige.ch/std/13.4.html (5 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

suffit, en effet, que cette place libre se trouve dans la moitié du tableau qui n'est pas parcourue.
Le fait que seule une moitié du tableau est parcourue tient à l'utilisation de la fonction Modulo pour
simuler la circularité. On peut en effet démontrer que la i-ème collision fournit la même position que la
(M-i)-ème collision (on suppose ici que l'intervalle d'indices est 0..M-1):
(h0 + i2) mod M = (h0 + (M-i)2) mod M

Il suffit pour cela de développer (M-i)2, qui donne M2-2iM+i2, soit (M-2i)M + i2. Or (x+yM) mod M = x
du fait que le modulo consiste en le reste de la division entière. On obtient donc bien :
(h0 + (M-i)2) mod M = (h0 + ((M-2i)M+i2) mod M = (h0 + i2) mod M
Exemple : Algorithme d'insertion dans une table de h-code avec traitement linéaire des collisions.

const MaxElements = ...;


MaxIndice = MaxElements-1;
CleCaseVide = MaxInt;

type Element = record


Cle : integer;
Info : .....;
end; { Element }

var T: array[0..MaxIndice] of Element;

function H(k : integer) : integer;


begin
H := k mod MaxElements;
end; { H }

procedure Initialisation;
var i: integer;
begin
for i := 0 to MaxIndice do
T[i].Cle := CleCaseVide;
end; { Initialisation }

function Insert(X:integer,I:Info) :integer;


var H0, hi : integer;
begin
H0 := H(X);
hi := H0;

while (T[hi].Cle <> CleCaseVide) and


(T[hi].Cle <> X) do begin
hi := (hi+1) mod MaxElements;

http://cuisung.unige.ch/std/13.4.html (6 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

if hi = H0 then begin
writeln('tableau plein');
exit(program);
end; { if }
end; { while }

if T[Hi].Cle = CleCaseVide then begin


T[hi].Cle := X;
T[Hi].Information := I;
end; { if }
Insert := hi;
end; { Insert }
● la constante "CleCaseVide" sert à désigner une position vide du tableau.

● la fonction "Insert" retourne l'indice de la position où X a trouvé place.

Exemple : Soit la suite suivante de clés alphabétiques k :


k : C E C I E S T U N E X E M P L E
ord(k): 3 5 3 9 5 19 20 21 14 5 24 5 13 16 12 5
H(k) : 3 5 3 9 5 0 1 2 14 5 5 5 13 16 12 5
N = 16 (Nombre de clés)
M = 19 : dimension du tableau qui doit être > N
H(k) = k mod M.
L'insertion des clés dans le tableau s'effectue selon la figure 13.5 (les [] indiquent les positions examinées
pour effectuer l'insertion). On voit, sur cet exemple la conséquence de la formation d'amas qui est
d'obliger à consulter des positions de valeurs d'indices primaires différentes avant de trouver des
positions libres. En l'occurrence, l'insertion du 4ème 'E' nécessite l'examen des positions occupées par X
et I de h-code différents.

http://cuisung.unige.ch/std/13.4.html (7 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

Il existe une troisième possibilité pour un traitement des collisions par adressage ouvert; c'est celle qui
consiste à calculer une seconde fonction de H-code pour déterminer le déplacement. Cette méthode est
appelée double-h-code.
Dans ce cas:
G(i) = i * H2(k) i=1,2, ..
h0 = H1(k)
hi = (h0 + i * H2(k)) mod M

La deuxième fonction de h-code, H2(k), doit être choisie avec certaines précautions. Par exemple :
● Il ne faut pas que H2(k) = 0, car cela entraîne une boucle infinie dans le traitement des collisions
(cf. algorithme précédent).
● H2 doit être différente de H1 sinon un autre phénomène d'amas se produit.

Par rapport à une fonction H1(k) = k mod M telle que nous l'avons envisagée dans les exemples, on peut
considérer que la fonction
H2(k) = 1+( k mod (M-2))

constitue une bonne deuxième fonction de h-code si M et M-2 sont tous deux premiers. Le traitement des
collisions par double-h-code est, en quelque sorte, un traitement personnalisé de la collision d'une clé k
en fonction de la valeur de cette clé !

http://cuisung.unige.ch/std/13.4.html (8 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4

Il existe d'autres méthodes de traitement de collisions faisant intervenir des algorithmes plus complexes,
mais, dans la plupart des cas, les méthodes décrites ici suffisent à résoudre les collisions de manière
satisfaisante.
Quelle que soit la variante d'adressage ouvert utilisée, on peut essayer de réduire le nombre d'éléments
ayant subi une collision en donnant la priorité à un nouvel élément sur un élément déjà présent dans le
tableau si ce dernier a déjà subi une collision et n'est donc pas placé conformément à la valeur de son
h-code. Cela implique, pour la solution quadratique, de retrouver le nombre de collisions déjà subies par
l'élément du tableau à déplacer, étant donné que les déplacements sont fonction du nombre de collisions.
Pour plus de détails sur les différentes méthodes de traitement de collisions, consulter :
D.E. Knuth
"The Art of Computer Programming",
vol. III, Sorting and Searching, pp. 506-549

13.5 Les fonctions de h-code

Table des matières.

http://cuisung.unige.ch/std/13.4.html (9 of 9) [07-06-2001 12:33:59]


Structuration des Données Informatiques - 13.4, exercice 1

exercice suivant

Hash-code
Question posée au contrôle continu du 16 juin 1997
Soient deux fonctions de hash-code H1 et H2 retournant les valeurs numériques suivantes:
mot: les examens du premier cycle pour lesquels il n y aurait qu
H1(mot): 15 6 11 1 3 6 2 5 7 15 2 9
H2(mot): 8 5 12 14 2 10 17 7 5 13 8 22
mot: un examen oral a subir sont admis si chaque note atteint quatre
H1(mot): 13 18 16 11 10 23 8 89 20 15 22
H2(mot): 9 6 1 4 3 7 13 6 17 2 12 16

On suppose que l'on utilise la méthode du chaînage externe à partir d'un tableau de 11 positions, en
utilisant H1 pour déterminer quelle chaîne utiliser et H2 pour trier les éléments d'une même chaîne
(c'est-à-dire qu'au sein d'une même chaîne, un mot mi sera placé avant un autre mot mj si
H2(mi)<H2(mj)). Dessinez le tableau des chaînes que l'on obtient, en mettant un mot et le résultat de H2
pour ce mot dans chaque élément de chaîne. Indiquez quelle expression basée sur H1 vous avez utilisée
pour le choix de la chaîne.
Solution

exercice suivant

http://cuisung.unige.ch/std/ex/13/4a.html [07-06-2001 12:34:01]


Structuration des Données Informatiques - 13.5

13.5 Les fonctions de h-code


Dans les discussions précédentes nous avons supposé l'existence d'une fonction H qui fasse correspondre,
avec toutes les exigences nécessaires (rapidité, uniformité, etc.), au domaine de valeurs des clés K celui
des adresses [0 .. M-1] du tableau de h-code. La question est alors : comment construire (ou choisir) une
telle fonction H ?
De toutes les formes de fonctions H (cf. Knuth, vol. III déjà cité) nous n'en retiendrons que 2 :
a)
Les fonctions basées sur une méthode de division.
b)
Les fonctions basées sur une méthode de multiplication.
Le papier suivant constitue une excellente synthèse sur les fonctions de h-code et contient une
bibliographie très étendue sur ce sujet :
G.D. Knott, "Hashing functions".
The Computer Journal, vol. 18, No. 3, 1975, pp. 265-278.

13.5.1 Les méthodes de division


La méthode de division pour une fonction de h-code est celle que nous avons employée dans les
exemples traités jusqu'ici. Elle consiste à prendre comme indice dans le tableau le reste de la division de
la clé (il faut qu'elle soit entière !) par la taille du tableau.
c'est-à-dire H(k) = k mod M (1)
Il est toujours possible, par un procédé adéquat, de transformer une clé quelconque en une valeur entière,
ne serait-ce qu'en déterminant son ordinal dans un ensemble de valeurs possibles.
Dans la formule (1) il faut cependant choisir M avec soin. Ainsi, si M est une puissance de la base
numérique de la machine, M = 2r, alors le calcul de H(k) revient à isoler les r bits les moins significatifs
de la clé K (par divisions entières successives dont on ne considère que le reste).
Prendre M parmi les nombres premiers est un pas dans la bonne direction, cependant il faut alors faire
attention au fait que dans certains cas le calcul de H(k) revient à simplement superposer les valeurs des
composantes d'une clé. Par exemple :
● Soit une clé constituée de 3 caractères C1 C2 C3 (pris dans l'ensemble des 64 caractères EBCDIC

par exemple). Soit une table de 4097 positions, ce qui correspond à 212 + 1 (ou 642+1) et démontré
par Fermat comme étant un nombre premier.
Si pour la fonction de h-code on considère
H(C1 C2 C3) = (C1 C2 C3) mod (212 + 1)

et, si on exprime (C1 C2 C3) comme une combinaison linéaire dans la base 64, on obtient :
H(C1 C2 C3) = (C1 * 642 + C2 * 64 + C3) mod (642 + 1)

http://cuisung.unige.ch/std/13.5.html (1 of 5) [07-06-2001 12:35:06]


Structuration des Données Informatiques - 13.5

alors, si (C3 + 64*C2) > C1 , le quotient de (C1 C2 C3) divisé par 212+1 est simplement C1 et le
reste est de la forme :
(C1*642 + C2*64 + C3) - (C1*(642 + 1)) = (C2*64 + C3) - C1

Ainsi exprimée en base 64, la valeur de H(C1 C2 C3) est (C2 C3-C1)64 , qui est une simple
superposition des caractères de la clé K. On voit donc que différentes permutations des mêmes
lettres donneront le même résultat et augmenteront ainsi le risque de collision.
En généralisant on dira que choisir la taille M d'un tableau de h-code comme étant de la forme
br a,

où b est la base de l'ensemble des caractères et r ainsi que a sont de petites constantes, est à éviter!
Si ce choix est évité, l'expérience montre que la méthode de division présente de bons résultats. De
plus la méthode de division semble (expérimentalement) se combiner valablement avec un
traitement de collisions par chaînage externe.

13.5.2 Les méthodes multiplicatives


Ces méthodes reposent souvent sur les propriétés de distribution de nombres particuliers. Ainsi, si
l'on considère le nombre d'or

et que l'on calcule ses multiples i* pour i [1,10], que l'on isole la partie fractionnaire de ces
multiples et que l'on détermine le plus grand entier plus petit ou égal à 10 fois la partie
fractionnaire, on obtient le tableau suivant (avec {i* } = (i* - i* ), où { } signifie partie
fractionnaire) :
plancher de
multiple partie fract. 10*partie fract.
i i* {i* } 10*{i* }

1 0.618034 .618034 6
2 1.236068 .236068 2
3 1.854102 .854102 8
4 2.472136 .472136 4
5 3.090170 .090170 0
6 3.708204 .708204 7
7 4.326238 .326238 3
8 4.944272 .944272 9
9 5.562306 .562306 5
10 6.180340 .180340 1

http://cuisung.unige.ch/std/13.5.html (2 of 5) [07-06-2001 12:35:06]


Structuration des Données Informatiques - 13.5

L'examen de ce tableau appelle quelques remarques :


1. Les dix premières valeurs de la dernière colonne sont des permutations des nombres entiers
entre 0 et 9. Ainsi, on peut utiliser, comme indice primaire, la partie fractionnaire du
multiple du nombre d'or, mis à l'échelle de la taille de la table et arrondi de manière
appropriée.
2. Si on considère les parties fractionnaires des multiples du nombre d'or {i* } (3ème colonne
du tableau), on constate que ces valeurs sont comprises dans l'intervalle [0,1] et qu'elles le
subdivisent de manière particulièrement subtile. En effet chaque nouvelle valeur partage le
plus grand intervalle restant (c'est-à-dire qui n'est pas le résultat de la dernière subdivision)
en parties respectant le nombre d'or. Ceci est illustré par la figure 13.6.

Ces différentes constatations peuvent conduire à la généralisation suivante. Si R est un nombre


irrationnel, alors la suite obtenue par :
{R}, {2R}, ... {nR} où {x} indique la partie fractionnaire de x
lorsqu'elle est placée dans l'intervalle [0,1], le subdivise en M + 1 segments de façon identique au
nombre d'or .
Ainsi, si l'on a une table de dimension M et une clé entière K qui est utilisée comme multiplicateur
de R, on peut construire la fonction de h-code suivante :
H(K) = M * {KR} (2)

http://cuisung.unige.ch/std/13.5.html (3 of 5) [07-06-2001 12:35:06]


Structuration des Données Informatiques - 13.5

La formule (2) est générale et, dans une application réelle, il faut, si possible, une fonction de
h-code qui :
a)
soit rapide à calculer;
b)
minimise les collisions.
Un développement complexe (cf. Knuth) permet de montrer que ces objectifs sont atteints si l'on
considère la formule suivante :
étant donné un nombre irrationnel R et un tableau de stockage de taille M=2m, on
détermine un entier A tel que R A/w
où w représente le nombre d'entiers représentables sur 1 mot machine de r bits (w =
2r)
on obtient alors :
H(k) = M * ((k* A/w ) mod 1) = (M*(A*k mod w)) / w
(k représente la valeur entière de la clé) ce qui revient à multiplier k par A, tronquer le
résultat à 1 mot machine et conserver les m bits de gauche ( M=2m).
En conclusion, on peut dire que la méthode du h-code constitue une alternative à la structure
d'arbre binaire lorsque :
1. il n'est pas nécessaire de gérer dynamiquement les données;
2. il n'est pas important de traiter les cas les plus défavorables avec un maximum de
performances (ce que font les arbres binaires équilibrés en garantissant un nombre de
comparaisons maximum de l'ordre de log2(N), mais ce que ne peut garantir la méthode du
h-code à cause des taux de collisions qui sont non déterministes);
3. il n'est pas demandé d'effectuer d'autres opérations que la recherche, sur les données une fois
structurées. En effet il est impossible de trier le tableau de h-code en déplaçant les valeurs
(d'autres solutions existent mais elles sont moins efficaces, telles que le tri par pointeurs
sans déplacer les données).
A noter que si l'on désire pouvoir supprimer des éléments du tableau en cours de traitement, un
algorithme de traitement de collisions par chaînage externe est préférable car plus efficace.

http://cuisung.unige.ch/std/13.5.html (4 of 5) [07-06-2001 12:35:06]


Structuration des Données Informatiques - 13.5

14. Structures à support mixte


Table des matières.

http://cuisung.unige.ch/std/13.5.html (5 of 5) [07-06-2001 12:35:06]


Structuration des Données Informatiques - 14.0

14. Structures à support mixte


Certaines applications nécessitent l'utilisation d'un très grand volume de données. Si ces données n'ont,
comme durée de vie, que la durée d'exécution du programme, une technique, dite de mémoire virtuelle,
sera mise en oeuvre pour permettre l'utilisation de ce grand volume de données de manière aussi simple
que possible. Par contre, si ces données doivent être rémanentes, on utilisera des fichiers séquentiels
indexés ou des B-arbres pour les stocker.

14.1. Mémoire virtuelle

Table des matières.

http://cuisung.unige.ch/std/14.0.html [07-06-2001 12:35:37]


Structuration des Données Informatiques - 14.1

14.1. Mémoire virtuelle


On désigne, sous ce terme, la possibilité qui consiste à utiliser une partie des mémoires secondaires
(principalement les disques) comme extension de la mémoire vive. On a donc un espace d'adresses
théorique beaucoup plus grand que l'espace d'adresses réel. Ceci est généralement réalisé par une
combinaison matériel-logiciel au niveau du système d'exploitation, en étendant l'espace des adresses
disponibles de manière transparente à l'utilisateur par une technique de pagination : la mémoire est
allouée aux programmes par blocs de taille fixe appelés "pages". Nous n'irons pas plus loin dans la
description de cette technique, du fait même de sa transparence pour l'utilisateur.

14.2. Fichiers séquentiels indexés

Table des matières.

http://cuisung.unige.ch/std/14.1.html [07-06-2001 12:35:39]


Structuration des Données Informatiques - 14.2

14.2. Fichiers séquentiels indexés


Cette structure a été popularisée par son inclusion dans le langage Cobol (ISAM : Index Sequential
Access Method). Elle permet de stocker séquentiellement un grand volume de données sur disque de
manière à pouvoir relire ces données, soit séquentiellement, soit de manière aléatoire, tout en ayant un
temps d'accès relativement favorable. Les données à stocker dans cette structure doivent répondre aux
exigences suivantes :
● posséder une clé de tri

● être en grande partie disponibles lors de la phase de création (peu d'ajouts ultérieurs)

14.2.1. Implantation
L'implantation des fichiers séquentiels indexés se base sur le fait que l'accès aux mémoires de masses
s'effectue par blocs entiers. Etant donné que le temps d'accès à un bloc du disque est beaucoup plus long
que le temps de transfert de ce bloc vers la mémoire centrale, plus ces blocs sont grands, plus favorable
sera le temps d'accès par donnée élémentaire mais plus grande sera l'occupation mémoire des tampons
d'entrée/sortie.
Comme son nom peut le laisser deviner, un fichier séquentiel indexé comporte deux parties séparées :
● les données stockées séquentiellement dans l'ordre croissant de leur clé

● une arborescence d'index permettant de trouver rapidement une donnée déterminée par sa clé.
Le fichier comporte donc deux sortes de blocs : les blocs d'index et les blocs de données (certaines
implantations stockent les données et les index dans deux fichiers différents). L'utilisateur a la possibilité
d'indiquer le facteur de blocage qui est le nombre minimum de données qui doivent tenir dans un bloc
(une donnée ne peut pas être à cheval sur deux blocs).
Les blocs d'index sont construits automatiquement par le système au fur et à mesure que l'utilisateur
introduit des données dans le fichier. Ils contiennent des paires (K,A) où A est l'adresse d'un bloc et K la
clé la plus petite de ce bloc, ce bloc pouvant être aussi bien un bloc d'index qu'un bloc de donnée. On
construit donc une structure hiérarchisée de blocs où seules les feuilles contiennent des données. Pour
rendre les choses plus claires, prenons un exemple :
Représentons les données par des paires (Ki,Di) où Ki est la clé et Di le reste de la i-ème donnée et les
index par des paires (Ki,Ai). Supposons que l'on ait n données par bloc de donnée (facteur de blocage) et
m index par bloc d'index.
Les données peuvent être représentées de la manière suivante :

http://cuisung.unige.ch/std/14.2.html (1 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2

Fig. 14.1. Blocs de données.


on a alors une première "couche" de blocs d'index, qui est représentée par la figure suivante :

Fig. 14.2. Premier niveau de blocs d'index.


puis une deuxième couche de blocs d'index, telle qu'illustrée par la figure 14.3. Ainsi de suite, jusqu'à ce
qu'il n'y ait plus qu'un seul bloc d'index au sommet de la pyramide (racine de l'arborescence).
Avec n données par bloc, on aura NbDonnées/n blocs de donnée. Avec m index par bloc, le sommet de la
pyramide peut contenir m index, le niveau en dessous peut en contenir m2, le i-ème niveau d'index
contiendra mi index. Le nombre d'accès disque à effectuer pour trouver une donnée quelconque sera donc
de :
logm(NbDonnées/n) + 1

Fig. 14.3. Deuxième niveau de blocs d'index.

14.2.2. Mise-à-jour de fichiers séquentiels indexés


Comme nous l'avons dit plus haut, la structure de séquentiel indexé n'est pas prévue pour subir de
nombreuses mises-à-jour. Certains moyens sont mis à disposition de l'utilisateur pour faciliter l'insertion
ultérieur de nouvelles données.
1. il est possible de spécifier, lors de la création, le taux de remplis sage initial des blocs de données.
En prenant un taux inférieur à 100%, on laisse ainsi de la place pour insérer de nouvelle données
sans avoir à modifier l'arborescence des index. L'inconvénient de cette solution est d'augmenter la

http://cuisung.unige.ch/std/14.2.html (2 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2

taille du fichier dans les mêmes proportions provoquant ainsi un certain gaspillage de place.
2. chaque bloc de donnée contient un "pointeur" vers un bloc de débordement. Si une donnée doit
être insérée dans un bloc qui est plein, le système va regarder dans le (ou les) blocs de
débordement pour y insérer la donnée. S'il n'y a pas encore de bloc de débordement pour le bloc de
données plein, le mécanisme d'insertion crée ce bloc de débordement et ajoute le lien
correspondant dans le bloc qui a débordé. Chaque bloc de données peut avoir un, voire plusieurs
blocs de débordement, qui forment alors une chaîne.
Il faut noter que l'accès à ces blocs de débordement se fait séquentiellement. Cette solution n'est donc
acceptable que si le nombre de blocs de débordement est réduit et si ces blocs sont répartis sur l'ensemble
des données. Si le fichier doit régulièrement subir des mises-à-jour, il faut alors périodiquement sauver
ce fichier séquentiellement sur une bande magnétique (ou sur disque s'il y a la place) pour pouvoir
ensuite recréer toute la structure de séquentiel indexé de façon à éliminer tous les blocs de débordement.
Exemple : Si, après avoir construit la structure de la figure 14.3, l'on désire insérer une nouvelle valeur
dont la clé est comprise entre Kn et Kn+1, il faudra créer un bloc de débordement lié au premier bloc de
données pour contenir cette nouvelle valeur. Ceci permet de respecter l'ordre de tri des données sans
devoir modifier les blocs d'index.

Fig. 14.4. Exemple de bloc de débordement.


Il est à noter que les deux méthodes décrites plus haut ne sont pas mutuellement exclusives. On peut en
effet très bien prévoir un taux de remplissage initial inférieur à 100% tout en permettant la création de
blocs de débordement si besoin est.
En Pascal, le plus simple serait de stocker les index et les données séparément dans deux fichiers à accès
direct. Les déclarations correspondant à une telle structure sont :

const FacteurDeBlocage = ...;


NbIndexParBloc = ...;

http://cuisung.unige.ch/std/14.2.html (3 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2

type TypeIndex = integer;


TypeCle = ...;
Donnee = record
Cle: TypeCle;
Info: ...;
end; { Donnee }
BlocDonnee = record
Contenu:array[1..FacteurDeBlocage]
of Donnee;
Debordement: TypeIndex;
end; { BlocDonnee }
BlocIndex = record
Cles: array[1..NbIndexParBloc]
of TypeCle;
Index:array[1..NbIndexParBloc]
of TypeIndex;
end; { BlocIndex }

var FIndex: file of BlocIndex;


FDonnees: file of BlocDonnee;
Il manque encore le moyen d'accéder à la racine de l'arborescence d'index. Le plus simple est
généralement de fixer ce bloc racine comme étant le tout premier bloc du fichier d'index. Lors de la
phase de création, ce bloc n'est connu que lorsque toute l'arborescence est construite. Il est donc écrit en
dernier.
Lors de la lecture d'un fichier séquentiel indexé, ce bloc racine étant très souvent utilisé, il a intérêt à être
lu dès l'ouverture du fichier et conservé en mémoire centrale pendant toute la durée du traitement.

14.2.3. Clés secondaires


Nous avons vu que les données, dans un fichier séquentiel indexé, devaient être triées selon la clé
d'accès. Si les données contiennent une autre information sur laquelle existe une relation d'ordre (clé
secondaire), il est possible de créer une autre arborescence d'index permettant d'accéder presque aussi
facilement aux données selon cette clé secondaire. Il est nécessaire, pour cela, que la couche d'index la
plus inférieure (celle dont les index pointent vers des blocs de données) contienne un index pour chaque
donnée du fichier, contrairement ce qui était le cas pour la clé principale qui ne nécessitait qu'un seul
index par bloc de données.
La taille de la clé secondaire pouvant être différente de celle de la clé principale, le nombre de clés
secondaires par bloc d'index (p) peut être différent de celui des blocs d'index de clé principale (m). On
obtient alors la structure suivante (où Ki représente une clé principale et Si une clé secondaire) :

http://cuisung.unige.ch/std/14.2.html (4 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2

Fig. 14.5. Utilisation d'une clé secondaire. Seuls les deux premiers niveaux d'index sont illustrés ici.
L'accès à une donnée par une clé secondaire est à peine plus lent que par la clé principale puisque le
nombre d'accès disque à effectuer pour trouver une donnée quelconque est de :
logm(NbDonnées) + 1

Il est à noter qu'une fois que les blocs d'index de clé secondaire sont construits, il n'est plus possible
d'ajouter de nouvelles données sans rendre ces index obsolètes. Pour s'en convaincre, il suffit de se
rappeler que l'ajout d'une nouvelle donnée n'implique pas de modification de l'arborescence des blocs
d'index de clé principale alors que cet ajout impliquerait obligatoirement une modification d'au moins un
bloc d'index de clé secondaire, puisqu'il doit y avoir un index secondaire par donnée au niveau le plus bas
de l'arborescence d'index de clé secondaire.

14.2.4. Variante plus ancienne


La description de la structure de séquentiel indexé que nous avons pu voir jusqu'ici n'implique aucune
dépendance particulière vis-à-vis de la structure physique des mémoires secondaires utilisées (les disques
magnétiques). Cette structure est donc totalement compatible avec un environnement multi-utilisateurs.
La première implantation d'une telle structure semble avoir été faite par le constructeur CDC pour leurs
séries 6600 et CYBER.
Toutefois, antérieurement à cela, des implantations plus restrictives ont été faites de la structure de
séquentiel indexé (en particulier par IBM). Ces implantations étaient en effet assez calquées sur la
structure physique des mémoires de masse, à une époque où les ordinateurs fonctionnaient

http://cuisung.unige.ch/std/14.2.html (5 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2

principalement en traitement par lots (batch processing). Ces premières implantations n'utilisaient que
deux (parfois trois ou quatre) niveaux d'index:
Un disque magnétique est composé de plusieurs surfaces magnétiques avec des pistes concentriques.
L'ensemble des pistes numéro i des différentes surfaces constitue le cylindre numéro i. Le disque est
donc constitué de c cylindres chacun subdivisé en p pistes (s'il y a p surfaces utilisables). Cette
hiérarchisation est utilisée pour constituer deux niveaux de blocs d'index. La première piste de chaque
cylindre est utilisée comme bloc d'index pour les données présentes sur le reste du cylindre (p-1 pistes).
Le premier cylindre du disque est utilisé comme blocs d'index pour repérer le cylindre pouvant contenir
la clé recherchée.
Si les pistes sont suffisamment grandes, on peut envisager de découper chaque piste en secteurs et utiliser
le premier secteur de chaque piste comme bloc d'index sur les données de la piste, constituant ainsi un
niveau supplémentaire dans la hiérarchie d'accès. A l'autre extrémité de la hiérarchie il est aussi possible
de rajouter un niveau d'indexation. On peut en effet constituer un bloc d'index (appelé index maître) qui
permette d'accéder directement au bon bloc d'index du premier cylindre.

14.3. B-arbres (ou arbres de Bayer)

Table des matières.

http://cuisung.unige.ch/std/14.2.html (6 of 6) [07-06-2001 12:36:22]


Structuration des Données Informatiques - 14.2, exercice 1

Exercice suivant

Fichiers séquentiels indexés et B-arbres


Question posée à l'examen du 15 octobre 1996
a)
Quelle est la taille de l'espace disque nécessaire pour être sûr de pouvoir créer un fichier séquentiel
indexé qui doit contenir 1'000'000 éléments, sachant qu'un élément nécessite une place de 76
octets, dont une clé de 8 octets, que les blocs de données ont une taille de 1536 octets et les blocs
d'index 1024 octets et que les pages de données sont initialement remplies à 80%? On supposera
que les références, dans un bloc d'index, à un autre bloc d'index ou à un bloc de donnée, tiennent
sur 4 octets. Justifiez votre réponse et donnez le détail des calculs. En plus de la taille de l'espace
disque, vous indiquerez, entre autres, le nombre de blocs de données, la profondeur de
l'arborescence des blocs d'index et le nombre de blocs d'index à chaque niveau.
b)
Pour les mêmes données (même nombre et même taille d'élément), quelle sera la taille de l'espace
disque nécessaire et le nombre de niveaux si l'on utilise une structure de B-arbre avec des pages de
1024 octets (donnez les valeurs minimales et maximales possibles, dans l'hypothèse de pages à
moitié pleines ou entièrement pleines)? Là aussi, justifiez votre réponse et donnez le détail des
calculs: ordre du B-arbre, nombre de pages minimum et maximum, ...
N.B. Comme pour le séquentiel indexé, les références à une page occupent 4 octets.
Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/14/2a.html [07-06-2001 12:36:50]


Structuration des Données Informatiques - 14.3

14.3. B-arbres (ou arbres de Bayer)


Cette structure a pour but de "paginer" un arbre binaire sur une mémoire de masse. Plus souple
d'utilisation que le séquentiel indexé (mises-à-jour plus faciles), elle a rapidement supplanté cette
dernière. Cette structure a été formulée en 1970 par R. Bayer dans une série d'articles :
● "Organization and maintenance of large ordered indexes"
Acta Informatica Vol 1 No 3 (1972) pp. 173-189.
● "Binary B-trees for virtual memory"
Proceedings of the ACM 1971 SIGFIDET Nov. 1971 pp. 219-235.
● "Symetric binary B-trees : Data Structures and maintenance algorithms"
Acta Informatica Vol 1 No 4 (1972) pp. 290-306.
Pour comprendre l'organisation des B-arbres supposons que l'on ait un arbre binaire tel que :

Fig. 14.6. Exemple d'arbre binaire.


Pour simplifier les explications qui suivent, nous allons légèrement pencher cet arbre de façon que les
pointeurs "gauche" soient horizontaux et les pointeurs droits verticaux. Nous obtenons alors la figure
suivante :

Fig. 14.7. Arbre binaire "penché".


on regroupe alors les noeuds d'une même ligne reliés entre eux par le pointeur gauche de la façon
suivante :

http://cuisung.unige.ch/std/14.3.html (1 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

Fig. 14.8. Regroupement en "pages".


On peut alors décider de regrouper chaque ligne dans une structure (page) qui contient donc une série de
noeuds (tableau) ayant des valeurs croissantes avec, tout à gauche, entre chaque noeud et tout à droite, un
pointeur vers une page contenant des valeurs respectivement plus petites, intermédiaires et plus grandes.
Etant donné que les noeuds sont stockés séquentiellement dans une page, tous les pointeurs horizontaux,
sauf le plus à gauche, peuvent être supprimés.
Si l'on appliquait cette méthode de regroupement en pages telle quelle, on aurait un taux d'occupation des
pages très variable, en fonction de la répartition des noeuds dans l'arbre. Ce n'est donc pas directement
de cette façon que l'on procède pour construire un B-arbre. Pour éviter de trop grandes variations
dans l'occupation des pages, on introduit lors de la construction d'un B-arbre une importante contrainte
supplémentaire qui est la suivante :
Chaque page d'un B-arbre doit contenir entre n et 2n noeuds, n étant une constante arbitraire. Cela
signifie qu'une page doit toujours être au moins à moitié remplie. Seule la première (racine) peut faire
exception à cette règle et peut même, éventuellement, ne contenir qu'un seul noeud.
En respectant cette contrainte, nous allons formuler des algorithmes simples pour la recherche, l'insertion
et la suppression d'éléments telles que la structure de données ainsi définie (B-arbre) possède les
caractéristiques suivantes (où n est l'ordre du B-arbre) :
1. chaque page contient, au plus, 2n éléments
2. chaque page, sauf la racine, contient au moins n éléments
3. chaque page est soit une feuille (pas de descendants), soit elle possède m+1 descendants (si elle
contient m éléments)
4. toutes les pages feuilles sont au même niveau.
La figure suivante illustre un B-arbre d'ordre 2 avec 3 niveaux :

http://cuisung.unige.ch/std/14.3.html (2 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

Fig. 14.9. Exemple de B-arbre d'ordre 2.


Chaque page contient 2, 3 ou 4 éléments, sauf la page racine qui, dans cet exemple, n'en contient qu'un
seul. Toutes les pages feuilles sont au niveau 3.
Chaque page est de la forme :
p0 k1d1 p1 k2d2 p2 .... pm-1 kmdm pm (1)

avec pi : pointeurs vers des pages


di : valeurs des éléments
ki : clés des éléments

14.3.1. Recherche dans un B-arbre


Soit un élément de clé x, que l'on désire rechercher dans un B-arbre. Supposons qu'une page, de la forme
(1) ait été amenée en mémoire. La recherche de x parmi les valeurs des clés ki peut se faire selon
les méthodes classiques (séquentielle, binaire, etc . . .). Si l'on ne trouve pas x parmi les clés de la page en
mémoire, on se trouve obligatoirement dans un des trois cas suivants :
1. ki < x < ki+1 pour 0< i <m : x est compris entre les clés ki et ki+1,
auquel cas on continue la recherche dans la page indiquée par pi
2. km < x : x est plus grande que toutes les clés de la page,
auquel cas on continue la recherche dans la page indiquée par pm
3. x < k1 : x est plus petite que toutes les clés de la page,
auquel cas on continue la recherche dans la page indiquée par p0.

Si, dans l'un de ces trois cas, le pointeur de page à considérer est "nil", cela signifie qu'il n'y a pas
d'élément de clé x dans le B-arbre et la recherche est terminée.

14.3.2. Insertion
D'une manière générale, l'insertion d'un nouvel élément dans un B-arbre est relativement simple. On
effectue les mêmes étapes que pour la recherche d'un élément jusqu'à ce que l'on arrive à une page feuille
où l'élément à insérer aurait pu se trouver s'il était présent dans la structure. On essayera alors d'insérer
l'élément dans la page (feuille) en question.
Si un élément doit être inséré dans une feuille contenant moins de 2n éléments, l'insertion s'effectuera à
l'intérieur de la page, sans autre difficulté. Si la page est pleine et qu'une feuille adjacente possède une
place libre, on effectuera un transfert entre les deux feuilles, en tenant compte de l'élément charnière qui
se trouve dans la page parente. Pour prendre un exemple, supposons que nous ayons le B-ar bre de la
figure 14.10 :

http://cuisung.unige.ch/std/14.3.html (3 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

Fig. 14.10. Exemple de B-arbre d'ordre 2.


et que l'on désire insérer la valeur 12. Cette valeur devrait être insérée dans la page B. On va donc
déplacer l'élément charnière (20) de la page parente (A) vers la page adjacentes à la page B et possédant
une place libre (page C). La place libérée en page A par le déplacement de l'élément charnière va
maintenant accueillir l'élément de la page B qui lui est directement inférieur (18). La page B contient
alors une place libre qui peut recevoir la nouvelle valeur à insérer (12). On aboutit enfin à la structure
suivante :

Fig.14.11. Insertion par rocade d'éléments


Par contre, l'insertion d'un nouvel élément dans une feuille pleine avec des pages adjacentes pleines
également a des conséquences sur la structure globale, dans la mesure où il faut allouer de nouvelles
pages.
Pour bien comprendre ce qui se passe dans un tel cas, considérons le B-arbre d'ordre 2 de la figure 14.12
:

Fig. 14.12. B-arbre de départ pour insertion dans une page pleine.

http://cuisung.unige.ch/std/14.3.html (4 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

On désire insérer l'élément 22 dans ce B-arbre. L'insertion dans la page C (page devant contenir 22) n'est
pas possible, car la page est déjà pleine (elle contient 2n éléments). On effectue alors les opérations
suivantes :
1. on prépare de la place en créant une nouvelle page (D) au même niveau que B et C.
2. on répartit les 2n+1 éléments (les 2n de C et le nouveau) de la manière suivante :
. l'élément "milieu" (c'est-à-dire le n+1-ème des 2n+1 éléments, soit 30 dans notre exemple)
est déplacé vers la page parente (A), où il joue le rôle de charnière entre la page C et la page
D
b. les éléments plus petits que l'élément milieu (22 et 26) sont placés dans la page C
c. les éléments plus grands que l'élément milieu (35 et 40) sont placés dans la page D.
On aboutit ainsi au B-arbre suivant :

Fig. 14.13. Insertion par création d'une nouvelle page.


Ce mécanisme préserve toutes les propriétés des B-arbres. En particulier, la division d'une page aboutit à
la création de 2 pages contenant exactement n éléments. Il est possible que l'insertion de l'élément
"milieu" dans la page parente puisse, à son tour, provoquer un dépassement de capacité (si la page
parente était pleine) et nécessiter un autre découpage. Le découpage peut ainsi se propager de niveau en
niveau et, éventuellement, atteindre la racine elle-même. C'est d'ailleurs la seule façon, pour un B-arbre,
d'augmenter de profondeur (nb. de niveaux). Contrairement aux autres structures arborescentes, un
B-arbre croît donc à partir des feuilles, vers sa racine.

14.3.3. Structure de données (représentation


interne)
Voyons maintenant comment représenter les données pour réaliser la structure de page définie par la
définition (1) du paragraphe 14.2.1. Nous représenterons les éléments d'une page par un tableau de
dimension 2n, où n est le degré du B-arbre :

const n = 2; { degré du B-arbre }


nn = 4; { 2*n nb. d'éléments max.
dans une page }
type Ref = ^Page;
{ on pourra aussi avoir un pointeur }
{ vers une zone du disque ou un }
{ numéro d'enregistrement dans un }

http://cuisung.unige.ch/std/14.3.html (5 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

{ fichier à accès direct }

Element = record
Cle : integer;
p : Ref;
Info : ...;
end; { Element }
Page = record
NbElements : 1..nn;
p0 : Ref;
Elements: array[1..nn] of Element;
end; { Page }
Les algorithmes de manipulation de cette structure peuvent être trouvés dans le livre de
N. Wirth, "Algorithms+Data Structures = Programs" p. 252 programme 4.7 (voir copie locale
légèrement modifiée).

14.3.4. Suppression d'un élément


L'élimination d'un élément d'un B-arbre est simple à première vue, mais un peu plus compliquée quant on
l'examine en détail. Comme pour les structures d'arbres, deux cas sont à distinguer :
1. l'élément à éliminer est dans une feuille
2. l'élément à éliminer est dans une page intermédiaire. Il faut alors l'échanger avec un des deux
éléments immédiatement adjacents (dans l'ordre des clés selon la relation d'ordre définie sur le
B-arbre). Ces deux éléments immédiatement adjacents se trouvent forcément dans des feuilles.
Une fois l'échange effectué, l'élément à détruire se trouve donc dans une feuille. On retombe donc
sur le cas 1) pour son élimination.
Dans les deux cas, la diminution du nombre d'éléments d'une feuille doit s'accompagner d'une
vérification :
si le nombre d'éléments est maintenant inférieur à n, alors le critère de B-arbre n'est pas
respecté. Il faut donc rétablir l'équilibre en prenant un autre élément ailleurs.
L'élément à emprunter doit être trouvé dans une des deux pages voisines. Si au moins l'une d'entre elles a
plus de n éléments, c'est dans celle là que sera effectué le prélèvement.
Si les deux pages voisines n'ont que n éléments chacune (le minimum autorisé), il faut alors fusionner
l'une d'elles avec la page fautive en amenant dans la page résultante, l'élément de la page parente qui se
trouve entre les deux pages à fusionner. On aura donc n-1 éléments provenant de la page fautive, n
éléments provenant de la page voisine et 1 élément provenant du niveau au-dessus, ce qui donnera 2n
éléments dans cette nouvelle page.
Le prélèvement d'un élément de la page parente peut provoquer, à son tour, une situation nécessitant une
fusion. Ce phénomène peut se propager de niveau en niveau, jusqu'à atteindre la racine qui peut, à son
tour, voir son nombre d'éléments tomber à 0, auquel cas elle disparaît, ce qui réduit la profondeur du
B-arbre d'un niveau.

http://cuisung.unige.ch/std/14.3.html (6 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

Exemple : suppression de la valeur 20 dans le B-arbre suivant :

Fig. 14.14. Exemple de B-arbre dont on va supprimer la valeur 20


Première étape : échange avec l'élément directement inférieur. On obtient alors :

Fig. 14.15. Echange de l'élément à détruire et de l'élément directement inférieur.


puis, suppression de la valeur 20 dans la feuille :

Fig. 14.16. La valeur 20 est supprimée.


La feuille doit maintenant être combinée avec une feuille adjacentes. On obtient alors :

http://cuisung.unige.ch/std/14.3.html (7 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3

Fig. 14.17. Deux feuilles ont été regroupées.


Cette fois, c'est la page contenant la valeur 14 qui est trop peu remplie. Il faut donc la regrouper avec la
page adjacente (de même niveau) en utilisant la valeur charnière (25) qui se trouve dans la page racine.
On obtient alors :

Fig. 14.18. La mise-à-jour est terminée.

15. Tables et arbres de décision.

Table des matières.

http://cuisung.unige.ch/std/14.3.html (8 of 8) [07-06-2001 12:37:31]


Structuration des Données Informatiques - 14.3, exercice 1

Exercice suivant

B-arbre
Question posée au contrôle continu du 17 juin 1996
Dessinez le B-arbre résultant de la suppression de la valeur g dans le B-arbre d'ordre 1 suivant:

Solution

Exercice suivant

http://cuisung.unige.ch/std/ex/14/3a.html [07-06-2001 12:37:35]


Structuration des Données Informatiques - 15.0

15. Tables et arbres de décision


15.1. Les tables de décision

Table des matières.

http://cuisung.unige.ch/std/15.0.html [07-06-2001 12:37:37]


Structuration des Données Informatiques - 15.1

15.1. Les tables de décision


Les tableaux présentent également un intérêt dans le traitement de problèmes décisionnels. Ils permettent
de présenter de manière concise tout un ensemble de combinaisons de conditions qui, si elles sont
satisfaites, impliquent qu'une combinaison d'actions définies est exécutée.
Lorsqu'un grand nombre de combinaisons de conditions peut se présenter et qu'un vaste choix de
combinaisons d'actions se présente, alors les tables de décisions permettent de gérer efficacement cette
complexité. De plus, elles offrent le cadre nécessaire pour vérifier les propriétés importantes des
systèmes décisionnels, à savoir :
. la cohérence,
b. la complétude,
c. la non-redondance.
Références :
K. LONDON,
"Decision tables : A Practical Approach for Data Processing"
Auerbach, Princeton, N.J, 1972.
(bonne monographie).

U.W. POOCH,
"Translation of Decision Tables"
Computing Surveys, vol. 6, No. 2, pp. 125-151, juin 1974.
Les tables de décision sont formées de trois composantes : les conditions, les règles et les actions. On
peut les schématiser ainsi :

Fig. 15.1. Exemple de table de décision.


Cette table est divisée en quatre parties par les épaisses lignes verticales et horizontales. La partie en haut
à gauche est appelée énoncé des conditions, la partie en bas à gauche est appelée énoncé des actions, la
partie en haut à droite est appelée indicateurs de conditions (peuvent être "O" pour oui, "N" pour non et
"-" pour peu importe) et la partie en bas à droite est appelée indicateurs d'actions (peuvent être soit blanc

http://cuisung.unige.ch/std/15.1.html (1 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

pour ne pas exécuter l'action ou X pour l'exécuter).

Fig. 15.2. Table étendue correspondant à la table de la figure 15.1.


Une table, dont certains indicateurs sont des "-" peut être trans formée en une table ne contenant pas
d'indicateur "peu importe" (table étendue). Pour ce faire, il suffit de duplifier la colonne contenant un "-",
de remplacer le "-" par un "O" dans une des deux copies et par un "N" dans l'autre. On réitère le
processus jusqu'à ce qu'il n'y ait plus de "-" dans la table. Pour l'exemple de la figure 15.1, l'on obtient
alors la table de la figure 15.2.
Plusieurs tables condensées différentes peuvent aboutir à la même table étendue. Ainsi, la table de la
figure 15.1 n'est pas la seule table qui permette d'obtenir la figure 15.2, la figure suivante (figure 15.3)
donne aussi le même résultat :

Fig. 15.3. Autre exemple de table condensée aboutissant aussi à la table étendue de la figure 15.2.
Cela vient du fait qu'un ensemble de règles ayant les mêmes actions peuvent être combinées de diverses
manières pour obtenir des tables condensées. La méthode qui produit le plus de "-" produira la table la
plus condensée.

http://cuisung.unige.ch/std/15.1.html (2 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

15.1.1. Propriétés des tables de décision


Au vu des exemples précédents, il est facile de définir les concepts de complétude, de cohérence et de
redondance :
● Deux règles sont incohérentes si elles ont les mêmes indicateurs de conditions mais qu'elles n'ont
pas les mêmes indicateurs d'actions.
● Deux règles sont redondantes si elles sont identiques. Une table contenant des règles redondantes
est dite redondante.
● Une table est complète si, avec n conditions, elle contient 2n règles qui ne sont ni incohérentes ni
redondantes. Cela signifie que pour n'importe quelle combinaison de conditions, il existe une règle
applicable.
Une règle simple ne contient que des "O" et des "N". Une règle composite contient aussi des "-". Une
règle composite R1 recouvre une règle R2 si l'extension de la règle R1 (suppression des "-") et
l'extension de la règle R2 ont une ou plusieurs colonne(s) en commun.
Exemple

Fig. 15.4. Exemple de règles se recouvrant.


La règle 1 et la règle 2 se recouvrent car la règle 1 peut être étendue aux règles 1a et 1b, or la règle 1a est
identique à la règle 2. Dans ce cas précis, le recouvrement est redondant car la règle 1a implique les
mêmes actions que la règle 2. De même, la règle 2 et la règle 3 se recouvrent car une des extensions de la
2 (la 2a) a les mêmes indicateurs de conditions que l'une des extensions de la règle 3 (3b). Seulement là,
les indicateurs d'actions diffèrent. Le recouvrement est donc incohérent.
Jusqu'à présent, les exemples que nous avons vus supposaient que les conditions énumérées étaient toutes
indépendantes les unes des autres. Il se peut, cependant, que l'on désire avoir des conditions qui soient
liées. Par exemple, on pourrait avoir C1 = "Age < 20", C2 = "Age [15,30]", C3 = "Age > 50". Dans ce
cas, il y a risque d'incohérence, y compris à l'intérieur d'une même règle (par exemple si l'on a "O" pour
C1 et C3 simultanément). Cela peut aussi aboutir à une table ambiguë, si une même donnée peut satisfaire
deux règles ayant des indicateurs d'actions différents (par exemple, en reprenant les mêmes conditions
C1, C2 et C3 que ci-dessus, si une règle avec des indicateurs de conditions valant (O, O, N) avait des
indicateurs d'actions différents d'une autre règle ayant des indicateurs de conditions valant (N, O, N), les
deux règles seraient ambiguë pour toute personne d'un âge compris entre 15 et 20 ans).

http://cuisung.unige.ch/std/15.1.html (3 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

15.1.2. Description en terme de type abstrait


● le rôle d'une table de décision est de définir des actions à entreprendre dans le cas où certaines
conditions sont remplies. L'association d'une série d'actions à un jeu de conditions est appelé une
règle. Une table de décision consiste en un ensemble de règles ainsi qu'en l'énoncé des conditions
et des actions sur lesquelles les règles sont basées.
● primitives de manipulation :
1. définir les conditions (énoncé) utilisées pour la table de décision.
2. définir les actions invoquées dans la table.
3. définir une règle en spécifiant la valeur que doit avoir chaque condition et les actions à
entreprendre si les conditions sont remplies.
4. étant donné un ensemble de valeurs de conditions, déterminer s'il existe une règle
correspondante.
5. étant donné un ensemble de valeurs de conditions, obtenir la liste des actions à invoquer.

15.1.3. Exemple d'implantation


La structure de donnée à utiliser pour construire une table de décision peut se présenter sous diverses
formes en fonction des caractéristiques de la table de décision. Ainsi, une table étendue (ne comportant
pas d'indicateurs de conditions "-") permet l'utilisation d'une matrice de booléens pour stocker la valeur
des indicateurs de condition, alors que dans l'exemple qui suit, il est fait usage d'un type énuméré pour
représenter une condition :

const MaxNbCond = ...;


MaxNbRegles = ...;
MaxNbActions = ...;

type Condition =(Vrai, Faux, Indetermine);


ValeurCas = Vrai..Faux;

TableDecision = record
NbConditions: 1..MaxNbCond;
NbRegles: 1..MaxNbRegles;
ValCond: array[1..MaxNbRegles,
1..MaxNbCond]
of Condition;
NbActions: 1..MaxNbActions;
Agir: array[1..MaxNbRegles,
1..MaxNbActions]
of boolean;
TxtConditions:array[1..MaxNbCond]
of string[30];

http://cuisung.unige.ch/std/15.1.html (4 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

TxtActions:array[1..MaxNbActions]
of string[30];
end; { TableDecision }
CasReel = array[1..MaxNbCond]
of ValeurCas;
procedure Traitement(Cas:CasReel;
Table:TableDecision);
{ cette procédure se charge de déterminer
quelles règles de la table de décisions
sont applicable au cas à traiter et
d'exécuter à chaque fois les actions
associées. }
var Regle, Action, Cond: integer;
Applicable: boolean;

begin { Traitement }
with Table do begin

for Regle := 1 to NbRegles do begin


Applicable := true;
for Cond := 1 to NbCond do
Applicable := Applicable and
((Cas[Cond]=ValCond[Regle,Cond]) or
(ValCond[Regle,Cond]=Indetermine));
if Applicable then

{ si la règle courante est applicable,


exécuter les actions corresp. }

for Action := 1 to NbActions do


if Agir[Regle,Action] then
case Action of
1: Action1;
...
end; { case }
end; { for Regle }
end; { with }
end; { Traitement }
Dans cet exemple, on a supposé que les différentes actions associées aux règles étaient suffisamment
complexes pour nécessiter l'élaboration d'une procédure par action. Dans un tel cas, Modula 2 offrirait
plus de souplesse pour l'activation des actions. On pourrait en effet remplacer l'instruction "case
Action of ..." par un appel d'une composante d'un tableau de procédures :
InvoqueAction[Action];
si l'on suppose que InvoqueAction est un champ de l'enregistrement TableDecision déclaré comme

http://cuisung.unige.ch/std/15.1.html (5 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

array[1..MaxNbactions] of procedure.
On peut aussi supposer, comme alternative, que l'on dispose d'un interpréteur d'actions et que l'exécution
d'une action revient à invoquer l'interpréteur en lui passant en paramètre le texte de l'action. L'instruction
"case" serait alors remplacée par :
Interpret(TxtAction[Action]);
Les tables dont les indicateurs de conditions sont limités aux valeurs "O", "N" et "-" sont dites tables à
indicateurs limités. On peut généraliser la notion de table de décision en étendant les indicateurs à des
nombres entiers pour obtenir des tables généralisées. Une signification est alors associée à chaque valeur
possible.
Un tel exemple de table généralisée est illustré dans la figure 15.5. Dans cette figure, la colonne "autre"
correspond aux actions à exécuter si aucune des autres règles ne peut être activée.
Exemple

Fig. 15.5. Choix du pourboire dans un restaurant.


Une table généralisée peut être transformée en une table limitée. Il suffit pour cela de considérer comme
des conditions booléennes sé parées l'égalité avec chaque valeur possible d'une condition pouvant avoir
plusieurs valeurs. Pour l'exemple vu plus haut, cela donnerait :
C1 : qualité du service excellent
C2 : qualité du service bon
C3 : qualité du service passable
etc...

15.1.4. Conversion de tables de décisions en


cascade de tests
Une fois qu'une table de décision a été réalisée, il faut pouvoir programmer les règles qui la composent.
Nous avons vu précédemment un exemple de structure de donnée ainsi qu'une procédure de traitement
correspondante. Une autre technique possibles consiste à convertir ces règles en une cascade de tests
utilisant les conditions énumérées dans la table. Cette technique est la suivante :

http://cuisung.unige.ch/std/15.1.html (6 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

Soit C une matrice de n lignes représentant les indicateurs des n conditions d'une table de décision (n>1).
On répète les actions suivantes :
● choisir une condition Ci, 1 i n

● créer deux nouvelles matrices CO et CN à partir de C, en éliminant la i-ème ligne et en gardant :


❍ dans CO les colonnes j telles que C[i,j] = "O" ou C[i,j] = "-" et
❍ dans CN les colonnes j telles que C[i,j] = "N" ou C[i,j] = "-"
● Ci constituera la racine d'un arbre ayant deux sous-arbres créés à partir de CO et CN récursivement.
On répète donc le traitement sur les nouvelles matrices.
● la récursivité sera arrêtée dès que CO ou CN n'ont plus qu'une ligne (et deux colonnes). Les règles
correspondantes formeront alors les feuilles liées à cette dernière condition.
Ce processus peut être illustré par la figure 15.6. Dans cette illustration, seuls les losanges contenant les
conditions sont significatifs. Les matrices intermédiaires y ont été ajoutées uniquement pour améliorer la
compréhension. De même, les règles indiquées dans les feuilles signifient que les actions
correspondantes peuvent être invoquées.

Fig. 15.6. Exemple de conversion en cascade de tests.


Il est évident que si l'on traite les conditions dans un ordre différent, on obtiendra une cascade différente.
La figure 15.7 illustre une autre possibilité obtenue à partir de la même table initiale.

http://cuisung.unige.ch/std/15.1.html (7 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

Fig. 15.7. Cascade de tests équivalente à celle de la fig. 15.6.


Indépendamment de toute implantation sur ordinateur, ces conversions peuvent être effectuées à la main
par un programmeur pour écrire la cascade de tests (if then else) correspondante et, ainsi, invoquer les
actions correspondant à chaque chemin. On obtient alors une instruction composée du genre :
if C1 then
if C2 then
if C3 then
{ activer règle corresp. à (O,O,O) }
else
{ activer règle corresp. à (O,O,N) }
else if C3 then
{ activer règle corresp. à (O,N,O) }
else
{ activer règle corresp. à (O,N,N) }
else if C2 then
if C3 then
{ activer règle corresp. à (N,O,O) }
else
{ activer règle corresp. à (N,O,N) }
else if C3 then
{ activer règle corresp. à (N,N,O) }
else
{ activer règle corresp. à (N,N,N) }
Cela a l'inconvénient de figer la table dans le programme. Pour plus de flexibilité, on peut envisager de
construire un tableau d'entiers à 2NbConditions entrées qui correspondront aux feuilles de l'arbores cence de
conditions. La déclaration pourrait être : array[ boolean, boolean, boolean] of
1..MaxNbRegles, pour une table à trois conditions; Le i-ème indice correspondrait à la i-ème
condition. Ceci est toutefois difficilement envisageable si l'on ne connaît pas à l'avance le nombre de
conditions de la table de décision à traiter. On a donc meilleur temps de déclarer un indice entier et
sélectionner, pour un cas donné, la composante correspondant à la formule :

http://cuisung.unige.ch/std/15.1.html (8 of 12) [07-06-2001 12:38:22]


Structuration des Données Informatiques - 15.1

ord(Ci)= indice de la composante du tableau contenant le numéro de règle correspondant

au cas donné

Cela donne les déclarations et la procédure de traitement suivants :

program TableDeDecision;

const MaxNbCond = 5;
MaxNbRegles = 32;
MaxNbActions = 5;
MaxCasDiff = 32; { 2**MaxNbCond }

type TableDecision = record


NbConditions: 1..MaxNbCond;
NbRegles: 1..MaxNbRegles;
Indicateurs: array[1..MaxCasDiff]
of 1..MaxNbRegles;
NbActions: 1..MaxNbActions;
Agir: array[1..MaxNbRegles,
1..MaxNbActions]
of boolean;
TxtConditions: array[1..MaxNbCond]
of string[30];
TxtActions: array[1..MaxNbActions]
of string[30];
end; { TableDecision }

StrConditions = string[MaxNbCond];

StrActions = string[MaxNbActions];

CasReel = StrConditions;

var Table: TableDecision;


Cas: CasReel;
F: text;

function Indice(S:StrConditions): integer;


{ permet de déterminer quelle règle cor-
respond aux indicateurs de condition

http://cuisung.unige.ch/std/15.1.html (9 of 12) [07-06-2001 12:38:23]


Structuration des Données Informatiques - 15.1

fournis en paramètre }
var I, Resultat: integer;
begin
Resultat := 0;
for I := 1 to length(S) do
Resultat:=Resultat*2 + ord(S[I]='O');
Indice := Resultat;
end; { Indice }

procedure Lecture(
var Table: TableDecision);
var R: 1..MaxNbRegles;
C: 1..MaxNbCond;
A: 1..MaxNbActions;
IndicCond: string[MaxNbCond];
IndicActions: string[MaxNbActions];
procedure EntreIndicateurs(
IndicCond:StrConditions;
Regle:integer);
var I: integer;
begin
I := pos('-', IndicCond);
if I = 0 then
Table.Indicateurs
[Indice(IndicCond)] := Regle
else begin
IndicCond[I] := 'O';
EntreIndicateurs(IndicCond,Regle);
IndicCond[I] := 'N';
EntreIndicateurs(IndicCond,Regle);
IndicCond[I] := '-';
end;
end; { EntreIndicateurs }

procedure EntreActions(
IndicActions:StrActions;
Regle: integer);
var I: integer;
begin
with Table do
for I:=1 to length(IndicActions) do
Agir[Regle,I]:=IndicActions[I]='X';
end; { EntreActions }

begin { Lecture }
assign(F,'NomDuFichierExterne');

http://cuisung.unige.ch/std/15.1.html (10 of 12) [07-06-2001 12:38:23]


Structuration des Données Informatiques - 15.1

reset(F);
with Table do begin
readln(F,NbConditions, NbRegles,
NbActions);
for C := 1 to NbConditions do
readln(F,TxtConditions[C]);
for A := 1 to NbActions do
readln(F,TxtActions[A]);
for R := 1 to NbRegles do begin
readln(F,IndicCond);
readln(F,IndicActions);
EntreIndicateurs(IndicCond,R);
EntreActions(IndicActions,R);
end; { for }
end; { whith }
end; { Lecture }

procedure Traitement(Cas: CasReel;


Table: TableDecision);
var Regle, Action: integer;
begin
with Table do begin
Regle := Indicateurs[Indice(Cas)];
write('il faut executer les actions');
for Action := 1 to NbActions do
if Agir[Regle,Action] then
writeln(TxtActions[Action]);
writeln;
end; { with }
end; { Traitement }

begin { programme principal }


Lecture(Table);
Cas := '';
for C:=1 to Table.NbConditions do begin
writeln(TxtConditions[C],'(O/N)');
insert(' ',Cas,C);
readln(Cas[C]);
end; { for }
Traitement(Cas,Table);
end. { TableDeDecision }

http://cuisung.unige.ch/std/15.1.html (11 of 12) [07-06-2001 12:38:23]


Structuration des Données Informatiques - 15.1

15.2. Arbres de décision

Table des matières.

http://cuisung.unige.ch/std/15.1.html (12 of 12) [07-06-2001 12:38:23]


Structuration des Données Informatiques - 15.1, exercice 1

exercice suivant

Tables de décision
Question posée au contrôle continu du 16 juin 1997
Avec les déclarations suivantes définissant des tables de décision, écrivez une procédure "Extension" qui
convertisse une table de décision condensée (paramètre d'entrée) en table étendue (paramètre de sortie):

const MaxNbCond = ...;


MaxNbRegles = ...;
MaxNbActions = ...;

type Conditions =(Vrai, Faux, Indetermine);

TableDecision = record
NbConditions: 1..MaxNbCond;
NbRegles: 1..MaxNbRegles;
ValCond: array[1..MaxNbRegles,
1..MaxNbCond]
of Conditions;
NbActions: 1..MaxNbActions;
Agir: array[1..MaxNbRegles,
1..MaxNbActions]
of boolean;
TxtConditions:array[1..MaxNbCond]
of string[30];
TxtActions:array[1..MaxNbActions]
of string[30];
end; { TableDecision }

procedure Extension(Entree: TableDecision;


var Sortie: TableDecision);
Note: Pour simplifier l'écriture, vous pourrez supposer que l'affectation d'enregistrements ou de tableaux
est autorisée en Pascal.
Solution

exercice suivant

http://cuisung.unige.ch/std/ex/15/1a.html [07-06-2001 12:38:25]


Structuration des Données Informatiques - 15.2

15.2. Arbres de décision


Les tables de décision sont utilisées lorsque le système décisionnel se présente sous la forme d'un triplet :
(conditions, règles, actions). Dans d'autres cas, un système décisionnel se présente comme une suite
d'étapes exigeant chacune une décision. L'objectif est alors de trouver le chemin répondant à certaines
contraintes.

15.2.1. Parcours d'un labyrinthe


L'exemple le plus classique est le parcours d'un labyrinthe. A chaque carrefour, il faut choisir une
nouvelle direction. Si la direction choisie ne mène pas à la solution, il faut pouvoir revenir au carrefour
précédent et essayer une autre direction.

Fig. 15.8. Les chiffres indiquent les points où une décision doit être prise. Les lettres désignent les
culs-de-sac.
Si l'on exclut la possibilité de revenir sur ses pas, on peut représenter le cheminement à l'intérieur du
labyrinthe par l'arbre de la figure 15.9.

Fig. 15.9. Arbre de décision pour le parcours d'un labyrinthe, sans retour en arrière.
Si on lève la restriction d'un retour possible en arrière, l'arbre prend alors la forme suivante :

http://cuisung.unige.ch/std/15.2.html (1 of 5) [07-06-2001 12:39:08]


Structuration des Données Informatiques - 15.2

Fig. 15.10. Arbre de décision avec retour en arrière.


Le même noeud peut apparaître plusieurs fois à plusieurs endroits dans l'arbre indiquant ainsi qu'il existe
plusieurs chemins pour l'atteindre. Dans de tels cas, il est intéressant de pouvoir déterminer quel est le
plus court chemin d'un point à l'autre. De plus, l'arbre est devenu infini étant donné la possibilité de
visiter un point un nombre indéterminé de fois.
Pour une représentation efficace, on abandonne alors la structure d'arbre au profit de celle de graphe. Une
telle situation se présente souvent dans des jeux de pièces (échecs par exemple) où les mouvements des
pièces permettent de créer la même configuration à plusieurs reprises. C'est pourquoi les règles
interdisent souvent de reproduire plus de n fois la même configuration.
Ayant pris l'exemple de jeux, on est amené tout naturellement à se demander comment représenter
l'évolution d'un jeu comme : les échecs, les dames, tic-tac-toe, nim, etc, mettant aux prises deux
adversaires.

15.2.2. Jeu de nim


Prenons comme exemple le jeu de nim. Soit un tas de n allumettes, les règles sont les suivantes :
● les joueurs A et B enlèvent chacun à son tour 1, 2 ou 3 allumettes du tas,

● le joueur qui enlève la dernière allumette a perdu,

● donc l'autre a gagné !

La figure 15.11 représente les situations possibles du jeu de nim pour n = 6 allumettes.
Une configuration du jeu est entièrement déterminée par le nombre d'allumettes dans le tas. A tout instant
l'état du jeu est donné par la configuration et l'indication du joueur dont c'est le tour. Une configuration
finale d'un jeu représente le gain, la perte du jeu ou un match nul. Tout autre configuration est
non-terminale. Dans le jeu de nim il n'y a qu'une configuration finale possible : lorsqu'il ne reste plus
d'allumette sur le tas.
Les arbres de jeu sont utiles pour déterminer quel est le meilleur mouvement qu'un joueur doit faire.

http://cuisung.unige.ch/std/15.2.html (2 of 5) [07-06-2001 12:39:08]


Structuration des Données Informatiques - 15.2

Dans la figure 15.11, le joueur A est au départ dans la situation où 3 possibilités s'offrent à lui; laquelle
doit-il prendre s'il veut gagner ?
Il doit choisir celle qui maximise ses chances de victoire.
Pour pouvoir faire ce choix, il faut pouvoir évaluer, parmi les différentes situations possibles, celle qui
est la meilleure. On peut pour cela utiliser une fonction d'évaluation E(x) qui associe une valeur
numérique à toute configuration x du jeu. Cette fonction est une mesure de la valeur d'une configuration
pour un joueur donné.

Fig. 15.11. Situations possibles pour le jeu de nim.


Cette fonction E(x) doit donner une valeur élevée lorsque le joueur a une bonne chance de gagner et une
valeur basse lorsqu'il a une faible chance de gagner (ou lorsque son adversaire a une grande chance de
gagner).
Ainsi, pour l'arbre de la figure 15.11, il est facile d'imaginer une fonction E(x) pour une configuration
finale (du point de vue du joueur A) :

E(x)=
{ 1 si x est une configuration pour A
-1 si x est une configuration perdante pour A

Partant de cette indication, quel mouvement (b, c ou d sur la figure) A doit-il faire au départ? Pour cela il
faut évaluer les configurations b, c, d respectivement et déterminer quelle est la meilleure. Il s'avère que
le mouvement gagnant est b, car c'est une position à partir de laquelle A peut gagner indépendamment
des mouvements de B.
Comment évaluer la valeur d'une position intermédiaire x? Cette évaluation repose sur les valeurs des

http://cuisung.unige.ch/std/15.2.html (3 of 5) [07-06-2001 12:39:08]


Structuration des Données Informatiques - 15.2

positions suivantes; ainsi comme les tours de jeu se font en alternance pour A et B, une position gagnante
pour A est une position perdante pour B. La fonction d'évaluation exprimant la valeur d'une
configuration pour un joueur donné, et toujours pour ce même joueur, on peut l'exprimer dans le cas de
notre exemple par la formule suivante, appelée formule du minimax:
max (V(ci)) si x est un carré 1<i<d
V(x)=
{ min(V(ci)) si x est un rond 1<i<d

avec d = degré du noeud x (c.à.d. le nombre de ses descendants).

15.2.3. Le jeu du Tic-Tac-Toe


Dans le cas de ce jeu, le nombre de configurations possibles, à partir d'une configuration de départ (la
configuration vide), est très grand : pour 3 x 3 cases, on a 9! configurations possibles (362'880). Ce
nombre peut être réduit si l'on tient compte des symétries du jeu, comme le montre la figure 15.12.

Fig. 15.12. Début de l'arborescence de décision.


Quelle fonction d'évaluation peut-on considérer pour ce jeu ?
Si le joueur X est pris comme joueur de référence (c'est celui qui commence le jeu), on peut déterminer
une valeur V comme :
V = V1 - V2
où V1 est la somme de :

● a) le nombre d'occurrences de 3 Xen ligne multiplié par 10


● b) le nombre d'occurrences de 2 Xen ligne avec la troisième position vide multiplié par 4

http://cuisung.unige.ch/std/15.2.html (4 of 5) [07-06-2001 12:39:08]


Structuration des Données Informatiques - 15.2

● c) le nombre d'occurrences d'unX sur une ligne avec les 2 autres positions vides.
et V2 la même somme pour les O.

Exemple

Fig. 15.13. Différentes configurations de jeu avec fonction d'évaluation correspondante.


Autre possibilité :

V1= nombre de lignes, colonnes et diagonales encore ouvertes pour X


V = nombre de lignes, colonnes et diagonales encore ouvertes pour O
2

15.3. Réalisation d'une procédure MiniMax

Table des matières.

http://cuisung.unige.ch/std/15.2.html (5 of 5) [07-06-2001 12:39:08]


Structuration des Données Informatiques - 15.3

15.3. Réalisation d'une procédure


MiniMax
Soit un jeu hypothétique :
● 2 joueurs :

type Joueurs = (Premier,Second);


● des mouvements :

type Mouvement = {type énuméré ou intervalle,suivant le jeu };


var Mvmt : Mouvement;
(Mvmt définit un mouvement possible respectant les règles du jeu).
● des procédures de jeu :

procedure Jouer (Joueur: Joueurs; Mvmt: Mouvement);


{modifie la situation avec Mvmt}

procedure Defaire (Joueur: Joueurs; Mvmt: Mouvement);


{revient à la situation d'avant Mvmt}
● une procédure basée sur les règles du jeu et déterminant toutes les configurations possibles à partir
d'une configuration donnée. Chaque configuration est définie par les mouvements qui la produisent
à partir de la configuration de départ.

procedure Possible(Joueur:Joueurs;
var MvtPossibles:Liste;
var Val:Valeur);

avec MvtPossibles = liste des mouvements possibles (paramètre de sortie),


Val = valeur associée à la configuration courante par lafonction d'évaluation.
La structure de la liste des mouvements possibles dépend fortement du jeu qui est simulé (jeu à base de
pièces sur un damier, jeu de dés, jeu de cartes, etc...). On peut toutefois imaginer que des procédures
annexes permettent de gérer cette liste et d'accéder à ses éléments.
Par exemple :
procedure PremierElement(
var MvtPossibles:Liste;
var Mvmt:Mouvement);
permet d'extraire le premier mouvement de la liste MvtPossibles (Mvmt <- car(MvtPossibles) et
MvtPossibles <- cdr(MvtPossibles) ).
procedure ElementSuivant(

http://cuisung.unige.ch/std/15.3.html (1 of 6) [07-06-2001 12:40:14]


Structuration des Données Informatiques - 15.3

var MvtPossibles:Liste;
var Mvmt:Mouvement);
permet d'obtenir l'élément suivant (Mvmt <- car(MvtPossibles) et MvtPossibles <- cdr(MvtPossibles)).

Function Taille (MvtPossibles:Liste)


: integer;
détermine le nombre de mouvements contenu dans la liste.
Function Vide (MvtPossibles:Liste)
: boolean;
détermine si la liste contient encore des mouvements.
Une première ébauche d'une procédure de jeu selon la méthode Mi niMax peut être la suivante (en
prenant beaucoup de liberté avec la syntaxe) :

procedure MiniMax (Profondeur : integer;


Joueur : Joueurs;
var Mvmt : Mouvement; var Val : Valeur);

{ cette procédure explore jusqu'à


"Profondeur" niveaux l'arbre de jeu :
elle donne comme résultat le mouvement
Mvmt pour le joueur et la valeur Val
associée à la situation actuelle. }

begin
Possible(Joueur,MvtPossibles,Val);
if MvtPossibles ne contient qu'un mouv.
then réponse = (Mvmt,Val)
else begin
for chaque mouvement possible do begin
jouer ce mouvement;
minimax (Profondeur-1,autre joueur,
...);
défaire ce mouvement
end; { for }
sélectionner la meilleure valeur pour
le Joueur parmi les valeurs produites
dans la boucle ci-dessus;
réponse := (Mvmt,Val);
end; { else }
end; { MiniMax }
A partir de cette ébauche, et avec les procédures de base définies avant, on peut proposer une formulation
plus rigoureuse pour la procédure minimax.

http://cuisung.unige.ch/std/15.3.html (2 of 6) [07-06-2001 12:40:14]


Structuration des Données Informatiques - 15.3

procedure MiniMax (Profondeur : integer;


Joueur : Joueurs;
var Mvmt : Mouvement;
var Val : Valeur);
{ Pour l'algorithme qui suit, on suppose
que la situation du jeu est maintenu
globalement en dehors de la procédure }

const Infini = MaxInt;

var Adversaire : Joueurs;


MAdv: Mouvement; { mouvement de
l'adversaire }
VAdv: Valeur; {valeur associée au
mouvement de l'adversaire }
MvtPossibles: Liste;
{ mouvements possibles pour Joueur }
MEssai: Mouvement;
{ un mouvement possible à essayer }

begin { MiniMax }
Possible(Joueur,MvtPossibles,Val);
if Taille(MvtPossibles) <= 0 then
"forfait"
else if (Taille(MvtPossibles) = 1) or
(Profondeur = 0) then
PremierElement (MvtPossibles,Mvmt)
else begin
if Joueur = Premier then begin
Adversaire := Second;
Val := -Infini; {la plus petite }
end {valeur possible}
else begin
Adversaire := Premier;
Val := Infini; {la plus grande }
end; {valeur possible}

PremierElement (MvtPossibles,MEssai);

while not Vide(MvtPossibles) do begin


Jouer(Joueur, MEssai);
MiniMax(Profondeur-1, Adversaire,
MAdv, VAdv);
Defaire(Joueur, MEssai);

http://cuisung.unige.ch/std/15.3.html (3 of 6) [07-06-2001 12:40:14]


Structuration des Données Informatiques - 15.3

if (Joueur = Premier) and


(VAdv > Val) then begin

{max} Val := VAdv; Mvmt := MEssai;


end

else if (Joueur=Second) and


(VAdv < Val) then begin
{min} Val := VAdv; Mvmt := MEssai;
end;
ElementSuivant(MvtPossibles, MEssai)
end; { while }
end; { if }
end; { MiniMax }

15.3.1. Variante de la méthode MiniMax : l'élagage


alpha-bêta
Si l'on applique la méthode du MiniMax, on se rend vite compte qu'il n'est pas toujours nécessaire de
parcourir la totalité de l'arbre de jeu pour trouver le chemin optimal : on "coupe" des branches que l'on
sait inutiles. Ainsi, soit l'arbre de jeu de la figure 15.14,
Au niveau 0 la racine prend la valeur maximale des descendants,
au niveau 1 chaque noeud prend la valeur minimale des descendants,
au niveau 2 chaque noeud prend la valeur maximale des descendants,
au niveau 3 chaque noeud prend la valeur minimale des descendants,
etc.
Si l'on propage, selon l'algorithme du MiniMax, les valeurs de la fonction d'évaluation associée à chaque
feuille, on obtient l'arbre de décision de la fi gure 15.15.

http://cuisung.unige.ch/std/15.3.html (4 of 6) [07-06-2001 12:40:14]


Structuration des Données Informatiques - 15.3

Fig. 15.14. Arbre de jeu avec valeur des situations terminales.

Fig. 15.15. Evaluation des situations non-terminales.


Les deux sous-arbres de la figure 15.15 qui ont été entourés peuvent ne pas être évalués car :
● après l'évaluation des deux branches de gauche qui ont amené les valeurs 7 et 5 aux noeuds de
niveau 1 on sait que :
. le passage au niveau 0 se fera par recherche du maximum,
b. le maximum courant est 7.
● lors de la construction de la deuxième branche de droite le passage du niveau 2 au niveau 1 se fait
par recherche du minimum. La valeur 3 est associée au descendant gauche du noeud de niveau 1;

http://cuisung.unige.ch/std/15.3.html (5 of 6) [07-06-2001 12:40:14]


Structuration des Données Informatiques - 15.3

par conséquent, il est impossible que le noeud de niveau 1 ait une valeur supérieure à 3 (car, à ce
niveau, on recherche le minimum). On peut donc "supprimer" la branche dans sa totalité,
c'est-à-dire ne pas l'évaluer.
● Il en va de même pour la première branche de droite.
C'est la méthode de l'élagage alpha-bêta. Les lettres grecques alpha et bêta sont souvent utilisées pour
marquer les points d'élagage, alpha pour max et bêta pour min.

Table des matières.

http://cuisung.unige.ch/std/15.3.html (6 of 6) [07-06-2001 12:40:14]


Commenting on the information provided by the Visual Programming/Software Engineering group

Commenting on the information provided by the


Visual Programming/Software Engineering group
You're welcome to submit a comment, a question or a suggestion in the form below (or even just say
"hi"). You can leave the Name and Email address fields empty if you wish to remain anonymous
(Mosaic will only tell us the Internet address of your machine).

Remember to press the Submit comment button to send your comment when you are done.
You can use the Back button at the bottom of the window if you don't want to send a comment.
If you want to know how all this works, follow this.

Your name:
Email address (please use a valid Internet email address if you want a reply):

WWW Home Page:


Tick this box if you want to get a copy of this note when I get to read it.

Submit comment

http://cuisung.unige.ch/UserComment.html (1 of 2) [07-06-2001 12:40:41]


Commenting on the information provided by the Visual Programming/Software Engineering group

Bertrand Ibrahim

http://cuisung.unige.ch/UserComment.html (2 of 2) [07-06-2001 12:40:41]


Bertrand Ibrahim

(automatically supports: mpeg audio, Sun audio, Windows WAV, and Mac AIFF)

Table of Content
● complete resume
Bertrand Ibrahim ● research group
● domains of interest
Maître d'Enseignement et de Recherche (Assistant
Professor) ● bookmarks
Computer Science Department, ● icon collection
University of Geneva, ● contact info
Switzerland
● PGP public key

I am looking for other academic opportunities, in Switzerland or in the United States. Feel free to
contact me if your university has an opening for a tenure track position.

My complete resume is available (also in compressed form, to save network bandwidth), including a
complete list of published papers, with links to online copies whenever possible.

You're welcome to visit my research group


My domains of interest are
● visual languages and visual programming (same document, but uncompressed), as well as
human-computer interaction,
● software engineering and natural language processing (same, but uncompressed), and, of course,

● the WWW, including remote execution of programs through WWW and use of the WWW in
education
● I am also maintaining entries to the WWW Virtual Library: one for Tcl/Tk and another one for
Visual Languages and Visual Programming.

I also give various continuing education Internet-related courses (in French, but with many links to
English documents), a first year computer science course on data structures and a third year computer
science course on Internet technology.
You are welcome to my selection of WWW documents classified into categories (same, compressed, to
save network bandwidth).

Many articles are also available in hardcopy in my office.

http://cuisung.unige.ch/Bertrand.html (1 of 3) [07-06-2001 12:41:20]


Bertrand Ibrahim

Do you want to peek at my collection of icons, collected all over the web?

Directory listing of the same collection of icons (will be faster over slow connections), allowing you to
make your selection.
...and here is a collection of drawings made with simple ASCII characters (uncompressed version).

If you're really interested in finding icons and images to put in your own documents, have a look at the
"Images_and_Icons" Virtual Library entry, and W3C icons.

How to get in touch with me:


Professional address:
Université de Genève
24, rue du Général Dufour (See map)
CH-1211 Geneva 4, Switzerland
E-mail addresses:
Internet: Bertrand.Ibrahim@cui.unige.ch
UUCP: mcsun!cui!bertrand.uucp
X.400: C=ch; ADMD=400net; PRMD=switch; ORG=unige; OU=cui; S=ibrahim; G=bertrand
CompuServe: >INTERNET:Bertrand.Ibrahim@cui.unige.ch
Office: 3rd floor, #350
Tel: (+41 22) 705 75 08
Fax: (+41 22) 705 7780 or (+41 22) 320 29 27
Telex: CH 423 801 Uni
DIP reference number: 5310547
Email via WWW: http://cui.unige.ch/eao/www/Bertrand/comm.html

PGP key (version 6.0):

-----BEGIN PGP PUBLIC KEY BLOCK-----


Version: PGPfreeware 6.0.2i

mQGiBDq7nAwRBADdlvPeX3H0Z+krfs/gEK9f9cixHvkaYPITJudYAR2v/25CfjVF
PEVvnsS9UK5jbhhhXVJ5AQjFWrAi+eOZMWxwCWxQfZ703vrkFPhd/Mfow3W/ko2C
WfehFqc7yDCHi5Srb9z+aeBQDkqZxLYUp0eySMl9JrMQy7SqJqyifB2+jwCg/w6t
CPQ0kHNRDNrJpCy3kDQwyykD/1H3QYvzmFIfD0K1hFxvkot1sBH4u6ZHgm/ntbSz
6NMziQS6nbJsQ2twpoHbdhrpm0ZM6uYOwo8pjx3RchM4w0VwRt1kY+hBko41/btC
nU+G3ptJzyQ1NfULp/mVWi0Yerm2+B6uI3zTPBXcT3EVE3GcTJJ9VX8pRSi4/Wmh
DEtdA/44NL6KuI5qjbkGpOqVo1wje8qaPXIGQrGQ3m1B7eMYTwIpt3zhh9fPR2+9
/REa4PIbicF8k90AEFrfItlFSqGvtnWGkhqvmGpDNCll8x353Sg3MKcFZqSzbZXz
qAFiGb4uOlkGsdXq/AJ1IuxAHafCkmTDSAlwB5sM8T/dImMopbQwQmVydHJhbmQg
SWJyYWhpbSA8YmVydHJhbmQuaWJyYWhpbUBjdWkudW5pZ2UuY2g+iQBLBBARAgAL

http://cuisung.unige.ch/Bertrand.html (2 of 3) [07-06-2001 12:41:20]


Bertrand Ibrahim

BQI6u5wMBAsCAwEACgkQxAh30Az9R6jLTACgu6B/qMG0hh4OXmmyX/IUrkfzb3sA
oJ/rNt+7/p/M9vXFoA+hVl3mD++guQINBDq7nA0QCAD2Qle3CH8IF3KiutapQvMF
6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz0AfGy0OplK33TGSGSfgMg71l6RfUodNQ
+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRPxfx2vIPFRzBhznzJZv8V+bv9kV7HAarT
W56NoKVyOtQa8L9GAFgr5fSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY72
88kjwEPwpVsYjY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy
1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7
AAICB/4k2FyaFEKiZfoQP4YP+D7UL5coCV0Qgxt254SYSIHq18FRSVm0y2DuO39+
zrU40aobzQMyJbCQFLaobFjTCilEdEXrFEcYqOE8WJ4EJf7VzkTyMvuUjbioB9Am
9kniGnciCpMbCUZNMICL04w6P/57SyQ93t+/t/6Srthzd3R0UMb/I2LlGrZo+LvQ
2sk4dwLqcGnC9btiW+OI/tlWCPn7gi6PqCcZHelIqZGjjSWEF0aL6spI5d0kui4Z
lGd7SpJv7y8EyHmFzLaUHiUWX/F+Fk/RvroFY81U6NnamV48sNFSezh/0+fRyeqx
HwwqMw3XVwi/llEEGzfM+2MWtpijiQBGBBgRAgAGBQI6u5wNAAoJEMQId9AM/Ueo
eDoAoPVbN1BDbjqv8uNLKm2d63zCK64uAKCT/v3ymkYh4E1whxp/10grECug/Q==
=zxtk
-----END PGP PUBLIC KEY BLOCK-----
PGP key (version 2.6.1):
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.1
mQBtAzJ90ToAAAEDAKYwVwsa1dffhQY7b+PaMS04oRqsIoC2vSjajAdJlV9o2Q1W
aX5B53SIQ7XaMYehx0WcWJkuUl+W0Mta3ekOuMcI3GMI6t/fWDGcYqbB3pCT4ZLB
OFSCCtg2HKTCnopK1QAFEbQwQmVydHJhbmQgSWJyYWhpbSA8YmVydHJhbmQuaWJy
YWhpbUBjdWkudW5pZ2UuY2g+
=zsO7
-----END PGP PUBLIC KEY BLOCK-----

Centre Universitaire d'Informatique

updated March 17, 2000.

http://cuisung.unige.ch/Bertrand.html (3 of 3) [07-06-2001 12:41:20]

Vous aimerez peut-être aussi