Vous êtes sur la page 1sur 38

INSTITUT AFRICAIN D’INFORMATIQUE (IAI-TOGO)

Tel : 22 20 47 00 / 22 22 13 70 e-mail : iaitogo@iai-togo.com 07 BP : 12456 Lomé 07

Chargés de cours :

MM. ANIFRANI/GBODUI/TCHANTCHO

Semestre 2,
Année académique 2017-2018
Conception et Implémentation des Structures de Données

Plan du cours

1. GENERALITES

2. LES STRUCTURES DE DONNEES STATIQUES

3. LES STRUCTURES DE DONNEES DYNAMIQUES

4. LES LISTES CHAÎNÉES

5. LES ARBRES
1.
GENERALITES

1.1. Problématique
Au début de l’informatique, l’accent a été mis essentiellement sur les traitements
c’est-à-dire l’aspect procédural des programmes aux dépens des données. Le
programmeur ne disposait pratiquement alors que de la case mémoire comme
élément de stockage et il devait exprimer totalement le schéma de résolution avec les
instructions de traitement présentes dans son langage de programmation.
L’ordinateur a été inventé pour exécuter plus facilement des calculs très longs et
complexes ; toutefois, dans la majorité des applications de l’informatique, c’est la
possibilité de stocker et de manipuler un grand nombre de données qui est
primordiale, tandis que la capacité à calculer, à exécuter des opérations
arithmétiques est très secondaire.
Lorsqu’on est dans ce cas, la grande quantité d’information que l’on doit traiter
représente, en un certain sens, une abstraction d’une partie du monde réel. Si l’on
veut résoudre un problème, même sans ordinateur, il est toujours nécessaire de
choisir une abstraction de la réalité, c’est-à-dire de définir l’ensemble des données que
représente la situation réelle. Ce choix doit être guidé par la nature même du
problème à résoudre. Vient alors le choix de la représentation de ces données, choix
lié à ce dont on dispose pour résoudre le problème, donc aux différents outils
disponibles sur l’ordinateur. Dans la plupart des cas, ces deux étapes ne sont pas
entièrement séparables.

Progressivement, les méthodes d’analyse et les langages de programmation ont


introduit des outils permettant de décrire des données complexes ce qui a amélioré
l’équilibre procédure-donnée et augmenté considérablement la productivité.

Ces réflexions permettent de comprendre le choix de notations fait pour décrire dans
ce cours les structures de données et leurs implémentations. La prise en compte de
données complexes se révèle très fructueuse au niveau de la spécification du schéma
de résolution et ne doit pas se limiter aux seules structures présentes dans les langages

Page 1 sur 36
Conception et Implémentation des Structures de Données Généralités

de programmation. C’est pourquoi, dans ce cours, on étudiera systématiquement les


structures de données classiques, leurs propriétés et leurs procédures standard de
manipulation.

1.2. Variable
Une variable est un symbole qui associe un nom appelé identifiant à une valeur. Les
variables constituent, avec les constantes, les données principales qui peuvent être
manipulées par un programme. La valeur d’une variable peut être de quelque type
de donnée que ce soit, et son nom doit être un identifiant unique (différent des mots-
réservés si le langage en possède).
En effet, une variable est un espace de stockage pour un résultat. Elle sert à
mémoriser de l’information. Ce qui est mis dans une variable est mis dans une partie
de la mémoire. Cependant, les possibilités d’une variable sont intimement liées au
langage de programmation auquel on fait référence. Par exemple, une variable en C
aura les caractéristiques ci-après :

