Vous êtes sur la page 1sur 13

Cours : Algorithmique et programmation II Année universitaire : 2019-2020

Classe : TI1x

Chapitre 2 : Les listes chaînées


Introduction

En informatique, une structure de données est une structure logique destinée à contenir des données, afin
de leur donner une organisation permettant leur traitement. On appelle les différentes utilisations possibles
de la structure de données des opérations. Une opération courante est la lecture : récupérer un élément
stocké dans la structure. L’insertion, la suppression, la recherche, etc. sont aussi des opérations de
manipulation des structures de données.
Lorsqu’on crée un algorithme utilisant des conteneurs, il existe différentes manières de les implémenter
(tableaux, listes, piles, files, etc.), la façon la plus courante étant les tableaux.
Lorsqu’on crée un tableau, les éléments de celui-ci sont placés de façon contiguë en mémoire. Pour
pouvoir le créer, il faut connaître sa taille. Si on veut supprimer un élément au milieu du tableau, il faut
recopier les éléments temporairement, réallouer de la mémoire pour le tableau, puis le remplir à partir de
l'élément supprimé. Ce sont beaucoup de manipulations et qui sont coûteuses en ressources.
Une liste chaînée est différente puisque les éléments de la liste sont répartis dans la mémoire et reliés
entre eux par des pointeurs. On peut ajouter et enlever des éléments d'une liste chaînée à n'importe quel
endroit, à n'importe quel instant, sans devoir recréer la liste entière.

I. Les listes simplement chaînées

1.1 Définition
Une liste simplement chaînée est un ensemble d’objets dynamiques liés entre eux récursivement par des
pointeurs, définie par l’adresse de son premier objet. Chaque objet contient l’adresse de son suivant.
On utilise les listes simplement chaînées pour construire une structure de données dont la taille est
inconnue à l’avance.

1.2 Caractéristiques
Une liste simplement chaînée est caractérisée par :
 Un élément tête qui désigne le premier élément de la liste
 Tout élément de la liste est composé de deux parties :
o La première partie contient un ou plusieurs champs qui forment les informations des
éléments à traiter

1
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

o La deuxième partie contient un champ permettant de faire le lien avec l’élément suivant
dans la liste. Ce champ est un pointeur.
 Le pointeur du dernier élément de la liste doit contenir la valeur NULL qui signifie la fin de la
liste.

1.3 Représentation schématique

1.4 Déclaration
typedef struct cellule {
int information ;
struct cellule * suivant ;
} cellule ;

typedef cellule* liste;


liste nomListe ;

 On crée le type cellule qui est une structure contenant un champ (information) de type entier (dans
cet exemple) et un pointeur sur cellule (suivant), qui contiendra l'adresse de la cellule suivante.
 On crée le type liste qui est un pointeur sur le type cellule.
 On déclare une variable de type liste ;

1.5 Création d’une liste


La création d’une liste consiste à l’initialisation d’une variable de type Liste à la valeur NULL ;

nomListe=NULL ;

ou avec une fonction :


void creerListe(liste *L){
*L=NULL ;
}

Exemple

int main(){
liste ma_liste1 = NULL;
cellule *ma_liste2 = NULL;
liste ma_liste3 ;
creerListe(&ma_liste3) ;
return 0;
}

2
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

1.6 Vérification si une liste est vide


int estVide(liste l)
{
return l == NULL ;
}

1.7 Ajout dans une liste chaînée


Pour ajouter un élément dans une liste, il faut d’abord allouer la zone mémoire nécessaire. Cet ajout
peut être effectué en tête de la liste, en queue ou à une position quelconque selon le traitement demandé.

1.7.1 Ajout en tête


Lors d'un ajout en tête, nous allons créer un élément, lui assigner la valeur que l'on veut ajouter, puis
pour terminer, raccorder cet élément à la liste passée en paramètre. Lors d'un ajout en tête, on devra donc
assigner à suivant l'adresse du premier élément de la liste passée en paramètre. Visualisons tout ceci sur un
schéma :

void ajouterEnTete(liste *L, int x)


{
/* On crée une nouvelle cellule */
cellule* nv = malloc(sizeof(cellule));
/* On assigne la valeur e au champ information de la nouvelle
cellule*/
nv->information =x;
/* On assigne l'adresse de la cellule suivante (qui est ici *liste
au nouvel élément */
nv->suivant = *L;
/* La nouvelle cellule devient le début de la liste*/
*L=nv;
}

1.7.2 Ajout en fin de liste


Il faut tout d'abord créer une nouvelle cellule, lui assigner sa valeur, et mettre l'adresse de l'élément
suivant à NUL. Ensuite, il faut faire pointer la dernière cellule de la liste originale sur la nouvelle
cellule créée.

3
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

void ajouterEnFin(liste *L, int x)


