Vous êtes sur la page 1sur 31

Cours de programmation

Structures de données élémentaires dynamiques

Olivier Aumage
LIP, ENS Lyon (bureau 302),
46, allée d’Italie, 69364 Lyon Cedex 07
email : Olivier.Aumage@ens-lyon.fr

Octobre 2001
2
Table des matières

1 Introduction 5
1.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Au programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Listes chaînées 7
2.1 Listes simplement chaînées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.1 Principe, forme générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Notion de maillon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Maillon suivant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Notion de liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Définitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.2 Définition en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Le maillon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
La liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Types à usage public, types à usage privé . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.3 Fonctions de parcours simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Longueur de la liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Affichage de la liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.4 Fonctions de modification de la liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Insertion en tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Suppression en tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Insertion en queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Suppression en queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1.5 Duplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
La notion d’aliasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Duplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2 Autres formes de listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.1 Listes doublement chaînées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Déclaration des maillons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Déclaration de la liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2.2 Listes circulaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3 Structures dérivées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.1 Piles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.2 Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 Arbres 13
3.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.1 Principe, forme générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Notion de nœud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3
Notion d’arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Arbres n-aires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.2 Arbres binaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.1 Déclaration en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Le nœud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
L’arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2.2 Algorithmes simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Taille de l’arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Hauteur de l’arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.3 Parcours en profondeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Exemple : affichage de l’arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Approche itérative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.4 Parcours en largeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3 Autres formes d’arbres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3.1 Arbres généralisés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Déclaration du nœud généralisé en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Déclaration de l’arbre en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 Structures dérivées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4 Tableaux et tables 21
4.1 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.1.1 Principe, forme générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.1.2 Définition en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Tableaux de types simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Tableaux de structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.3 Opérateur crochets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1.4 Fonctions de parcours simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Parcours linéaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Parcours dichotomique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2 Chaînes de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.1 Principe, forme générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.2 Définition en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.3 Fonctions de parcours simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 Tables de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Alvéoles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Résolution des collisions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Importance de la fonction de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3.2 Définition en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3.3 Mise en œuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Fonction de hachage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Ajout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Recherche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

4
Chapitre 1

Introduction

1.1 Définition
Une structure dynamique est une structure dont la taille peut varier en fonction des besoins.

1.2 Au programme
Il est prévu de voir les structures suivantes :
1. listes chaînées ;
2. arbres ;
3. tableaux et tables.

5
6
Chapitre 2

Listes chaînées

2.1 Listes simplement chaînées


2.1.1 Principe, forme générale
Notion de maillon
L’élément de base d’une liste chaînée s’appelle le maillon. Il est constitué :
– d’un champ de données ;
– d’un pointeur vers un maillon.

Maillon suivant
Le champ pointeur vers un maillon pointe vers le maillon suivant de la liste. S’il n’y a pas de maillon
suivant, le pointeur vaut NULL.

Notion de liste
Une liste simplement chaînée est un pointeur sur un maillon. Une liste vide est une liste qui ne contient
pas de maillon. Elle a donc la valeur NULL.

Définitions
La terminologie suivante est généralement employée :
– le premier maillon de la liste est appelé tête ;
– le dernier maillon de la liste est appelé queue.

2.1.2 Définition en C
Le maillon
La déclaration des types constituant le maillon est la suivante :
1 typedef struct s_maillon *p_maillon_t;

2 typedef struct s_maillon

3 {

4 int valeur;

5 p_maillon_t suivant;

6 } maillon_t;

7
La liste

La déclaration des types constituant la liste est la suivante :

1 typedef p_maillon_t liste_t;

2 typedef liste_t *p_liste_t;

Types à usage public, types à usage privé

Deux types de fonctions vont interagir avec les listes :


– les fonctions qui utilisent les listes ;
– les fonctions qui gèrent les listes.
Les premières voient l’aspect externe des listes (les types liste_t et p_liste_t) alors que les secondes
voient l’aspect interne des listes (les types maillon_t et p_maillon_t).
Les fonctions qui utilisent les listes n’ont pas à savoir que les listes sont constituées de maillons.

2.1.3 Fonctions de parcours simples


Longueur de la liste
– Version itérative :
1 int longueur_i(liste_t liste)