- son nom ;
- son type ;
- sa valeur (si on utilise le mot-clé const, elle ne variera pas au cours du temps);
- son adresse ;
- sa portée (la portion de code source où elle est accessible) ;
- sa durée de vie (le temps d’exécution pendant laquelle la variable existe).
La notion de variable est complétée par la notion de pointeur (variable qui contient
l’adresse d’une autre variable. L’adresse contenue dans la variable est directement
accessible au programmeur en C/C++ mais peut ne pas l’être dans d’autres langages.

1.3. Type de données


Un type de données détermine l’ensemble des valeurs auquel appartient une
constante, ou qui peuvent être prises par une variable ou une expression, ou qui
peuvent être engendrées par un opérateur ou une fonction.
Le type de données de la valeur d’une constante, d’une variable ou d’une expression
peut être déduit de sa forme ou de sa déclaration sans qu’il soit nécessaire d’exécuter
un quelconque traitement.

Page 2 sur 36
Conception et Implémentation des Structures de Données Généralités

Tout opérateur et toute fonction exige des arguments d’un certain type et produit
un résultat d’un type donné. Si un opérateur admet des arguments de types
différents, le type du résultat peut être déterminé à l’aide de règles spécifiques du
langage.
En conséquence, un compilateur peut utiliser cette information sur les types pour
vérifier la légalité de certaines instructions. C’est ainsi que l’affectation erronée d’une
variable booléenne à une variable arithmétique peut être détectée avant l’exécution
du programme.
On introduit des variables et des types de données dans un programme pour faire des
calculs ; c’est pourquoi on doit leur associer des opérateurs. Pour chaque type de
données standard, un langage de programmation propose un ensemble d’opérateurs
standard.
Les opérateurs de base les plus importants sont la comparaison c’est-à-dire le test sur
l’égalité (et sur l’ordre dans le cas des types ordonnés) et l’affectation c’est-à-dire
l’injonction à mettre à égalité.

En C, ces deux opérations sont notées comme suit :


Comparaison : x == y (expression dont la valeur est Vrai ou Faux)
Affectation à x : x=y (expression qui fait que x devient égal à y)

Attention à ne pas les confondre

1.4. Structure de données


Une structure de données est un ensemble organisé d’informations reliées
logiquement, ces informations pouvant être traitées collectivement ou
individuellement. L’exemple le plus simple d’une structure de données est le tableau
monodimensionnel ou vecteur constitué d’un certain nombre de composantes de
même type. On peut effectuer des opérations sur chaque composante prise
individuellement mais on dispose aussi d’opérations globales portant sur le vecteur
considéré comme un seul objet. Une structure de données est caractérisée par ses
composantes et leur arrangement mais surtout par son mode d’utilisation. Ainsi,
deux structures ayant les mêmes composantes et les mêmes arrangements doivent
être considérées comme différentes si leurs modes d’exploitation sont
fondamentalement différents (Exemple des piles et des files).

Page 3 sur 36
Conception et Implémentation des Structures de Données Généralités

1.5. Type abstrait de données


Egalement appelé structure de données abstraite, un type abstrait de données est une
spécification mathématique d'un ensemble de données et de l'ensemble des opérations
qu'elles peuvent effectuer. On qualifie d'abstrait ce type de données car il correspond
à un cahier des charges qu’une structure de données doit ensuite implémenter.
Les types abstraits sont des entités purement théoriques utilisées principalement
pour simplifier la description des algorithmes.
Un type abstrait est composé de cinq champs :

- Type abstrait : contient le nom du type que l’on est en train de décrire et précise
éventuellement si celui-ci n’est pas une extension d’un autre type abstrait ;
- Utilise : contient les types abstraits que l’on va utiliser dans celui qu’on est en
train de décrire ;
- Opérations : contient une description des opérations par leur nom, leurs
arguments et leur retour (prototypage) ;
- Pré-conditions : contient les conditions à respecter sur les arguments des
opérations pour que celles-ci puissent avoir un comportement normal.
- Axiomes : contient une série d’axiomes 1 pour décrire le comportement de
chaque opération d’un type abstrait.

1.6. Structure de données et type abstrait de données


Quand la nature des données n’a pas d’influence sur les opérations à effectuer, on
parle alors de type abstrait générique et on fait la confusion avec les structures de
données.

Dans ce cours, on considérera que les structures de données sont indépendantes du


type des données (donc de leur nature) et qu’elles sont définies par l’ensemble des
opérations qu’elles effectuent sur ces données.

1Un axiome est une proposition indémontrable utilisée comme fondement d’un raisonnement ou d’une théorie
mathématique.

Page 4 sur 36
2.
LES STRUCTURES DE
DONNEES STATIQUES
Une structure de données statique est un ensemble d’informations élémentaires liées
logiquement, dont le nombre est fixe. Si tous les éléments d’une structure sont de
même type, la structure est homogène, dans le cas contraire, elle est dite hétérogène.

2.1. Structures de données statiques homogènes


L’élément représentatif des structures statiques homogènes est le tableau que l’on
retrouve pratiquement dans tous les langages de programmation. On étudiera
successivement les tableaux monodimensionnels et les tableaux multidimensionnels
(qui ne sont que des extensions des précédents).

2.1.1. Tableau monodimensionnel

- Spécification fonctionnelle
Le tableau monodimensionnel est un élément bien connu en mathématique ; c’est la
représentation informatique d’un vecteur. Cette structure est constituée d’un
ensemble de composantes repérables à l’aide d’une valeur appelé indice. Chaque
information élémentaire est accessible directement en lecture et en écriture. Cette
structure est extrêmement utilisée aussi bien en informatique que dans la vie
courante : on parle d’un tableau de notes, d’un tableau d’amortissement, etc.

- Spécification logique

Soient TAB le nom du tableau, T le type de données des éléments de TAB, N le


nombre d’éléments de TAB (N est appelé la dimension du tableau) : en C, le tableau
TAB se définit de la manière suivante
T TAB [N] ;

Page 5 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

Exemple : int A [5] ; est la définition d’un tableau A de 5 entiers


NB : On peut définir un tableau et l’initialiser en se passant de la dimension du
tableau. Dans ce cas, la taille du tableau sera égale au nombre d’éléments spécifiés
dans la déclaration.
Exemple : int A[] = {10, 20, 30} ; est la définition d’un tableau de 3 entiers que sont 10,
20 et 30

En déclarant un tableau par int A[5] ; nous avons défini un tableau de 5 entiers
auxquels on peut accéder par A[0], A[1], …, A[4].
NB : Si on considère un tableau TAB de dimension N, en C l’accès au premier élément
se fait par TAB[0] et l’accès au dernier par TAB[N-1].
Ainsi, A[1] = 2 transfère la valeur 2 dans la case d’indice 1 (qui est la 2 ème case) du
tableau A.

- Spécification physique

Pour représenter un tableau monodimensionnel qui est une structure statique, on


n’envisage raisonnablement que le mode de stockage contigu car la mémoire peut
être elle-même considérée comme un grand tableau. Si un élément de tableau occupe
p cellules de mémoire et si la dimension du tableau est d, le tableau est représenté en
mémoire par une zone contiguë de p.d cellules.

En C, le nom d’un tableau est le représentant de l’adresse du premier élément du


tableau. Les adresses des autres composantes sont automatiquement calculées en
fonction de cette adresse.
Exemple : Si un tableau de caractères (char) commence à l’adresse 1000 et si chaque
caractère est codé sur un octet, alors l’adresse du 4ème élément (donc d’indice 3) est 1003.

NB1 : Le premier élément a conventionnellement l’indice 0


NB2 : Dans tous les langages de haut niveau, l’adressage à l’intérieur d’un tableau
n’est pas à la charge du programmeur. Celui-ci désigne directement les éléments du
tableau dans ses programmes, et c’est le compilateur qui génère les séquences
d’adressage.

Page 6 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

2.1.2. Tableau multidimensionnel

- Spécification fonctionnelle
Les tableaux multidimensionnels sont des extensions des tableaux à une dimension,
servant à représenter des objets mathématiques plus complexes comme les matrices.
Ces structures qui apparaissent comme des tableaux dans un espace d’indices à
plusieurs dimensions sont utilisées très largement dans tous les domaines de la
physique, de l’informatique, etc. On peut citer par exemple la représentation d’une
grandeur physique en divers points de l’espace, le stockage d’informations pour des
dépouillements multicritères en statistique, etc.

- Spécification logique
La seule différence entre les tableaux mono et multidimensionnels est la méthode
d’adressage d’une composante, le traitement proprement dit de la composante ne
change pas, puisqu’il n’est pas caractéristique de la structure tableau mais seulement
du type de l’élément. Un élément de tableau multidimensionnel est repéré à l’aide de
plusieurs indices au lieu d’un seul mais on peut montrer facilement qu’un tel tableau
se ramène à une structure monodimensionnelle. En effet, un tableau
multidimensionnel n’est rien d’autre qu’un tableau de tableaux.

Exemple : int A[10][5] est un tableau de 10 lignes et de 5 colonnes d’entiers. En effet,


celui-ci est un tableau de 10 tableaux monodimensionnels de 5 entiers chacun.

- Spécification physique
Comme pour les tableaux à une dimension, le nom du tableau représente l’adresse du
premier élément du tableau. En C, les composantes d'un tableau à deux dimensions
sont stockées ligne par ligne dans la mémoire : il s’agit de la représentation contiguë.
Dans d’autres langages comme Fortran, le stockage se fait plutôt colonne par
colonne.
Par ailleurs, il existe aussi une représentation chaînée dans laquelle on utilise une
hiérarchie de tableaux de pointeurs pour atteindre les éléments effectifs du tableau
considéré.

Page 7 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

2.1.3. Les méthodes de tri

Trier c’est ordonner un ensemble d’éléments en fonction de clés sur lesquelles est
définie une relation d’ordre. Les méthodes de tri constituent un des domaines où le
plus d’études ont été effectuées pour trouver des algorithmes efficaces.

Voici quelques grandes méthodes de tri :


- Tri par insertion : L’opération de base consiste à prendre l’élément frontière
dans la partie non triée, à l’insérer à sa place dans la partie triée, puis à
déplacer la frontière d’une position.

Partie triée Partie non triée

- Tri par échange : Le principe de base est de réordonner les couples non classés
tant qu’il en existe.

- Tri par extraction : L’opération de base consiste à rechercher l’extremum dans


la partie non triée, à le permuter avec l’élément frontière puis à déplacer la
frontière d’une position.

Partie triée

Partie non triée

- Tri par fusion : Il consiste à séparer l’ensemble à trier en deux sous-ensembles


d’importance à peu près égale, puis à trier par fusion (algorithme récursif)
chacun des sous-ensembles que l’on fusionne ensuite en un seul ensemble.

Page 8 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

2.1.3.1. Tri par insertion

Tant que la partie non triée n’est pas vide, on en prend l’élément frontière avec la
partie triée et on l’insère à sa place dans cette dernière. L’insertion de l’élément
frontière est effectuée soit par décalages (dans ce cas, l’algorithme est itératif) soit
par permutations successives (algorithme récursif) à l’aide d’une procédure.
Cf. exercice d’application
Il existe une amélioration de ce tri appelée méthode de Shell. Elle ne fera pas l’objet
de ce cours.

2.1.3.2. Tri par échange : le tri par bulles


Bien que cette méthode soit reconnue comme une des plus mauvaises, nous lui
consacrons dans un but pédagogique quelques développements, d’abord en raison de
sa simplicité et ensuite pour montrer les différents raffinements qu’on peut lui
apporter.
Dans cette méthode, on effectue un certain nombre de parcours du tableau à classer,
chaque parcours effectuant une amorce de classement. Plus précisément, un parcours
consiste à aller d’un bout à l’autre du tableau en effectuant la comparaison de deux
éléments successifs et en les permutant s’ils ne sont pas classés. Cette comparaison
remonte dans le tableau comme une bulle, en entraînant l’extremum.
void Tri_Bulles (int *TAB, int n) {
int i, j; //indices courants
int tmp; //variable tampon
for (i=1; i<=n-1 ; i++)
for (j=n-1; j>=i ; j--)
if (TAB[j-1] > TAB[j]) {
x = TAB[j-1];
TAB[j-1] = TAB[j];
TAB[j] = x;
}
}

Remarques :
1) Si après un parcours, l’élément maximal est rangé, le deuxième parcours peut
donc être limité à N-1 positions, etc. Une manière évidente d’améliorer cet
algorithme est donc de se souvenir qu’il y a eu ou non une permutation
pendant une passe et de se rappeler de la position de la dernière permutation.
Cet algorithme sera écrit en exercice d’application.