{
/* On crée une nouvelle cellule */
cellule* nv = malloc(sizeof(cellule));
/* On assigne la valeur au nouvel élément */
nv->information = x;
/* On ajoute en fin, donc aucun élément ne va suivre */
nv->suivant = NULL;
if(*L== NULL)
{
/* Si la liste est vide, nv est le début de la liste créé */
*L=nv;
}
else

{ /* Sinon, on parcourt la liste à l'aide d'un pointeur


temporaire et on indique que le dernier élément de la liste
est relié au nouvel élément */
cellule* p=*L;
while(p->suivant!= NULL)
{
p = p->suivant;
}
p->suivant = nv;
}
}

Application 1 :
Créer une liste contenant les entiers 5, 7 et 2 successivement.
int main(){
liste L1 ;
//création d’une liste chaînée vide
creerListe(&L1) ;
ajouterEnTete(&L1,5) ;
ajouterEnFin(&L1,7) ;
ajouterEnFin(&L1,2) ;
}

4
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

1.7.3 Ajout une cellule après une position pos de la liste


On veut ajouter une cellule juste après une cellule de position k.
La figure suivante représente l’ajout d’une cellule contenant l’information « 9 » après la position « 2 ».

void ajouterApresPos(liste *L, int pos,int x)


{
if(pos<1 || pos>longueur(*L)){
printf("\nErreur: Position d'insertion incorrecte\n")
return ;
}
if(pos==longueur(*L)){
ajouterEnFin(L,x) ;
return ;
}
cellule*nv, *p;
p=*L;
int i;
for(i=1;i<pos;i++){
p=p->suivant;
}
nv = malloc(sizeof(cellule));
nv->information=x;
nv->suivant=p->suivant;
p->suivant=nv;
}

1.8 Affichage des éléments d’une liste


Fonction itérative :
void afficherListe(liste L)
{
Cellule *p = L;
/* Tant que l'on n'est pas au bout de la liste */
while(p != NULL)
{
/* On affiche le champ information de la cellule*/
printf("%d\t", p->information);
/* On avance d'une case */
p = p->suivant;
} }
5
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

Fonction récursive :
void afficherListeRec(liste L)
{
if(L !=NULL)
{
/* On affiche le champ information du premier de la liste*/
printf("%d\t", L->information);
/* On appelle la fonction pour afficher le reste de la liste */
afficherListeRec(L->suivant);
}
}

1.9 Déterminer la longueur d’une liste


Fonction itérative :
int longueur(liste L){
int n=0 ;
cellule*p=L;
//Parcours de la liste jusqu'à sa fin
while(p!=NULL){
n++;
p=p->suivant;
}}
Fonction récursive :
int longueurRec(liste L){
if(L==NULL)
return 0 ;
}
return 1+ longueurRec(L->suivant) ;
}

1.10 Suppression dans une liste chaînée


Pour cette action, il faut faire attention de ne libérer les pointeurs qu’après avoir fait les chaînages
nécessaires.

1.10.1 Suppression en tête de liste


 On utilise un pointeur intermédiaire dans lequel on garde l’adresse de la première cellule.
 On fait passer le pointeur début de la liste vers la cellule suivante.
 On libère le pointeur intermédiaire.

void supprimerEnTete(liste *L){


cellule *p;
//Vérifier d’abord que la liste n’est pas vide
if( !estVide(*L)){
p= *L;
*L = p->suivant;
free(p); } }

6
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

1.10.2 Suppression en fin de liste


On doit parcourir la liste en utilisant deux pointeurs intermédiaires pour repérer le dernier élément et son
précédent.
void supprimerEnFin(liste *L)
{
cellule *p1, *p2 ;
p1=*L ;
/* Si la liste contient un seul élément */

if(p1->suivant == NULL)
{
/* On le libère et on la liste prend NULL (la liste est maintenant
vide) */
free(p1) ;
*L=NULL ;
return ;
}
/* Si la liste contient au moins deux éléments */
p2=*L ;
/* Tant qu'on n'est pas au dernier élément */
while(p1->suivant != NULL)
{ //on avance les pointeur vers les cases suivantes
p2=p1 ;
p1=p1->suivant ;
}
/* A la sortie de la boucle, p1 pointe sur le dernier élément,
et p2 sur l'avant-dernier. On indique que l'avant-dernier devient
la fin de la liste et on supprime le dernier élément */
p2->suivant = NULL;
free(p1);
}

1.10.3 Suppression d’un élément donné

void supprimer_element(liste *L,int x)