2 {

3 p_maillon_t p_maillon = liste;

4 int longueur = 0;

6 while (p_maillon != NULL)

7 {

8 longueur++;

9 p_maillon = p_maillon->suivant;

10 }

11

12 return longueur;

13 }
– Version récursive :
1 int longueur_r(liste_t liste)

2 {

3 p_maillon_t p_maillon = liste;

5 if (p_maillon != NULL)

6 {

7 return 1 +

8 longueur_r(p_maillon->suivant);

9 }

10

11 return 0;

12 }

8
Affichage de la liste
Note : les fonctions suivantes sont données uniquement en version récursive, mais les versions itératives
sont également possibles.

1 void affichage(liste_t liste)

2 {

3 p_maillon_t p_maillon = liste;

5 if (p_maillon)

6 {

7 printf("%d ", p_maillon->valeur);

8 affichage(p_maillon->suivant);

9 }

10 else

11 {

12 printf("\n");

13 }

14 }

2.1.4 Fonctions de modification de la liste


Insertion en tête
1 void

2 insertion_en_tete(p_liste_t p_liste,

3 int entier)

4 {

5 p_maillon_t p_maillon_ancien = *p_liste;

6 p_maillon_t p_maillon_nouveau = NULL;

8 p_maillon_nouveau = malloc(sizeof(maillon_t));

10 p_maillon_nouveau->valeur = entier;

11 p_maillon_nouveau->suivant = p_maillon_ancien;

12

13 *p_liste = p_maillon_nouveau;
14 }

Suppression en tête
1 int

2 suppression_en_tete(p_liste_t p_liste)

3 {

4 p_maillon_t p_maillon = *p_liste;

5 int resultat = 0;

9
7 if (p_maillon != NULL)

8 {

9 resultat = p_maillon->valeur;

10 *p_liste = p_maillon->suivant;
11 free(p_maillon);

12 }

13

14 return resultat;

15 }

Insertion en queue

1 void

2 insertion_en_queue(p_liste_t p_liste,

3 int entier)

4 {

5 p_maillon_t p_maillon = *p_liste;

7 if (p_maillon == NULL)

8 {

9 insertion_en_tete(p_liste, entier);

10 }

11 else

12 {

13 insertion_en_queue(&(p_maillon->suivant), entier);

14 }

15 }

Suppression en queue

1 int

2 suppression_en_queue(p_liste_t p_liste)

3 {

4 p_maillon_t p_maillon = *p_liste;

5 int resultat = 0;

7 if (p_maillon != NULL)

8 {

9 if (p_maillon->suivant == NULL)

10 {

11 resultat = suppression_en_tete(p_liste);

12 }

13 else

14 {

15 resultat =

10
16 suppression_en_queue(&(p_maillon->suivant));

17 }

18 }

19

20 return resultat;

21 }

2.1.5 Duplication
La notion d’aliasing
À la suite d’une erreur de programmation, il est possible que des maillons de listes différentes soient
mélangés. Concrêtement, deux maillons de deux listes différentes pointent vers le même maillon. Ce dernier
est donc accessible depuis deux listes différentes. Ce phénomène est appelé aliasing. Il doit absolument être
évité.

Duplication
1 liste_t dup(liste_t liste_source)

2 {

3 p_maillon_t p_maillon_source = liste_source;

4 p_maillon_t p_maillon_destination = NULL;

6 if (p_maillon_source != NULL)

7 {

8 p_maillon_destination = malloc(sizeof(maillon_t));

10 p_maillon_destination->valeur = p_maillon_source->valeur;

11 p_maillon_destination->suivant = dup(p_maillon->suivant);

12 }

13

14 return p_maillon_destination;

15 }

2.2 Autres formes de listes


2.2.1 Listes doublement chaînées
Déclaration des maillons
1 typedef struct s_maillon *p_maillon_t;

2 typedef struct s_maillon

3 {

4 int valeur;

5 p_maillon_t precedent;

6 p_maillon_t suivant;

7 } maillon_t;

11
Déclaration de la liste
1 typedef struct s_liste

2 {

3 p_maillon_t tete;

4 p_maillon_t queue;

5 } liste_t;

6 typedef liste_t *p_liste_t;

2.2.2 Listes circulaires


Principe
Les listes circulaires peuvent se présenter sous la forme de listes simplement ou doublement chaînées.
Leur particularité est que la queue est reliée à la tête (pour la version doublement chaînée, la tête est égale-
ment reliée à la queue).

2.3 Structures dérivées