Page 9 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

2) A cette amélioration, on peut ajouter une autre qui consiste à alterner le sens
du parcours dans le cas d’un tableau quasi-trié : c’est l’algorithme de Shakersort
ou alors le tri-shaker.

2.1.3.3. Tri par extraction


Cette méthode se base sur le principe suivant :
- On parcourt le tableau pour choisir l’élément de plus basse clé ;
- On échange cet élément avec le premier élément
- On répète ceci avec les N-1 éléments restants puis avec les N-2, etc. jusqu’à ce
que la partie non triée devienne vide.

void Tri_Extraction (int *TAB, int n) {


int i, j, k; //indices
int tmp;

for (i=0; i<n-1; i++) {


k = i;
tmp = TAB[i];
for (j = i+1; j <= n-1; j++) {
if (TAB[j] < tmp) {
k = j;
tmp = TAB[k];
}
}
TAB[k] = TAB[i];
TAB[i] = tmp;
}
}

Ce tri a aussi donné lieu à deux améliorations dont la méthode du tournoi sportif qui
consiste à prendre en compte toutes les informations recueillies durant les différents
parcours afin de trier plus rapidement le tableau et la méthode du tri par arbre qui
consiste à considérer le tableau à trier comme la représentation d’un arbre binaire.

2.2. Structure de données statique hétérogène : enregistrement


Comme nous l’avons définie en début de chapitre, une structure de données statique
hétérogène est caractérisée par le fait qu’elle possède un nombre fixe d’informations
de type différent. La structure représentative de cette organisation très riche est
l’enregistrement (en C, on parle de type structure) que nous allons spécifier aux trois
niveaux habituels.

- Spécification fonctionnelle
Un enregistrement est un ensemble d’informations de types différents accessibles
individuellement ou collectivement en lecture et écriture. On trouve de très
nombreux exemples d’enregistrements dans la vie courante tels que l’identification

Page 10 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

d’une personne pour la sécurité sociale qui comporte des informations aussi
différentes que le nom, le sexe, la date de naissance, le lieu de résidence, etc. De façon
générale, on peut représenter à l’aide d’un enregistrement toute collection
d’informations variées attachées à une personne ou un objet.

- Spécification logique
Pour spécifier au niveau logique un enregistrement nommé ENREG avec des champs
champ1, champ2, …, champn de types respectifs T1, T2, …, Tn, on utilise en C la
syntaxe suivante :
struct ENREG
{
T1 champ1;
T2 champ2;
...
Tn champn;
};

Exemple : On veut définir une structure Point permettant de stocker l’abscisse (x) et
l’ordonnée (y) d’un point.
struct Point
{
int x ;
int y ;
};

Attention ! Vous devez mettre un point-virgule après l’accolade fermante. C’est


obligatoire. Si vous ne le faites pas, la compilation plantera.

Les enregistrements peuvent contenir des tableaux. Soit une structure Personne :
struct Personne
{
char nom[20];
char prenom[20];
int age;
};

Pour déclarer une variable etudiant de type structure Personne, on va écrire


struct Personne etudiant

Pour accéder à une composante de la structure, on doit écrire :


variable.nomDeLaComposante
Exemple : etudiant.age

Le typedef
On peut ajouter une instruction appelée typedef qui sert à créer un alias c’est-à-dire à
dire qu’écrire telle chose équivaut à écrire telle autre chose.
typedef struct Personne Personne ;

Page 11 sur 36
Conception et Implémentation des Structures de Données Les structures de données statiques

signifie donc que, écrire Personne équivaut à écrire struct Personne.

Ainsi, pour déclarer une variable etudiant de type structure Personne, on va écrire
Personne etudiant ;
au lieu de
struct Personne etudiant ;
comme précédemment.

NB : Le typedef ne fonctionne pas qu’avec les structures. On peut l’utiliser avec les
types simples.
Exemple : typedef int Entier ;

- Spécification physique
Les enregistrements sont stockés en mémoire de manière contiguë avec ou sans
optimisation de place.

Exercices d’application
1) Ecrire un programme qui additionne deux matrices d’entiers de dimensions
NxP. Le résultat est stocké dans une 3ème matrice.