{
cellule *p1, *p2 ;
if(*L->information==x)
{
supprimerEnTete(L) ;
return ;
}
p1=*L;
7
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

p2=*L;
//parcourir jusqu’à trouver x ou fin de la liste
while(p1 !=NULL &&p1->information !=x)
{
p2=p1 ;
p1=p1->suivant;
}
if(p1!=NULL)
{/*il existe une cellule qui contient comme information la valeur
de x cad p1->information = =x*/
p2->suivant=p1->suivant;
free(p1);
}

1.11 Recherche d’un élément dans une liste


On veut renvoyer l'adresse du premier élément trouvé ayant une certaine valeur. Si aucun élément n'est
trouvé, on renverra NULL.
Liste rechercherElement(liste L, int x)
{
cellule *p=L;
/* Tant que l'on n'est pas au bout de la liste */
while(p!= NULL)
{
if(p->information == x)
{//on a trouvé l’élément recherché
return p ;
}
//on avance vers le prochain
p=p->suivant;
}
//on n’a pas trouvé l’élément recherché
return NULL;
}

Application 2
Ecrire une fonction qui retourne le nombre d'occurrences d'une valeur x dans une liste chaînée.

int nombreOccurences(liste L, int x)


{
int nb = 0;
if(L == NULL)
return 0;
while((L = rechercherElement(L, x)) != NULL)
{
/* On incrémente */
L = L->suivant;

8
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

nb++;
}
return nb;
}

II. Les listes doublement chaînées

2.1 Définition
Il arrive parfois qu’on ait besoin de parcourir une liste à l’envers, ou bien de donner les précédents et
suivant d’un même élément d’une liste. Pour ce faire, on peut ajouter dans chaque élément de la liste un
deuxième pointeur qui contient l’adresse de son précédent. Ainsi, le précédent du premier élément
contient la valeur NULL.

2.2 Représentation schématique

2.3 Déclaration
typedef struct cellule {
int information ;
struct cellule * precedent ;
struct cellule * suivant ;
} cellule;

typedef struct{
cellule *premier;//pointeur sur le début de la liste
cellule *dernier;//pointeur sur la fin de la liste
} listeD;

2.4 Création d’une liste


void creerListe(listeD *L){
L->premier=NULL ;
L->dernier=NULL ;
}

9
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

2.5 Vérification si une liste est vide


int estVide(listeD L)
{
return L.premier== NULL ;
}

2.6 Ajout en tête


void ajouterEnTete(listeD *L ,int x){
cellule *nv=malloc(sizeof(cellule));
nv->information=x;
nv->precedent=NULL;
if(estVide(*L)){
/*liste vide donc premier sera égal à dernier*/
nv->suivant=NULL;
L->premier=nv;
L->dernier=nv ;
}
else{
nv->suivant=L->premier;
L->premier->precedent=nv;
L->premier=nv;
}
}

2.7 Ajout en fin


void ajouterEnFin(listeD *L ,int x){
if(estVide(*L))
ajouterEnTete(L,x) ;
else{
cellule *nv=malloc(sizeof(cellule));
nv->information=x;
nv->suivant=NULL;
L->dernier->suivant=nv ;
nv->precedent=L->dernier ;
L->dernier=nv ;
}
}

10
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

2.8 Ajout après une cellule de la liste

void ajouterApres(listeD *L, cellule *p,int x)


{
if (p==L->dernier){
ajouterEnQueue(L,x);
return ;
}
cellule* nv = malloc(sizeof(cellule));
nv->information=x;
nv->suivant=p->suivant;
nv->precedent=p ;
p->suivant->precedent=nv ;
p->suivant=nv;
}

2.9 Suppression en tête


void supprimerEnTete(listeD *L){
cellule *p;
if( !estVide(*L)){
p= L->premier;
L->premier = p->suivant;
if(L->premier !=NULL)
L->premier->precedent=NULL ;
else
L->dernier=NULL;
free(p);
}
}

2.10 Suppression en fin


void supprimerEnFin(listeD *L)
{
if( !estVide(*L)){
cellule *p=L->dernier ;
cellule*q=L->dernier->precedent ;

if(q!=NULL){
q->suivant =NULL;
11
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

L->dernier=q ;
}
else
{
L->premier=NULL ;
L->dernier=NULL ;
}
free(p);
}
}

2.10 Suppression d’un élément donné

void supprimer_element(listeD *L,int x)


{
cellule *p;
p=L->premier ;
while(p !=NULL &&p->information !=x)
{
p=p->suivant;
}
if(p!=NULL)
{ if(p==L->premier)
supprimerEnTete(L);
else if(p==L->dernier)
supprimerEnFin(L);
else{
p->suivant->precedent=p->precedent ;
p->precedent->suivant=p->suivant;
free(p);
}
}
}

12
Cours : Algorithmique et programmation II Année universitaire : 2019-2020
Classe : TI1x

III Les listes circulaires

2.4 Définition
Une liste circulaire est une sorte de liste simplement chainée ou doublement chaînée qui n’a pas de fin
càd le pointeur suivant du dernier élément de la liste pointera sur le premier élément de la liste au lieu de
la valeur NULL.

2.5 Représentation schématique

2.6 Déclaration
La syntaxe de déclaration sera la même pour une liste simplement chainée circulaire ou non et pour une
liste doublement chainée circulaire ou non.
La seule différence réside dans les opérations de manipulation de listes. En fait, il faut remplacer toute
instruction mettant la valeur NULL dans le champ suivant du dernier élément par affectation de l’adresse
du premier élément au champ suivant du dernier élément.

13

Vous aimerez peut-être aussi