2.3.1 Piles
Les piles sont aussi appelées stack ou LIFO (Last-In, First-Out) : le dernier élément ajouté est le premier
retiré.
Les piles sont accessibles uniquement avec les deux fonctions suivantes :
– void push(pile, objet) ajoute un objet sur la pile ;
– objet pop(pile) enlève et retourne le premier objet au-dessus de la pile.

2.3.2 Files
Les files sont aussi appelées queue (en anglais) ou FIFO (First-In, First-Out) : le premier élément ajouté
est le premier retiré.
Les files sont accessibles uniquement avec les deux fonctions suivantes :
– void enqueue(file, objet) ajoute un objet à la file ;
– objet dequeue(file) enlève et retourne le premier objet de la file.

12
Chapitre 3

Arbres

3.1 Généralités
3.1.1 Principe, forme générale
Notion de nœud
L’élément de base d’un arbre s’appelle le nœud. Il est constitué :
– d’un champ de donnée ;
– d’un ou plusieurs pointeurs vers des nœuds.

Notion d’arbre
Un arbre est un pointeur sur un nœud. Un arbre vide est un arbre qui ne contient pas de nœud. Il a donc
la valeur NULL.

Terminologie
On utilise classiquement la terminologie suivante pour décrire les arbres :
Père Le père A d’un nœud B est l’unique nœud tel que :
– un des sous-arbres contient B
– hauteur(A) − hauteur(B) = 1.
Fils Les fils d’un nœud sont les nœuds de ses sous-arbres de hauteur immédiatement inférieure.
Frères Les frères d’un nœud A sont les nœuds qui possèdent le même père que A.
Sous-arbre Un sous-arbre d’un nœud A est un arbre dont la racine est un fils de A.
Racine La racine de l’arbre est l’unique nœud qui n’a pas de père.
Feuille Une feuille de l’arbre est un nœud qui n’a pas de fils.
Branche Un arc qui relie deux nœuds est une branche.
Hauteur Définition récursive :
– La hauteur d’un arbre vide est nulle.
– La hauteur d’un arbre non-vide est égale à la hauteur de sa racine.
– La hauteur d’un nœud est égale à 1 plus le maximum des hauteurs de ses sous-arbres.

Arbres n-aires
Un arbre dont les nœuds ont au plus n fils est un arbre n-aire. Lorsque n vaut 2, l’arbre est dit binaire.
Dans le cas particulier de l’arbre binaire on utilise les termes de fils gauche et fils droits d’un nœud, ainsi que
les notions de sous-arbre gauche et de sous-arbre droit.

13
3.2 Arbres binaires
La forme d’arbre la plus simple à manipuler est l’arbre binaire. De plus les formes plus générales
d’arbres (avec n > 2) peuvent être représentées par des arbres binaires.

3.2.1 Déclaration en C
Le nœud
La déclaration des types constituant le nœud est la suivante :
1 typedef struct s_noeud *p_noeud_t;

2 typedef struct s_noeud

3 {

4 int valeur;

5 p_noeud_t fils_gauche;

6 p_noeud_t fils_droit;

8 /* Optionnellement */

9 p_noeud_t pere;

10 } noeud_t;

Le pointeur vers le nœud père est optionnel. Il sert simplement à simplifier l’écriture et la complexité de
certains algorithmes. Le choix de l’utilisation ou non d’un pointeur père est à rapprocher du choix entre
une liste simplement chaînée et une liste doublement chaînée au chapitre précédent.

L’arbre
La déclaration des types constituant l’arbre est la suivante :
1 typedef p_noeud_t arbre_t;

2 typedef arbre_t *p_arbre_t;

3.2.2 Algorithmes simples


Taille de l’arbre
La fonction taille calcule le nombre de nœuds de l’arbre de manière récursive. 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
sous-arbre gauche et du sous-arbre droit plus 1.
1 int taille(arbre_t arbre)

2 {

3 p_noeud_t p_noeud = arbre;

5 if (p_noeud)

6 {

7 return 1 + taille(p_noeud->fils_gauche) + taille(p_noeud->fils_droit);

8 }

10 return 0;

11 }

14
Hauteur de l’arbre

La fonction hauteur calcule la hauteur de l’arbre de manière récursive. Elle utilise une fonction max
qui retourne le maximum de deux entiers. Si l’arbre est vide, la fonction hauteur retourne 0. Si l’arbre
contient au moins un nœud, la fonction hauteur retourne le maximum des hauteurs de ses sous-arbres
plus 1.