2) Ecrire un programme qui multiplie deux matrices d’entiers de dimensions


respectives NxP et PxQ. Le résultat est stocké dans une 3ème matrice.

3) Ecrire la fonction INSERER qui place un élément X à l'intérieur d'un tableau


qui contient N éléments triés par ordre croissant, de façon à obtenir un tableau
à N+1 éléments triés par ordre croissant. La dimension du tableau est
incrémentée dans la fonction INSERER.
Ecrire la fonction TRI_INSERTION qui utilise la fonction INSERER pour
trier par ordre croissant les éléments d'un tableau à N éléments.
Ecrire un programme pour tester la fonction TRI_INSERTION.

Méthode : Trier le tableau de gauche à droite en insérant à chaque fois


l'élément I+1 dans le tableau (déjà trié) des I premiers éléments.

Page 12 sur 36
3.
LES STRUCTURES DE
DONNEES DYNAMIQUES

3.1. Emergence du besoin de structures dynamiques


Jusque-là, nous avons étudié des structures statiques, c’est-à-dire qui ont un nombre
d’informations fixe, utilisées pour la représentation de données dont la taille est
connue à l’avance, et n’évolue pas dans le temps. En effet, très souvent dans la
pratique, on veut représenter des objets soit dont on ne connaît pas la taille a priori,
soit dont la taille est variable selon les cas au cours du temps. On est alors amené à
utiliser des structures qui peuvent évoluer, pour bien s’adapter à la représentation de
ces objets. Ce sont les structures de données dynamiques.

Exemple 1 : On doit lire sur un fichier d’entrée une suite de nombres sur lesquels on
effectuera un traitement ultérieur. On ne connaît pas la quantité de nombres à lire et
on ne peut les compter avant. Quelles sont les structures possibles pour représenter
l’ensemble de ces nombres ?

Exemple 2 : On veut représenter des expressions arithmétiques simples formées sur


le modèle récursif suivant :
expression = (terme opérateur terme)
terme = lettre | expression
opérateur = + | - | * | /

telles que par exemple :


(X + Y)
(X – (Y * Z))
((X + Y) * (Z – T))

Le fait que le modèle de production soit récursif permet de construire en théorie des
expressions de taille infinie ; on ne peut donc pas envisager une structure de taille
fixe pour représenter de telles expressions, même si l’on sait que, dans la pratique, on
devra se limiter à une taille finie.

Page 13 sur 36
Conception et Implémentation des Structures de Données Les structures de données dynamiques

Exemple 3 : On veut modéliser une file d’attente de clients pouvant payer leur
facture au seul guichet d’une agence de la compagnie d’électricité ouvert le samedi…

Après avoir mis en évidence, à l’aide de ces exemples, le besoin fondamental de


pouvoir définir des structures de données dynamiques, nous expliquerons de façon
générale comment les représenter en mémoire, puis nous étudierons l’exemple
particulier des enregistrements récursifs. Les chapitres suivants seront consacrés à
l’étude des principales structures dynamiques fondamentales.

3.2. Représentation physique des structures dynamiques


Le principe de base de la représentation est de suivre les évolutions de la structure,
en lui attribuant de la place en mémoire quand elle grandit et en la récupérant quand
elle diminue. Ceci est réalisé par un mécanisme d’allocation et libération dynamique
d’espace mémoire. Le mécanisme d’allocation et libération dynamique utilise une
zone mémoire particulière appelée tas (heap) dans laquelle on réserve quand c’est
nécessaire des emplacements qu’on libère ensuite quand on n’en a plus besoin. Ce
mécanisme permet de représenter correctement les structures dynamiques avec une
consommation minimale de mémoire puisqu’on n’alloue à la structure l’espace
mémoire pris dans le tas qu’au fur et à mesure de ses besoins et qu’on le restitue quand
la structure diminue.

Dans les langages modernes, on dispose de deux procédures standard d’acquisition et


de libération d’espace mémoire. En C, il s’agit des fonctions malloc et free.

3.2.1. La fonction malloc et l’opérateur sizeof

La fonction malloc de la bibliothèque stdlib nous aide à localiser et à réserver de la


mémoire au cours d'un programme. Elle nous donne accès au tas.

a) La fonction malloc
malloc( <N> )

fournit l'adresse d'un bloc en mémoire de <N> octets libres ou la valeur zéro s'il n'y
a pas assez de mémoire.

Attention !
Sur notre système, le paramètre <N> est du type unsigned int. A l'aide de malloc,
nous ne pouvons donc pas réserver plus de 65535 octets à la fois!

Page 14 sur 36
Conception et Implémentation des Structures de Données Les structures de données dynamiques

Exemple :

Supposons que nous ayons besoin d'un bloc en mémoire pour un texte de 4000
caractères. Nous disposons d'un pointeur T sur char (char *T). Alors l'instruction :
T = malloc(4000);

fournit l'adresse d'un bloc de 4000 octets libres et l'affecte à T. S'il n'y a plus assez de
mémoire, T obtient la valeur zéro.

Si nous voulons réserver de la mémoire pour des données d'un type dont la grandeur
varie d'une machine à l'autre, nous avons besoin de la grandeur effective d'une
donnée de ce type.

L'opérateur sizeof nous aide alors à préserver la portabilité du programme.

b) L’opérateur unaire sizeof

sizeof <var> fournit la grandeur de la variable <var>


sizeof <const> fournit la grandeur de la constante <const>
sizeof (<type>) fournit la grandeur pour un objet du type <type>

Exemple 1 :
Après la déclaration,
short A[10];
char B[5][10];

nous obtenons les résultats suivants sur un IBM-PC (ou compatible) :


sizeof A s'évalue à 20
sizeof B s'évalue à 50
sizeof 4.25 s'évalue à 8
sizeof "Bonjour !" s'évalue à 10
sizeof(float) s'évalue à 4
sizeof(double) s'évalue à 8

Exemple 2 :
Nous voulons réserver de la mémoire pour X valeurs du type int; la valeur de X est
lue au clavier :
int X;
int *PNum;
printf("Introduire le nombre de valeurs :");
scanf("%d", &X);
PNum = malloc(X*sizeof(int));

Page 15 sur 36
Conception et Implémentation des Structures de Données Les structures de données dynamiques

c) exit

S'il n'y a pas assez de mémoire pour effectuer une action avec succès, il est conseillé
d'interrompre l'exécution du programme à l'aide de la commande exit (de stdlib) et de
renvoyer une valeur différente de zéro comme code d'erreur du programme

3.2.2. La fonction free

Si nous n'avons plus besoin d'un bloc de mémoire que nous avons réservé à l'aide de
malloc, alors nous pouvons le libérer à l'aide de la fonction free de la bibliothèque
stdlib.
free( <Pointeur> )
libère le bloc de mémoire désigné par le <Pointeur> n'a pas d'effet si le pointeur a la
valeur zéro.

