1. CHAPITRE 7 : LES LISTES CHAINÉES
1. LES LISTES CHAINÉES SIMPLES
2. LES LISTES CIRCULAIRES
3. LES LISTES DOUBLEMENT CHAINÉES
2023-2024
C
1
2. 2023-2024
C Tableau unidimentionnel Introduction
2
Les listes chainées simples
Introduction
Les tableaux:
Stocker un ensemble de données de même type d’une façon linéaire
Taille connue et fixe des éléments
Insertion et suppression non pratiques qui nécessitent un décalage souvent coûteuses
Allocation d’un seul bloc en mémoire avec des cases contiguës
Les listes
Taille inconnue au départ, elle varie au cours du temps
Insertion et suppression dynamiques pratiques
Allocation indépendante de chaque élément, sous la forme d'un maillon (cellule).
Les éléments sont habituellement éparpillés en mémoire.
3. 2023-2024
C Tableau unidimentionnel Introduction
3
Les listes chainées simples
Structures de données récursives
Rappel sur la récursivité
Une fonction récursive est une fonction qui appelle lui-même.
Exemple : définition récursive de la factorielle
U0 =1
Un =n * Un-1
Mise en œuvre
4. 2023-2024
C Tableau unidimentionnel Introduction
4
Les listes chainées simples
Structures de données récursives
Structure de données récursive
Une structure de données qui se référence elle-même.
Exemple : définition récursive d’une liste
Une liste est :
Soit vide
Soit une valeur suivie d’une autre liste
Elle peut s’écrire :
Liste ::={} | {valeur, Liste}
Cette définition s’appuie donc récursivement sur elle-même
Illustration
5. 2023-2024
C Tableau unidimentionnel Introduction
5
Les listes chainées simples
Introduction
C’est une façon d'organiser les données en mémoire de manière beaucoup plus
flexible que les tableaux.
À créer nous-mêmes, comme le langage C ne propose pas ce système de
stockage
Une liste chaînée est un moyen d'organiser une série de données en mémoire.
Cela consiste à assembler des structures en les liant entre elles à l'aide de
pointeurs.
On pourrait les représenter comme ceci :
6. 2023-2024
C Tableau unidimentionnel Introduction
6
Les listes chainées simples
Principe
Chaque élément peut contenir ce que l'on veut : un ou plusieurs int, double,
structures,…
chaque élément possède un pointeur vers l'élément suivant.
7. 2023-2024
C Tableau unidimentionnel Introduction
7
Les listes chainées simples
Principe
Contrairement aux tableaux, les éléments d'une liste chaînée ne
sont pas placés côte à côte dans la mémoire.
Chaque case pointe vers une autre case en mémoire qui n'est pas
nécessairement stockée juste à côté.
8. 2023-2024
C Tableau unidimentionnel Introduction
8
Les listes chainées simples
Principe
Voilà deux schémas pour expliquer comment se passent l'ajout et la suppression
d'un élément d'une liste chaînée.
Remarquez : le symbole en bout de chaîne qui signifie que l'adresse de l'élément
suivant ne pointe sur rien, c'est-à-dire sur NULL.
9. 2023-2024
C Tableau unidimentionnel Introduction
9
Les listes chainées simples
Implémentation
Objectif : Créer une structure qui fonctionne sur le principe que nous venons de
découvrir.
typedef struct Element Element;
struct Element
{
int nombre;
Element* suivant;
};
Un pointeur vers un élément du même type appelé suivant. C'est ce qui permet de lier les
éléments les uns aux autres : chaque élément « sait » où se trouve l'élément suivant en
mémoire.
10. 2023-2024
C Tableau unidimentionnel Introduction
10
Les listes chainées simples
Implémentation
En plus de la structure qu'on vient de créer (que l'on dupliquera autant de fois
qu'il y a d'éléments), nous allons avoir besoin d'une autre structure pour
contrôler l'ensemble de la liste chaînée.
Nous créons des variables de type Liste autant qu’on veut de listes chaînées.
Le pointeur premier de chaque Liste pointe toujours sur le 1er
élément de la liste
Notre nouvelle structure de contrôle s’appelle Liste et contient seulement un
pointeur qui pointe toujours sur le premier élément de cette liste
typedef struct Liste Liste;
struct Liste
{
Element* premier;
};
11. 2023-2024
C Tableau unidimentionnel Introduction
11
Les listes chainées simples
Implémentation
En effet, il faut conserver l'adresse du premier élément pour savoir où
commence la liste.
Si on connaît le premier élément, on peut retrouver tous les autres en « sautant »
d'élément en élément à l'aide des pointeurs « suivant ».
typedef struct Liste Liste;
struct Liste
{ Element* premier;
};
12. 2023-2024
C Tableau unidimentionnel Introduction
12
Les listes chainées simples
Implémentation
Dernier élément de la liste : Comment le reconnaitre?
Il suffit de faire pointer le dernier élément de la liste vers NULL, c'est-à-dire de
mettre son pointeur suivant à NULL. Cela nous permet de réaliser un schéma
enfin complet de notre structure de liste chaînée
List
e
Element 1 Element
2
Element
3
Tête Queue
13. 2023-2024
C Tableau unidimentionnel Introduction
13
Les listes chainées simples
Opérations sur listes chainées
Parmi les fonctions usuelles pour les listes chaînées :
initialiser la liste ;
ajouter un élément ;
supprimer un élément ;
afficher le contenu de la liste ;
supprimer la liste entière.
Vérifier si la liste est vide
On pourrait créer d'autres fonctions (par exemple pour calculer la taille de la liste)
mais elles sont moins indispensables.
14. 2023-2024
C Tableau unidimentionnel Introduction
14
Les listes chainées simples
Opérations sur listes chainées : Initialisation
La fonction d'initialisation est la toute première que l'on doit appeler pour créer
la structure de contrôle Liste
Liste * initialiser ()
{
//allocation de l’espace nécessaire pour un pointeur sur Liste
Liste * p=(Liste *)malloc(sizeof(Liste *));
//Si l’allocation n’est pas réussie
if (p == NULL) exit(EXIT_FAILURE);
//Pointer la tête de liste sur NULL
p-> premier = NULL;
return p;
}
15. 2023-2024
C Tableau unidimentionnel Introduction
15
Les listes chainées simples
Opérations sur listes chainées : Initialisation
La fonction d'initialisation est la toute première que l'on doit appeler pour créer
la structure de contrôle Liste
Liste * initialiser ()
{
//allocation de l’espace nécessaire pour un pointeur sur Liste
Liste * p=(Liste *)malloc(sizeof(Liste *));
//Si l’allocation n’est pas réussie
if (p== NULL) exit(EXIT_FAILURE);
//Pointer la tête de liste sur NULL
p -> premier = NULL;
return p;
}
le type de données est Liste
et p pointe sur la liste créée.
La taille à allouer est
calculée
automatiquement avec
sizeof(*Liste)
En cas d'erreur d’allocation,
on arrête immédiatement
le programme en faisant
appel à exit().
16. 2023-2024
C Tableau unidimentionnel Introduction
16
Les listes chainées simples
Opérations sur listes chainées : Initialisation
Nous avons donc réussi à créer en mémoire une liste dont le pointeur sur la tête
de liste est NULL (liste vide). Ça ressemble à la figure suivante :
Liste * initialiser ()
{
//allocation de l’espace nécessaire pour un
pointeur sur Liste
Liste * p=(Liste *)malloc(sizeof(Liste *));
//Si l’allocation n’est pas réussie
if (p == NULL) exit(EXIT_FAILURE);
//Pointer la tête de liste sur NULL
p -> premier = NULL;
return p;
}
17. 2023-2024
C Tableau unidimentionnel Introduction
17
Les listes chainées simples
Opérations sur listes chainées : Insertion en tête
On va étudier l’ajout au début, au milieu et à la fin d’une liste dans ce cours
Pour nous mettre en situation, imaginons un cas semblable à la fig. suivante : la
liste est composée de trois éléments et on souhaite ajouter un nouveau au
début.
18. 2023-2024
C Tableau unidimentionnel Introduction
18
Les listes chainées simples
Opérations sur listes chainées : Insertion en tête
Dans un premier temps, on alloue l'espace nécessaire au stockage du nouvel
élément et on y place le nouveau nombre nvNombre. Il reste alors une étape
délicate : l'insertion du nouvel élément dans la liste chaînée.
Pour mettre à jour correctement les pointeurs, nous devons procéder dans cet
ordre précis :
1. faire pointer notre nouvel élément vers son futur successeur, qui est l'actuel
premier élément de la liste ;
2. faire pointer le pointeur premier vers notre nouvel élément.
On ne peut pas suivre ces étapes dans l'ordre inverse ! En effet, si vous faites
d'abord pointer premier vers notre nouvel élément, vous perdez l'adresse du
premier élément de la liste ! Faites le test, vous comprendrez de suite pourquoi
l'inverse est impossible.
19. 2023-2024
C Tableau unidimentionnel Introduction
19
Les listes chainées simples
Opérations sur listes chainées : Insertion en tête
Il va falloir adapter le pointeur premier de la liste ainsi que le pointeur suivant de
notre nouvel élément pour « insérer » correctement celui-ci dans la liste.
void insertion_debut(Liste * p, int nvNombre)
{
/* Création du nouvel élément */
Element *nouveau = (Element*)malloc(sizeof(Element*));
if (p == NULL || nouveau == NULL)
{
exit(EXIT_FAILURE);
}
nouveau -> nombre = nvNombre;
/* Insertion de l'élément au début de la liste */
nouveau -> suivant = p -> premier;
/* MAJ de la tête de liste (premier) */
p -> premier = nouveau;
}
20. 2023-2024
C Tableau unidimentionnel Introduction
20
Les listes chainées simples
Opérations sur listes chainées : Insertion à la fin
Il va falloir parcourir la liste pour arriver à la fin et changer le chaînage
void insertion_fin(Liste * p, int nvNombre)
{
/* Création du nouvel élément */
Element* nouveau = (Element*) malloc(sizeof(Element*));
if (p == NULL || nouveau == NULL) exit(EXIT_FAILURE);
nouveau -> nombre = nvNombre;
nouveau -> suivant = NULL;
/*arriver jusqu’à la fin*/
Element* der = p -> premier;
while(der -> suivant != NULL) der = der -> suivant;
/* Insertion de l'élément à la fin de la liste */
der -> suivant = nouveau;
}
21. 2023-2024
C Tableau unidimentionnel Introduction
21
Les listes chainées simples
Opérations sur listes chainées : Insertion à une position k
Il va falloir parcourir la liste pour arriver à la position k tant que k<longueur (liste)
On va écrire d’abord la fonction qui calcule la longueur de la liste
int longueur(Liste * p)
{
/* Création du nouvel élément */
Element* courant = p -> premier;
if (p == NULL) exit(EXIT_FAILURE);
int compteur = 0;
while (courant != NULL)
{ courant = courant -> suivant;
compteur++;
}
return compteur;
}
Pourquoi ne pas
utiliser p pour le
parcours !!
22. 2023-2024
C Tableau unidimentionnel Introduction
22
Les listes chainées simples
Opérations sur listes chainées : Insertion à une position k
Si k=1, il s’agit d’une insertion au début
Sinon si k=longueur(liste)+1 alors il s’agit d’une insertion à la fin
Sinon :
Si k<1 ou k>longueur(liste)+1 alors k est invalide
Sinon
On alloue de l’espace pour le nouvel élément
Si c’est bon, on crée le nouvel élément en affectant sa valeur
On fait le parcours de la liste jusqu’à l’élément à la position k
On doit garder aussi la position de l’élément précédent de l’élément k car on
doit changer son suivant sur le nouvel élément.
Le suivant du nouveau doit pointer sur l’élément à la position k actuel pour
prendre sa place
23. 2023-2024
C Tableau unidimentionnel Introduction
23
Les listes chainées simples
Opérations sur listes chainées : Insertion à une position k
void insertion_milieu(Liste * p, int nvNombre, int k)
{
Element *p2=p ->premier, *prec;
if (p == NULL ) exit(EXIT_FAILURE);
if ((k > (longueur(p)+1)) || (k <= 0))
printf("nLa position est invaliden");
else{
if (k ==1) insertion_debut(p, nvNombre);
else if (k == longueur (p)+1) insertion_fin(p, nvNombre);
else {
/* Création du nouvel élément */
Element* p1 = malloc(sizeof(Element*));
if(p1) {
p1->nombre = nvNombre;
for (int i=1; i<k; i++)
{ prec = p2;
p2 = p2->suivant; }
/*Réaliser l'insertion*/
prec->suivant = p1;
p1->suivant = p2; } }
} }
24. 2023-2024
C Tableau unidimentionnel Introduction
24
Les listes chainées simples
Opérations sur listes chainées : Suppression du début
Il est techniquement possible de supprimer un élément précis au milieu de la
liste.
Ici, nous allons étudier la suppression du premier élément de la liste
void suppression_debut(Liste * p)
{
if (p == NULL) exit(EXIT_FAILURE);
if (p->premier != NULL)
{
Element *aSupprimer = p->premier;
p ->premier = p ->premier ->suivant;
free(aSupprimer);
}
}
Cette fonction est courte mais il faut bien comprendre qu'on
doit faire les choses dans un ordre précis :
1. faire pointer premier vers le second élément ;
2. supprimer le premier élément avec un free.
Si on faisait l'inverse, on perdrait l'adresse du second
élément !
25. 2023-2024
C Tableau unidimentionnel Introduction
25
Les listes chainées simples
Opérations sur listes chainées : Suppression du début
On commence par vérifier que le pointeur qu'on nous envoie n'est
pas NULL, sinon on ne peut pas travailler. On vérifie ensuite qu'il y a
au moins un élément dans la liste, sinon il n'y a rien à faire.
Ces vérifications effectuées, on peut sauvegarder l'adresse de
l'élément à supprimer dans un pointeur aSupprimer.
On adapte ensuite le pointeur premier vers le nouveau premier
élément, qui est actuellement en seconde position de la liste
chaînée.
Il ne reste plus qu'à supprimer l'élément correspondant à notre
pointeur aSupprimer avec un free.
26. 2023-2024
C Tableau unidimentionnel Introduction
26
Les listes chainées simples
Opérations sur listes chainées : Suppression de la fin
si la liste n’est pas vide, alors il faut parcourir la liste jusqu’à le dernier élément
Il faut aussi maintenir l’avant dernier élément car son suivant va pointer sur
NULL
A la fin, on doit libérer l’espace alloué par l’ancien dernier élément
27. 2023-2024
C Tableau unidimentionnel Introduction
27
Les listes chainées simples
Opérations sur listes chainées : Suppression de la fin
void suppression_fin(Liste* p)
{
Element* courant =p ->premier;
Element* AvDer;
if (p == NULL ) exit(EXIT_FAILURE);
/*arriver jusqu’à la fin*/
while(courant -> suivant != NULL) {
AvDer = courant;
courant = courant -> suivant;
}
/* Suppression de l'élément de la fin de la liste */
AvDer -> suivant = NULL;
free(courant);
}
courant
28. 2023-2024
C Tableau unidimentionnel Introduction
28
Les listes chainées simples
Opérations sur listes chainées : Affichage
Partir du premier élément et afficher chaque élément un à un en
«sautant » de bloc en bloc.
void afficher(Liste * p)
{
if (p == NULL) exit(EXIT_FAILURE);
Element *courant = p ->premier;
while (courant)
{
printf("%d -> ", courant ->nombre);
courant = courant >suivant;
}
printf("NULLn");
}
29. 2023-2024
C Tableau unidimentionnel Introduction
29
Les listes chainées simples
Opérations sur listes chainées : Vide/Calcul nbr éléments
Vérifier si la tête pointe sur NULL sinon calculer le nombre
d’éléments en utilisant un pointeur sur Element pour le parcours de
la liste
void vide(Liste * p)
{
int vide =0;
if(longueur(liste)==0)
vide =1;
if (!vide) printf("Liste non vide et contient %d elements",n);
else
printf("Liste vide");
}
30. 2023-2024
C Tableau unidimentionnel Introduction
30
Les listes chainées simples
Programme principal
On peut tester ces
opérations dans
une fonction main:
int main()
{
Liste * maListe = initialiser();
Liste * l=initialiser();
insertion_debut(l,5);
afficher(l);
insertion_debut(l,12);
afficher(l);
insertion_fin(l,20);
afficher(l);
insertion_milieu(l,13,2);
afficher(l);
suppression_debut(l);
afficher(l);
suppression_fin(l);
afficher(l);
vide(l);
return 0;
}
31. En résumé
› Les listes chaînées constituent un nouveau moyen de stocker des
données en mémoire. Elles sont plus flexibles que les tableaux car on
peut ajouter et supprimer des « cases » à n'importe quel moment.
› Il n'existe pas en langage C de système de gestion de listes chaînées, il
faut l'écrire nous-mêmes
› Dans une liste chaînée, chaque élément est une structure qui contient
l'adresse de l'élément suivant.
› Il est conseillé de créer une structure de contrôle (du type Liste dans
notre cas) qui retient l'adresse du premier élément.
› Il existe une version améliorée, mais plus complexe, des listes
chaînées appelée « listes doublement chaînées », dans lesquelles
chaque élément possède en plus l'adresse de celui qui le précède.
C
31
32. CHAPITRE 7 : LES LISTES CHAINÉES
1. LES LISTES CHAINÉES SIMPLES
2. LES LISTES CIRCULAIRES
3. LES LISTES DOUBLEMENT CHAINÉES
2023-2024
C
32
33. 2023-2024
C Tableau unidimentionnel Introduction
33
Les listes simplement chainées circulaires
Définition
Une liste, où le pointeur NULL du dernier élément est remplacé par
l’adresse du premier élément, est appelée une liste circulaire.
Une liste circulaire n’a pas de premier et de dernier élément
Tous les éléments sont accessibles à partir de n’importe quel autre élément
Si la liste circulaire contient un seul maillon alors son adresse sera la même de
celle de son suivant
34. CHAPITRE 7 : LES LISTES CHAINÉES
1. LES LISTES CHAINÉES SIMPLES
2. LES LISTES CIRCULAIRES
3. LES LISTES DOUBLEMENT CHAINÉES
2023-2024
C
34
Notes de l'éditeur
#1:Le pointeur NUL
Seule exception: La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part'.
int *P; P = 0;
#9:1. Si on veut travailler de manière générique, l'idéal est de faire un pointeur sur void : void*. Cela permet de faire pointer vers n'importe quel type de données.
#32:Le pointeur NUL
Seule exception: La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part'.
int *P; P = 0;
#34:Le pointeur NUL
Seule exception: La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part'.
int *P; P = 0;