1 int max(int a, int b)

2 {

3 if (a > b)

4 {

5 return a;

6 }

8 return b;

9 }

10

11 int hauteur(arbre_t arbre)

12 {

13 p_noeud_t p_noeud = arbre;

14

15 if (p_noeud)

16 {

17 return 1 + max(hauteur(p_noeud->fils_gauche)

18 + hauteur(p_noeud->fils_droit));

19 }

20

21 return 0;

22 }

3.2.3 Parcours en profondeur


Le parcours dit en profondeur d’abord consiste à explorer un arbre de haut en bas puis de gauche à droite.
On distingue trois types de parcours en pronfondeur :
– le parcours préfixe traite le nœud courant puis le sous-arbre gauche et enfin le sous-arbre droit ;
– le parcours infixe traite le sous-arbre gauche, puis le nœud courant et enfin le sous-arbre droit ;
– le parcours postfixe traite le sous-arbre gauche, le sous-arbre droit et enfin le nœud courant.

Exemple : affichage de l’arbre

Les trois versions du parcours en profondeur récursif sont présentées ci-après. Pour chaque version, la
fonction disp affiche le contenu de l’arbre sous la forme d’une ligne de nombres entiers séparés par des
espaces.

Parcours préfixe Le traitement effectué sur les nœuds par la fonction disp est l’affichage du contenu
du nœud. Dans ce premier parcours préfixe, le traitement du nœud courant a lieu avant les deux appels
récursifs.

15
1 void disp(arbre_t arbre)

2 {

3 p_noeud_t p_noeud = arbre;

5 if (p_noeud)

6 {

7 printf("%d ", p_noeud->valeur);

8 disp(p_noeud->fils_gauche);

9 disp(p_noeud->fils_droit);

10 }

11 }

Parcours infixe

1 void disp(arbre_t arbre)

2 {

3 p_noeud_t p_noeud = arbre;

5 if (p_noeud)

6 {

7 disp(p_noeud->fils_gauche);

8 printf("%d ", p_noeud->valeur);

9 disp(p_noeud->fils_droit);

10 }

11 }

Parcours postfixe

1 void disp(arbre_t arbre)

2 {

3 p_noeud_t p_noeud = arbre;

5 if (p_noeud)

6 {

7 disp(p_noeud->fils_gauche);

8 disp(p_noeud->fils_droit);

9 printf("%d ", p_noeud->valeur);

10 }

11 }

Approche itérative
L’algorithme du parcours en profondeur préfixe peut être programmé très simplement selon une ap-
proche itérative en utilisant une structure de donnée supplémentaire : une pile (cf. section 2.3.1).

1 void disp(arbre_t arbre)

2 {

16
3 p_noeud_t p_noeud = arbre;

4 pile_t pile = NULL;

6 if (noeud)

7 {

8 push(pile, p_noeud);

10 do

11 {

12 p_noeud = pop(pile);

13

14 printf("%d ", p_noeud->valeur);

15

16 if (p_noeud->fils_droit)

17 {

18 push(pile, p_noeud->fils_droit);

19 }

20

21 if (p_noeud->fils_gauche)

22 {

23 push(pile, p_noeud->fils_gauche);

24 }

25 }

26 while (pile);

27 }

28 }

3.2.4 Parcours en largeur


Le parcours dit en largeur d’abord consiste à explorer un arbre de gauche à droite puis de haut en bas. Il est
très facile d’obtenir un parcours en largeur à partir du parcours en profondeur préfixe itératif en remplaçant
la pile par une file (cf. section 2.3.2).
1 void disp(arbre_t arbre)

2 {

3 p_noeud_t p_noeud = arbre;

4 file_t file = NULL;

6 if (noeud)

7 {

8 enqueue(file, p_noeud);

10 do

11 {

12 p_noeud = dequeue(file);

13

17
14 printf("%d ", p_noeud->valeur);

15

16 if (p_noeud->fils_gauche)

17 {

18 dequeue(file, p_noeud->fils_gauche);

19 }

20

21 if (p_noeud->fils_droit)

22 {

23 dequeue(file, p_noeud->fils_droit);

24 }

25 }

26 while (file);

27 }

28 }

3.3 Autres formes d’arbres


3.3.1 Arbres généralisés
Principe