Attention !
1) La fonction free peut aboutir à un désastre si on essaie de libérer de la mémoire
qui n'a pas été allouée par malloc.
2) La fonction free ne change pas le contenu du pointeur ; il est conseillé d'affecter
la valeur zéro au pointeur immédiatement après avoir libéré le bloc de
mémoire qui y était attaché.
3) Si nous ne libérons pas explicitement la mémoire à l'aide free, alors elle est
libérée automatiquement à la fin du programme.

3.3. Les enregistrements récursifs


A priori, dans un enregistrement, un champ peut contenir un élément de n’importe
quel type, en particulier un enregistrement, et éventuellement un élément du type de
l’enregistrement initial : on a alors un enregistrement récursif.
En théorie, une telle définition récursive permet d’engendrer une structure
dynamique de taille éventuellement finie.
Exemple :

On considère les élèves du cours primaire dans les rangs la main gauche posée sur
l’épaule de l’autre élève. Proposer une définition d’une structure Eleve de telle
manière qu’il soit possible de retrouver à chaque fois l’élève devant l’élève actuel.

Page 16 sur 36
Conception et Implémentation des Structures de Données Les structures de données dynamiques

struct Eleve
{
char nom[50];
char prenom[50];
int age;
struct Eleve *suivant; /*le pointeur sur le prochain élève*/
} ;

Exercices d’application

1) Ecrire un programme qui lit 10 phrases d'une longueur maximale de 200


caractères au clavier et qui les mémorise dans un tableau de pointeurs sur char
en réservant dynamiquement l'emplacement en mémoire pour les chaînes.
Ensuite, l'ordre des phrases est inversé en modifiant les pointeurs et le tableau
résultant est affiché.
2) Ecrire un programme qui lit 10 mots au clavier (longueur maximale : 50
caractères) et attribue leurs adresses à un tableau de pointeurs MOT. Effacer
les 10 mots un à un, en suivant l'ordre lexicographique et en libérant leur
espace en mémoire. Afficher à chaque fois les mots restants en attendant la
confirmation de l'utilisateur (par 'Enter').
3) Ecrire un programme qui lit 10 mots au clavier (longueur maximale : 50
caractères) et attribue leurs adresses à un tableau de pointeurs MOT. Copier
les mots selon l'ordre lexicographique en une seule 'phrase' dont l'adresse est
affectée à un pointeur PHRASE. Réserver l'espace nécessaire à la PHRASE
avant de copier les mots. Libérer la mémoire occupée par chaque mot après
l'avoir copié. Utiliser les fonctions de la bibliothèque string.

Page 17 sur 36
4.
LES LISTES CHAÎNÉES

4.1. Notion de suite


Dans la vie courante, on a souvent affaire à des collections homogènes d’objets ou
d’individus dont la taille est susceptible de varier à la suite d’ajouts ou de retraits
d’éléments, organisées de la manière suivante :
- On sait repérer l’une, l’autre, ou les deux extrémités de la structure ;
- Les éléments sont ordonnés, et l’on peut donc définir le suivant et le précédent
de tout élément qui n’est pas une extrémité ;
- Pour atteindre un élément particulier, on doit nécessairement parcourir
séquentiellement la structure à partir d’une extrémité, jusqu’à la trouver.

On peut citer comme exemples d’une telle organisation : une file de voyageurs devant
un guichet, une file de voitures devant un poste de péage, etc. Nous appelons suite
une telle organisation dynamique homogène. Selon le mode d’exploitation d’une
suite, on définit différentes structures de données que nous allons présenter
maintenant.

4.2. Les listes chaînées


- Spécification fonctionnelle

Une liste chaînée est un ensemble ordonné et extensible d’éléments de même type
auxquels on accède séquentiellement, et où l’on peut ajouter ou retrancher un
élément en n’importe quelle position. C’est donc une suite sur laquelle on peut
effectuer une gamme très large d’opérations. Par exemple, un éditeur de textes utilise
une telle structure pour représenter un texte source qui apparaît comme une suite de
lignes dans laquelle on peut effectuer des insertions et des retraits en n’importe quel
point.

Page 18 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

- Spécification logique

Une liste chaînée peut être décomposée en deux parties : le début de la liste et la suite.
On définira alors une liste chaînée d’éléments de type T de la façon suivante :
struct element {
T tete;
struct element* suivant; /* le pointeur sur le prochain élément */
};

En effet, suivant qui est le prochain élément sur la liste représente la suite de la liste.
Il suffira de placer à chaque fois un pointeur sur un élément d’une liste pour avoir la
liste à partir de cet élément. Il suffira donc de faire une déclaration de ce type :
struct element* listChainee

Pour simplifier l’écriture, on peut définir des types structure à l’aide de typedef.
Exemple : On veut définir une liste chaînée d’entiers.
typedef struct element {
int tete;
struct element* suivant;
} TElement;

typedef TElement* TListEntiers;

Pour déclarer un élément de la liste, on écrira donc :


TElement e;

Et pour une liste, on écrira :


TListEntiers L;

Pour créer une nouvelle liste vide, on écrira :


L = NULL ;

On peut aussi combiner la déclaration et l’initialisation de la liste de cette manière :


TListEntiers L = NULL;

On définira, par ailleurs, les fonctions d’insertion en tête, en queue de liste, de


suppression en tête, en queue de liste, d’insertion ou de suppression à une position
quelconque, d’affichage d’une liste et du test de vacuité d’une liste.

- Spécification physique

Il y a deux manières de représenter en mémoire les listes chaînées :

Page 19 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

1) La représentation par tableaux : on se sert alors de deux tableaux et d’une


variable repérant le début. Cette représentation est facile à mettre à œuvre
mais présente des inconvénients majeurs comme la limitation a priori de la
taille de la liste et la perte de la place occupée par les éléments retirés.
2) La représentation par pointeurs :

NULL

Page 20 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

Exemple 1 : Ajout d’un élément à une position quelconque

NULL

TETE

On insère l’élément pointé par P après l’élément pointé par Q.

Exemple 2 : Suppression d’un élément à une position quelconque

NULL

TETE

Q P

On retire l’élément pointé par P. Mais pour cela, il faut s’assurer qu’il y ait toujours un
pointeur Q sur l’élément précédant celui pointé par P afin de ne pas perdre la liste.

Extension des listes chaînées

1) Telles que nous les avons présentées, les listes chaînées ne peuvent être
parcourues que dans un seul sens. Quelquefois, il est intéressant pour traiter
certains problèmes d’effectuer des parcours dans les deux sens ; on définit alors
des listes doublement chaînées où chaque élément possède un lien de chaînage
vers l’élément suivant et un lien de chaînage vers l’élément précédent.

Page 21 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

2) Une autre extension possible des listes chaînées consiste à poser que le premier
élément est le suivant du dernier : on définit ainsi une liste circulaire. On peut
aussi avoir des listes circulaires doublement chaînées.

4.3. Les files d’attente


- Spécification fonctionnelle
Cette structure se présente comme un cas particulier de la structure précédente. Elle
modélise par exemple des travaux en attente d’exécution dans un système de
traitement par lots. L’étude des exemples de file d’attente permet de dégager les
caractéristiques fonctionnelles. Une file d’attente est une structure de données
dynamique homogène avec un accès en entrée et un accès en sortie. Les données sont
ajoutées par l’intermédiaire d’un poste d’écriture appelé queue et prélevés par
l’intermédiaire d’un poste de lecture appelée tête. Une file d’attente est donc une
structure à accès FIFO (First In, First Out) c’est-à-dire premier entré, premier sorti.
NB : Il existe une autre forme de file appelée file séquentielle dont nous ne traiterons
pas dans ce cours. Il y a une différence entre ces deux types de files. Dans le cas d’une
file séquentielle, on effectue des lectures sans modification alors que dans le cas de la
file d’attente, on peut faire des suppressions effectives d’éléments. De plus, l’ajout
d’éléments s’effectue sans problème dans le cas d’une file d’attente, alors que dans le
cas d’une file séquentielle, l’ajout d’un élément au milieu de la file rend inaccessibles
tous les éléments situés derrière.

- Spécification logique
Comme la liste, on peut décomposer une file d’attente en deux parties : d’une part la
tête qui correspond au premier élément (celui qui peut être retiré), d’autre part le
corps qui comprend tous les autres éléments. Cette décomposition met en évidence le
rôle particulier joué par l’élément de tête alors que l’élément de queue n’a pas besoin
d’être traité séparément car il ne joue aucun rôle dans le fonctionnement d’une file
d’attente.
Les primitives d’accès à une file d’attente permettent de retirer l’élément de tête
(fonction DEFILER), de rajouter un élément à la file (ENFILER) et de déterminer
si la file est vide (VIDE).
Ces fonctions peuvent être retrouvées à travers celles qui ont été écrites au niveau
des structures de liste chaînée. Il faut noter la primitive ENFILER est récursive.
Dans la pratique, il faut effectuer un test pour voir s’il y a de la place mémoire avant
d’effectuer ENFILER.

Page 22 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

4.4. Les piles


- Spécification fonctionnelle

Cette structure correspond à la notion usuelle de pile d’assiettes, pile de dossiers, etc.
La caractéristique essentielle d’une pile est que l’on retire toujours d’abord le dernier
élément déposé, l’ordre de sortie étant donc l’inverse de l’ordre d’entrée. On utilise
fréquemment la structure de pile en informatique comme par exemple dans les appels
en cascade de sous-programmes, où l’on termine d’abord le sous-programme appelé
en dernier.

On peut définir une pile comme une structure de données dynamique homogène à un
seul point d’accès, les données sont ajoutées ou retranchées par l’intermédiaire d’une
tête d’accès appelé SOMMET de la pile. La gestion d’une pile est dite LIFO (Last In,
First Out) c’est-à-dire denier entré, premier sorti.

- Spécification logique

Au niveau logique, on décompose une pile en deux parties : le sommet et le corps. Les
opérations caractéristiques d’une pile sont EMPILER pour ajouter un élément et
DEPILER pour retirer un élément.

Ces deux fonctions correspondent à d’autres déjà écrites au niveau de la structure


liste chaînée.

Remarque :
Au niveau physique, la file d’attente et la file peuvent être représentées de manière
chaînée ou de manière contiguë comme les listes chaînées.

Exercice d’application

On considère les élèves du cours primaire dans les rangs la main gauche posée sur
l’épaule de l’autre personne comme une liste chainée.

▪ Ecrire la définition d’un type élève (nom, age)


▪ Ecrire une fonction estVide qui permet de tester si la liste d’élèves passée en
paramètre est vide ou pas

Page 23 sur 36
Conception et Implémentation des Structures de Données Les listes chainées

▪ Ecrire une fonction afficherListe qui permet d’afficher les objets d’une liste
passée en paramètre
▪ Ecrire une fonction ajouterEleveDebut puis une fonction
supprimerEleveDebut qui permet d’insérer ou de supprimer un élève en début
de la liste (premier élève de la liste)
▪ Ecrire une fonction ajouterEleveFin puis une fonction supprimerEleveFin qui
permet d’insérer ou de supprimer un élève à la fin de la liste (dernier élève de
la liste)
▪ Ecrire une fonction ajouterEleveDevantEleveX puis une fonction
supprimerEleveDevantEleveX qui permet d’insérer ou de supprimer un élève
devant l’élève X de la liste
▪ Ecrire une fonction ajouterEleveDerriereEleveX puis une fonction
supprimerEleveDerriereEleveX qui permet d’insérer ou de supprimer un élève
derrière l’élève X de la liste

Page 24 sur 36
5.
LES ARBRES

5.1. Notion d’arbre


L’arbre est une structure de données fondamentale en informatique, très utilisée dans
tous les domaines, parce que bien adaptée à la représentation naturelle
d’informations homogènes organisées, et d’une grande commodité et rapidité de
manipulation. On rencontre cette structure dans tous les domaines de l’informatique,
que ce soit par exemple en algorithmique (support de méthodes performantes de tris
ou de gestion d’information en tables), ou bien en compilation (arbres syntaxiques
pour représenter les expressions ou productions possibles d’un langage) ou encore
dans les domaines de l’intelligence artificielle (arbres de jeux, arbres de décisions, de
résolution, etc.).

Exemple 1 : représentation d’une expression arithmétique


Une expression arithmétique comme A – (B + C * (D – E)) * F se représente
facilement par un arbre où apparaît clairement la priorité des opérations.

A *

+ F

B *

C -

D E

Page 25 sur 36
Conception et Implémentation des Structures de Données Les arbres

Exemple 2 : arbre de décisions

Dans les domaines de l’Intelligence Artificielle (jeux, génération de plans,


démonstration automatique de théorèmes, systèmes experts, etc.), il est rarement
possible de prévoir à l’avance le déroulement exact des calculs ou plus généralement
des manipulations de données à effectuer. A chaque étape de la résolution, plusieurs
décisions (coups, actions, inférences, etc.) sont possibles, chacune d’elles conduisant
à un nouvel état d’avancement dans la résolution. La recherche d’un chemin de
décisions nécessite la construction et l’exploration souvent par essais successifs et
retours en arrière en cas d’échec ou d’impasse, d’un arbre formé de tout ou partie des
différents états et transitions possibles, appelé espace de recherche.