Pour repésenter des arbres quelconques on utilise une variante de la représentation fils gauche/fils droit
appelée fils gauche/frère droit. Chaque nœud contient un pointeur vers son premier fils (le plus à gauche)
et un pointeur vers son frère droit (et éventuellement un pointeur vers son père comme pour la représen-
tation d’un nœud d’arbre binaire). Avec ce schéma, le nombre de fils d’un nœud donné est arbitraire et
indépendant du nombre de fils des autres nœuds.

Déclaration du nœud généralisé en C

1 typedef struct s_noeud_gen *p_noeud_gen_t;

2 typedef struct s_noeud_gen

3 {

4 int valeur;

5 p_noeud_gen_t fils_gauche;

6 p_noeud_gen_t frere_droit;

8 /* Optionnellement */

9 p_noeud_gen_t pere;

10 } noeud_gen_t;

Déclaration de l’arbre en C

1 typedef p_noeud_gen_t arbre_t;

2 typedef arbre_t *p_arbre_t;

18
3.4 Structures dérivées
– Les forêts sont des structures constituées d’un ou plusieurs arbres. Une manière de représenter une
forêt est d’utiliser un nœud généralisé ayant pour fils chacun des arbres de la forêt.
– Les treillis sont des structures similaires aux arbres, à ceci près que les nœuds peuvent avoir plusieurs
parents.
– Les graphes, orientés ou non, sont des structures encore plus générales que les arbres : chaque nœud
(souvent appelé sommet) contient une valeur et un certain nombre de pointeurs vers d’autres som-
mets appelés arêtes. Il n’y a plus de notion de parenté.

19
20
Chapitre 4

Tableaux et tables

4.1 Tableaux
4.1.1 Principe, forme générale
Définition

Un tableau — aussi appelé un vecteur — est un ensemble d’éléments de même type représentés consé-
cutivement en mémoire et désignés par un pointeur vers le premier de ces éléments.

Terminologie

On utilise classiquement la terminologie suivante à propos des tableaux :


Base La base du tableau est un pointeur vers son premier élément, c’est-à-dire une adresse vers le début
du tableau.
Longueur La longueur — aussi appelée la taille — du tableau est le nombre d’éléments du tableau.
Indices Les indices du tableau sont les numéros de chaque éléments du tableau. Si la longueur du tableau
est l, les indices sont les entiers compris entre 0 inclus et l − 1 inclus.
Bornes La borne inférieure d’un tableau est son plus petit indice (toujours 0 en C). La borne supérieure d’un
tableau est son plus grand indice (l − 1 si la longueur du tableau est l).

4.1.2 Définition en C
Tableaux de types simples

Tableaux statiques La définition d’un tableau statique (dont la taille ne peut pas être modifiée en cours
d’exécution) en C est la suivante :

1 {

2 int t[10];

3 ...

4 }

Tableaux dynamiques La définition d’un tableau dynamique d’éléments de type simples en C est la sui-
vante :

21
1 {

2 int *t = NULL;

3 int n = 10;

5 t = calloc(10, sizeof(int));

6 ...

7 free(t);

8 }

Tableaux de structures

Tableaux statiques

1 /* Type "pointeur sur ma_structure_t" */

2 typedef struct s_ma_structure *p_ma_structure_t;

4 /* Type "ma_structure_t" */

5 typedef struct s_ma_structure

6 {

7 int champ_int;

8 double champ_double;

9 } mad_structure_t;

10

11 {

12 p_ma_structure_t t[10];

13 int i = 0;

14

15 for (i = 0; i < 10; i++)

16 {

17 t[i] = malloc(sizeof(ma_structure_t));

18 t[i]->champ_int = 1;

19 t[i]->champ_double = 3.1415926;

20 ...

21

22 }

23 ...

24

25 for (i = 0; i < 10; i++)

26 {

27 free(t[i]);

28 t[i] = NULL;

29 }

30 }

Tableaux dynamiques

22
1 /* Type "pointeur sur ma_structure_t" */

2 typedef struct s_ma_structure *p_ma_structure_t;

4 /* Type "ma_structure_t" */

5 typedef struct s_ma_structure

6 {

7 int champ_int;

8 double champ_double;

9 } mad_structure_t;

10