choix i
état initial
état i

5.2. Spécification fonctionnelle


La structure d’arbre n’est pas caractérisée à proprement parler par des
fonctionnalités particulières comme le sont par exemple les piles et les files ; sa
caractérisation est plutôt morphologique, et donc à la frontière entre les niveaux
fonctionnel et logique. Intuitivement, un arbre est une collection d’informations
homogènes organisée en niveaux, où chaque information d’un niveau donné peut être
reliée à plusieurs informations d’un niveau inférieur, par des branches ne formant
pas de boucles (sinon, on a affaire à un graphe), et représentant soit un lien de nature
particulière (sémantique, hiérarchique, relation d’ordre, etc.) soit une transition ou
une action possible.
Plus précisément, on peut définir simplement un arbre de façon récursive de la façon
suivante : un arbre d’éléments de type T est soit vide, soit formé d’une donnée de
type T appelée racine et d’un ensemble fini de taille variable d’arbres de type T
appelés sous-arbres.

Page 26 sur 36
Conception et Implémentation des Structures de Données Les arbres

On appellera tout naturellement feuille la racine d’un arbre n’ayant pas lui-même de
sous-arbres. Plus généralement, on appellera nœud la racine de tout sous-arbre, la
racine et les feuilles étant des nœuds particuliers. Un sous-arbre d’un arbre sera
appelé son fils et inversement celui-ci sera appelé père de celui-là. Deux sous-arbres
du même arbre seront des frères, et leurs racines respectives sont des nœuds frères.

Cas particulier des arbres binaires


Dans ce qui suit, nous nous limiterons à l’étude des arbres binaires qui sont un cas
particulier des arbres généraux lorsque chaque nœud est la racine d’au plus deux
sous-arbres, appelés respectivement sous-arbre droit et sous-arbre gauche. Cette
limitation n’est pas pénalisante car on peut transformer un arbre quelconque en
arbre binaire en introduisant alors une distinction entre le lien gauche (pour la
descendance par exemple) et le lien droit (pour la fraternité). Il s’ensuit que pour un
arbre binaire, les liens entre un père et ses deux fils ne sont pas nécessairement
équivalents.

5.3. Spécification logique des arbres binaires


5.3.1. Définitions

Dans le cas d’un arbre binaire, la définition récursive se réduit à : un arbre binaire
est soit vide, soit formé d’une racine (RACINE) et de deux sous-arbres, le sous-arbre
gauche (SAG) et le sous-arbre droit (SAD) qui sont eux-mêmes des arbres.

On déclarera ainsi un arbre d’entiers :


typedef struct arbre
{
int racine;
struct arbre *gauche;
struct arbre *droite;
} arbre;

5.3.2. Primitives d’accès

Comme dit précédemment, la structure d’arbre n’est pas liée à des primitives de
manipulation caractéristiques, comme EMPILER et DEPILER pour les piles. En
fait, on distingue les arbres quelconques sur lesquels les seuls algorithmes usuels de
manipulation sont les parcours, des arbres ordonnés sur lesquels il peut être
intéressant de définir en plus des primitives standard de recherche, d’ajout et de
retrait d’un élément avec conservation de la propriété d’ordre qui qualifie l’arbre.

Page 27 sur 36
Conception et Implémentation des Structures de Données Les arbres

NB : Dans tous les cas, on dispose du prédicat logique VIDE(A) qui indique si l’arbre
A est vide ou non.

5.3.3. Parcours d’arbres binaires

On appelle parcours d’un arbre tout algorithme permettant d’accéder une fois et une
seule à tous les nœuds de l’arbre. On distingue six parcours généraux, symétriques
deux à deux, valides pour des arbres binaires quelconques et répartis en trois
catégories qui sont les parcours préfixes ou en préordre, les parcours postfixes ou en
postordre et les parcours infixes ou en ordre.

Parcours préfixes
Faire un parcours préfixe d’un arbre binaire, c’est d’abord inspecter la RACINE puis
faire un parcours préfixe du SAG (resp. du SAD) et enfin faire un parcours préfixe
du SAD (resp. du SAG).

5 8

1 4 3

7 6

Parcours préfixe RACINE, SAG, SAD : 2 5 1 7 4 8 3 6


Parcours préfixe RACINE, SAD, SAG : 2 8 3 6 5 4 1 7
En langage C, la fonction suivante permet de faire un parcours préfixe RACINE,
SAG, SAD.
void ParcoursPrefixe(arbre *tree)
{
if (!tree) return;
if (tree->gauche) ParcoursPrefixe(tree->gauche);
printf ("Cle = %d\n", tree->racine);
if (tree->droite) ParcoursPrefixe(tree->droite);
}

Page 28 sur 36
Conception et Implémentation des Structures de Données Les arbres

Parcours postfixes
Faire un parcours postfixe d’un arbre binaire, c’est d’abord faire un parcours postfixe
du SAG (resp. du SAD) puis du SAD (resp. du SAG) et enfin inspecter la RACINE.
Exemple : en considérant le même arbre que précédemment, on a :
Parcours postfixe SAG, SAD, RACINE : 7 1 4 5 6 3 8 2
Parcours postfixe SAD, SAG, RACINE : 6 3 8 7 1 4 5 2
La fonction ParcoursPostfixe sera écrite en exercice d’application

Parcours infixe
Faire un parcours infixe d’un arbre binaire, c’est faire un parcours infixe du SAG
(resp. du SAD) puis inspecter la RACINE et enfin faire un parcours infixe du SAD
(resp. du SAG).
Exemple : toujours avec le même arbre, on obtient dans ce cas :
Parcours infixe SAG, RACINE, SAD : 1 7 5 4 2 8 6 3
Parcours infixe SAD, RACINE, SAG : 3 6 8 2 4 5 7 1
La fonction ParcoursInfixe sera écrite en exercice d’application

5.3.4. Arbres ordonnés

Arbre binaire ordonné horizontalement


Un arbre binaire est ordonné horizontalement (de gauche à droite) si la clé 2 de tout
nœud non feuille est supérieure à toutes celles de son SAG et inférieure à toutes celles
de son SAD.

Exemple :

2 La clé est l’élément caractéristique du nœud.

Page 29 sur 36
Conception et Implémentation des Structures de Données Les arbres

13

9 45

5 11 50

7 10 12 48 52

Recherche d’un élément


On peut donner un algorithme standard pour expliquer le principe de la recherche
d’un élément dans un arbre binaire ordonné horizontalement. On notera que cette
recherche est dichotomique et donc très supérieure à une recherche systématique à
l’aide d’un parcours général comme ceux étudiés précédemment.
int recherche (arbre *tree, int nbre)
{
while (tree)
{
if (nbre == tree->racine) return 1;
if (nbre > tree->racine) tree = tree->droite;
else tree = tree->gauche;
}
return 0;
}

Insertion d’un élément


Le principe de l’insertion d’un élément dans un arbre binaire ordonné
horizontalement est le suivant : on recherche de façon récursive la place de l’élément,
ce qui conduit nécessairement à une feuille si l’élément ne figure pas déjà dans l’arbre,
puis on accroche l’élément à cette feuille.

Exemple : insertion de l’élément 6 dans l’arbre suivant :

Page 30 sur 36
Conception et Implémentation des Structures de Données Les arbres

8 8

4 10 4 10

5 9 13 5 9 13
0 0 0 0 0 0

6
0

Le code C du principe de l’insertion s’écrira en exercice d’application

Suppression d’un élément

Le principe de la suppression d’un élément dans un arbre binaire ordonné


horizontalement est le suivant :
- si l’élément est une feuille, alors on le supprime simplement ;
- si l’élément n’a qu’un descendant, alors on le remplace par ce descendant ;
- si l’élément a deux descendants, on le remplace au choix soit par l’élément le
plus à droite du SAG, soit par l’élément le plus à gauche du SAD afin de
conserver la propriété d’ordre horizontal.

Exemple 1 : suppression de l’élément 3

3 9

2 5 11

4 6 10 19

17

Page 31 sur 36
Conception et Implémentation des Structures de Données Les arbres

4 9

2 5 11

6 10 19

17

Exemple 2 : suppression de l’élément 7

16

7 18

4 9 24

2 6 8 10

16

6 18

4 9 24

2 8 10

Le code C du principe de la suppression s’écrira en exercice d’application

Arbre binaire ordonné verticalement


Un arbre binaire est ordonné verticalement si la clé de tout nœud non feuille est
inférieure (respectivement supérieure) à celle de ses fils (et donc par récurrence à celle
de tous ses descendants).

Nous ne traiterons pas spécifiquement des arbres binaires ordonnés verticalement.

Page 32 sur 36
Conception et Implémentation des Structures de Données Les arbres

5.4. Spécification physique


On distingue deux représentations :
- la représentation par pointeurs,
- la représentation par tableaux.

Dans ce cours, nous ne présenterons que la représentation par pointeurs.

NULL

On manipule l’arbre via un pointeur qui pointe sur la racine de l’arbre, qui elle-même
a deux pointeurs qui pointent sur ses nœuds fils et ainsi de suite

Exercices d’application

1. Ecrire la fonction vide qui retourne 1 si l’arbre est vide et 0 dans le cas
contraire.

2. Ecrire une fonction taille qui permet de calculer la taille d’un arbre. Cette
fonction calcule de façon récursive le nombre de nœuds de l’arbre. Si l’arbre est vide,
la fonction retourne 0. Si l’arbre contient au moins un nœud, la fonction retourne la
somme des tailles du SAG et SAD plus 1.

3. Ecrire une fonction hauteur qui permet de calculer la hauteur d’un arbre. Elle
la calcule de façon récursive. Si l’arbre est vide, hauteur retourne 0. Si l’arbre contient
au moins un nœud, hauteur retourne le maximum des hauteurs des sous-arbres plus
1.

4. Parcours en largeur : il s’agit ici d’un parcours de l’arbre couche par couche.

Page 33 sur 36
Conception et Implémentation des Structures de Données Les arbres

On part de l’idée suivante : pour parcourir l’arbre couche par couche, il faut essayer
de stocker les nœuds dans des couches. Plus précisément, on va maintenir pendant le
parcours deux couches : la "couche courante" qui contient les nœuds que l’on est en
train de parcourir, et la "couche des enfants" où on met les enfants de la couche
courante.
Un peu plus précisément, on a l’algorithme suivant :
• au départ, on met la racine dans la couche courante, on prend une liste
vide pour la couche des enfants ;
• ensuite, on parcourt les nœuds de la couche courante en ajoutant leurs
enfants dans la couche des enfants ;
• quand on a terminé le parcours, on change les couches : on prend la
couche des enfants comme une nouvelle couche et on recommence le
parcours.
Quand, à la fin du parcours de la couche courante, on obtient une couche des enfants
vide, l’algorithme s’arrête. En effet, s’il n’y a pas d’enfant dans la couche des enfants,
cela veut dire qu’aucun des nœuds de la couche qui vient d’être parcourue n’avaient
d’enfants : ce n’était que des feuilles, donc on est arrivé à la fin de l’arbre.

Page 34 sur 36
Références bibliographiques
J. Guyot & C. Vial, Arbres, Tables et Algorithmes, Eyrolles, Paris, 1988.
N. Wirth, Algorithmes et Structures de Données, Eyrolles, Paris, 1988.
F. Faber & J.F. Anne, Programmation en C, Document numérique.

www.openclassrooms.com

Page 35 sur 36
Table des matières

1. GENERALITES...................................................................................................... 1
1.1. Problématique .................................................................................................... 1
1.2. Variable .............................................................................................................. 2
1.3. Type de données ................................................................................................. 2
1.4. Structure de données ........................................................................................... 3
1.5. Type abstrait de données .................................................................................... 4
1.6. Structure de données et type abstrait de données ................................................ 4
2. LES STRUCTURES DE DONNEES STATIQUES ................................................ 5
2.1. Structures de données statiques homogènes ........................................................ 5
2.1.1. Tableau monodimensionnel .......................................................................... 5
2.1.2. Tableau multidimensionnel .......................................................................... 7
2.1.3. Les méthodes de tri ...................................................................................... 8
2.2. Structure de données statique hétérogène : enregistrement ................................10
3. LES STRUCTURES DE DONNEES DYNAMIQUES ...........................................13
3.1. Emergence du besoin de structures dynamiques .................................................13
3.2. Représentation physique des structures dynamiques .........................................14
3.2.1. La fonction malloc et l’opérateur sizeof .......................................................14
3.2.2. La fonction free ...........................................................................................16
3.3. Les enregistrements récursifs .............................................................................16
4. LES LISTES CHAÎNÉES ......................................................................................18
4.1. Notion de suite ..................................................................................................18
4.2. Les listes chaînées ..............................................................................................18
4.3. Les files d’attente...............................................................................................22
4.4. Les piles .............................................................................................................23
5. LES ARBRES ........................................................................................................25
5.1. Notion d’arbre ...................................................................................................25
5.2. Spécification fonctionnelle .................................................................................26
5.3. Spécification logique des arbres binaires.............................................................27
5.3.1. Définitions ..................................................................................................27
5.3.2. Primitives d’accès .......................................................................................27
5.3.3. Parcours d’arbres binaires ...........................................................................28
5.3.4. Arbres ordonnés ..........................................................................................29
5.4. Spécification physique .......................................................................................33
Références bibliographiques ............................................................................................35

Page 36 sur 36

Vous aimerez peut-être aussi