11 {

12 p_ma_structure_t *t = NULL;

13 int n = 10;

14 int i = 0;

15

16 t = calloc(n, sizeof(p_ma_structure));

17

18 for (i = 0; i < 10; i++)

19 {

20 t[i] = malloc(sizeof(ma_structure_t));

21 t[i]->champ_int = 1;

22 t[i]->champ_double = 3.1415926;

23 ...

24

25 }

26 ...

27

28 for (i = 0; i < 10; i++)

29 {

30 free(t[i]);

31 t[i] = NULL;

32 }

33

34 free(t);

35 }

4.1.3 Opérateur crochets


L’opérateur [] utilisé sur un pointeur admet les équivalences suivantes (avec p, un pointeur quel-
conque, et i un indice valide quelconque du tableau pointé par p) :

1 p[i] <=> *(p + i)

3 &(p[i]) <=> &(*(p + i)) <=> p + i

23
4.1.4 Fonctions de parcours simples
Sauf mention contraire, les fonctions ci-dessous portent sur des tableaux d’entiers.

Parcours linéaire

Affichage itératif

1 void disp(int *t, int n)

2 {

3 int i = 0;

5 for (i = 0; i < n; i++)

6 {

7 printf("%d\n", t[i]);

8 }

9 }

Affichage récursif

1 void disp(int *t, int n)

2 {

3 if (n)

4 {

5 printf("%d\n", *t);

6 disp(t + 1, n - 1);)

7 }

8 }

Recherche

1 int find(int *t, int n, int entier)

2 {

3 /* Retourne l’indice de entier s’il existe, ou -1 sinon */

4 int i = 0;

6 for (i = 0; i < n; i++)

7 {

8 if (t[i] == entier)

9 {

10 return i;

11 }

12 }

13

14 return -1;

15 }

24
Parcours dichotomique
L’utilisation d’algorithmes dichotomiques suppose que le tableau à traiter soit trié.

Recherche dichotomique
1 int find_d(int *t, int n, int entier)

2 {

3 /* Retourne l’indice de entier s’il existe, ou -1 sinon */

4 int a = 0;

5 int b = n;

7 while (a < b)

8 {

9 int i = (b + a) / 2;

10

11 if (entier == t[i])

12 {

13 return i;

14 }

15 else if (entier < t[i])

16 {

17 b = i;

18 }

19 else

20 {

21 a = i + 1;

22 }

23 }

24

25 return -1;

26 }

4.2 Chaînes de caractères


4.2.1 Principe, forme générale
Les chaînes de caractères en C sont des tableaux dont les éléments sont de type char. De plus, le dernier
élément de la chaîne (pas nécessairement du tableau) doit être le caractère nul noté ’\0’ dont la valeur
numérique est 0, à ne pas confondre avec le caractère zéro noté ’0’ et dont la valeur numérique est 48 dans
le codage ASCII.

4.2.2 Définition en C
Tableaux statiques de caractères
1 {

2 char c[14] = "Hello, World!";

25
3

4 printf("%s\n", c); /* --> imprime Hello, World! + retour à la ligne

5 printf("%c\n", c[7]); /* --> imprime W + " "

6 }

Tableaux dynamiques de caractères


1 {

2 char *c = NULL;

4 c = calloc(1 + strlen("Hello, World!"), sizeof(char));

5 strcpy(c, "Hello, World!");

7 printf("%s\n", c); /* --> imprime Hello, World! + retour à la ligne

8 printf("%c\n", c[7]); /* --> imprime W + " "

10 free(C);

11 }

4.2.3 Fonctions de parcours simples


Longueur de la chaîne Version 1 :
1 int my_strlen(char *s)

2 {

3 int l = 0;

5 while (s[l] != ’\0’)

6 {

7 l++;

8 }

10 return l;

11 }

Version 2 :
1 int my_strlen(char *s)

2 {

3 char *p = s;

5 while (*p != ’\0’)

6 {

7 p++;

8 }

10 return p - s;

11 }

26
Version 3 :
1 int my_strlen(char *s)

2 {

3 char *p = s;

5 while (*p)

6 {

7 p++;

8 }

10 return p - s;

11 }

Copie d’une chaîne vers une autre


1 void my_strcpy(char *s_dst, char *s_src)

2 {

3 while ((*s_dst = *s_src))

4 {

5 s_src++;

6 s_dst++;

7 }

8 }

Concaténation d’une chaîne à la suite d’une autre


1 void my_strcat(char *s_dst, char *s_src)

2 {

3 while (*s_dst)

4 {

5 s_dst++;

6 }

8 my_strcpy(s_dst, s_src);

9 }

Comparaison de deux chaînes


1 void my_strcmp(char *s1, char *s2)

2 {

3 while (*s1 == *s2)

4 {

5 if (!*s1)

6 {

7 return 0;

8 }

27
9

10 s1++;

11 s2++;

12 }

13

14 return *s2 - *s1;

15 }

4.3 Tables de hachage


4.3.1 Principe
Comme les tableaux, les tables de hachage permettent de gérer des données par l’intermédiaire de clés
(appelées indices avec les tableaux). Elles sont utilisées dans le cas où l’univers U des clés est trop grand
pour être représenté intégralement, par exemple dans le cas où les clés sont des mots ou des numéros de
sécurité sociale.

Alvéoles
Une table de hachage est constituée d’un ensemble de m alvéoles. L’idée de base est d’utiliser une
fonction h : U → {1, . . . , m − 1} de la clé pour déterminer l’objet à trouver (ou plutôt l’alvéole dans laquelle
se trouve l’objet à trouver) — et non la clé elle même, comme c’est le cas pour les tableaux.

Résolution des collisions


La fonction h étant surjective, il en résulte que plusieurs objets ayant des clés différentes peuvent être
placés dans une même alvéole. Une solution couramment employée est de chaîner les objets d’une même
alvéole au moyen d’une liste chaînée.

Importance de la fonction de hachage


La fonction de hachage h détermine donc la répartition des objets dans les alvéoles. Comme le temps
d’accès moyen aux objets d’une même alvéole dépend du nombre d’objets dans cette alvéole, une bonne
fonction de hachage sera une fonction qui évite un déséquilibre au niveau du remplissage des alvéoles.

4.3.2 Définition en C
Une définition possible (clés de type int, valeurs de type int) est la suivante :

1 /* Liste de chainage */

2 typedef struct s_maillon *p_maillon_t;

3 typedef struct s_maillon

4 {

5 int cle;

6 int valeur;

7 p_maillon_t suivant;

8 } maillon_t;

10 /* Table */

28
11 typedef struct s_table *p_table_t;

12 typedef struct s_table

13 {

14 int n;

15 p_maillon_t *alveoles;

16 } table_t;

Une définition plus complexe (clés de type char, valeurs de type char) :

1 typedef struct s_maillon *p_maillon_t;

2 typedef struct s_maillon

3 {

4 char * cle;

5 void * valeur;

6 p_maillon_t suivant;

7 } maillon_t;

9 typedef p_maillon_t liste_t;

10 typedef liste_t *p_liste_t;


11

12 typedef struct s_table *p_table_t;

13 typedef struct s_table

14 {

15 int m;

16 listes *alveoles;

17 } table_t;

4.3.3 Mise en œuvre


Prenons comme exemple la table utilisant de clés sous forme de chaînes de caractères.

Fonction de hachage
Une fonction de hachage rudimentaire est la suivante.

1 int h(int m, char *cle)

2 {

3 int x = 0;

5 while (*cle)

6 {

7 x += *cle;

8 cle++;

9 }

10

11 return cle % m;

12 }

29
Initialisation

1 p_table_t init(int m)

2 {

3 p_table_t t = NULL;

5 t = malloc(sizeof(table_t));

6 t->m = m;

7 t->alveoles = calloc(m, sizeof(liste_t));

9 return t;

10 }

Ajout

1 void ajout(p_table_t t, char *cle, void *objet)

2 {

3 int a = 0;

4 p_maillon_t m = NULL;

6 a = h(t->m, cle);

7 m = t->alveoles[a];

9 while (m)

10 {

11 if (strcmp(m->cle, cle) == 0)

12 break;

13

14 m = m->suivant;

15 }

16

17 if (!m)

18 {

19 m = malloc(sizeof(maillon_t));

20 m->cle = malloc(1 + strlen(cle));

21 strcpy(m->cle, cle);

22 m->suivant = t->alveoles[a];

23 t->alveoles[a] = m;

24 }

25

26 m->valeur = objet;

27 }

30
Recherche
1 void *recherche(p_table_t t, char *cle)

2 {

3 int a = 0;

4 p_maillon_t m = NULL;

6 a = h(t->m, cle);

7 m = t->alveoles[a];

9 while (m)

10 {

11 if (strcmp(m->cle, cle) == 0)

12 return m->valeur;

13

14 m = m->suivant;

15 }

16

17 return NULL;

18 }

-=-=-=-

31

Vous aimerez peut-être aussi