CH 2 Suite 1 - Les Listes Doublement Chainées
CH 2 Suite 1 - Les Listes Doublement Chainées
CH 2 Suite 1 - Les Listes Doublement Chainées
Universit Stif 1
Facult des Sciences
Dpartement dinformatique
Filire : Licence Acadmique
Module : Algorithmique et structure de donnes
Anne universitaire : 2016-2017
CH2 suite 1 : Les listes doublement chaines
Lorsque chaque lment d'une liste chaine pointe vers l'lment suivant, nous parlons de liste simplement
chaine. Lorsque chaque lment d'une liste pointe la fois vers l'lment suivant et prcdent, nous parlons
alors de liste doublement chaine ou liste symtrique. Retenez donc qu'une liste chaine nous permet de
stocker un nombre inconnu d'lments.
Voici une reprsentation schmatique des listes doublement chaines:
Vous pouvez donc voir sur ce schma que chaque lment d'une liste doublement chaine contient :
Une donne (ici un simple entier)
Un pointeur vers l'lment suivant (NULL si l'lment suivant n'existe pas)
Un pointeur vers l'lment prcdent (NULL si l'lment prcdent n'existe pas)
Passons maintenant la reprsentation de ces listes en langage C.
Cette premire structure va nous permettre de reprsenter un 'node' (lment) de notre liste chane. Nous
pouvons alors voir que chaque lment de notre liste contiendra un lment de type int. D'autre part :
p_next pointera vers l'lment suivant (ou NULL s'il s'agit du dernier lment de la liste)
p_prev pointera vers l'lment prcdent (ou NULL s'il s'agit du premier lment)
Les liens entre les diffrents lments de notre liste chane sont donc assurs par nos deux pointeurs p_next
et p_prev.
1
Attends attends, c'est quoi a size_t ? Et a veut dire quoi p_tail et p_head ?
Tout d'abord, nous utilisons une variable nomme length contenant la taille de notre liste chane (length =
taille en anglais). Grce cette variable, nous aurons accs au nombre d'lments de notre liste chane.
Cependant, cette variable peut paratre un peu particulire puisqu'elle est de type size_t. Ce fameux size_t
correspond un entier non sign c'est dire un entier positif (a tombe bien car notre liste chane ne pourra
pas contenir -1 lment). Ce type est de ce fait communment utilis pour tout ce qui concerne les tailles
(taille d'un tableau, ...).
Enfin, claircissons ce fameux p_tail et p_head, quoi vont-ils bien nous servir ? Ceci est tout simple.
p_head va pointer vers le premier lment de notre liste alors que p_tail va pointer vers le dernier lment.
Ainsi, nous garderons de manire permanente un pointeur vers le dbut et la fin de la liste.
Ok, mais quoi a va nous servir ?
Et bien cela va tout simplement servir faciliter les diffrentes oprations que nous effectuerons sur nos
listes. En effet, pour exemple, lorsque nous souhaiterons ajouter un lment en fin de liste, nous n'aurons pas
besoin de parcourir la liste dans sa totalit pour ajouter l'lment en fin car nous disposerons directement
d'une rfrence vers la fin de liste.
Enfin, afin de faciliter l'criture, nous crons un alias de notre structure grce l'oprateur typedef. Nous
appelons cet alias Dlist pour Double List. Pour utiliser une liste dans nos programmes nous utiliserons alors :
Code : C
1 Dlist *list = NULL; /* Dclaration d'une liste vide */
{
p_new->length = 0;
p_new->p_head = NULL;
p_new->p_tail = NULL;
}
return p_new;
Pour respecter une certaine convention, toutes nos fonctions seront de la forme dlist_.
Comment fonctionne cette fonction ? Je pense que vous l'aurez devin sans trop de problmes. Tout d'abord,
nous crons une variable p_new qui sera notre nouvelle liste. Nous utilisons alors malloc pour rserver de
l'espace mmoire pour cette liste.
La syntaxe suivante:
Code : C
1 int *p_data = malloc(sizeof *p_data);
Est identique :
Code : C
1 int *p_data = malloc(sizeof(int));
De cette manire, si l'on modifie notre type, on n'aura pas besoin de le modifier dans notre malloc.
Ensuite et de manire gnrale, il est ncessaire de vrifier si notre malloc n'a pas chou. En effet, si celuici renvoie NULL, et que nous essayons d'accder aux lments de notre structure Dlist, c'est le drame.
Enfin, nous mettons nos pointeurs p_head ainsi que p_tail NULL (vu que notre liste est vide), puis nous
initialisons la taille de notre liste 0 et nous retournons notre nouvelle liste.
Ajouter un lment
Aprs avoir allou une nouvelle liste chane, voyons maintenant comment ajouter un lment dans celle-ci.
Ajout en fin de liste
Grce la forme de notre structure, l'ajout en fin de liste va tre simplifi. En effet, rappelez-vous, nous
gardons toujours un pointeur vers la fin de notre liste, nous n'avons donc nul besoin de parcourir la liste en
entier afin d'arriver au dernier lment, nous l'avons dj. Voici comment va se passer l'ajout en fin de liste:
A partir de ce schma, essayons d'en dduire un algorithme. Tout d'abord, nous devons vrifier si notre liste
n'est pas NULL. Si elle ne l'est pas, nous allons crer un nouvel lment (nouveau node). Une fois celui-ci
cr, nous devons stoquer notre donne dans le champ donne (data) de notre structure puis faire pointer
p_next vers NULL car ce sera le dernier lment de notre liste. A partir de l, deux possibilits s'offrent
nous :
3
S'il n'existe pas de dernier lment (donc la liste est vide) Alors
o Nous faisons pointer p_prev vers NULL
o Nous faisons pointer la tte et la fin de liste vers notre nouvel lment
Sinon
o Nous rattachons le dernier lment de notre liste notre nouvel lment (dbut du chanage)
o Nous faisons pointer p_prev vers le dernier lment de notre liste
o Nous faisons pointer notre fin de liste vers notre nouvel lment (fin du chanage)
Enfin, nous incrmentons notre champ length de notre liste puis nous retournons la liste. Tout ceci constitue
alors l'algorithme d'ajout en fin de liste. Essayez tout d'abord de le coder par vous mme, cela sera bnfique
pour vous et vous aidera comprendre le concept. Si vous bloquez, munissez-vous dune feuille et dun
crayon et essayez de reprsenter toutes les tapes sur votre feuille. Ensuite, ressayez de coder l'algorithme.
Voici l'implmentation :
Code : C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Le code ci-dessus est entirement comment, il ne sera donc pas ncessaire d'ajouter de commentaires.
N'hsitez pas relire ce morceau de code. Si vous avez des difficults le comprendre, jouez le rle du
compilateur et imaginez vous le droulement de chaque instruction.
Ajout en dbut de liste
Pour ajouter un lment en dbut de liste, nous allons utiliser exactement le mme procd que pour l'ajout
en fin de liste. Et oui, grce nos pointeurs en dbut et en fin de liste, nous pouvons nous permettre de
reprendre nos implmentations. Si vous avez bien compris comment se passait l'ajout en fin de liste, vous
n'aurez aucun de mal raliser l'ajout en dbut de liste. L aussi, essayez d'abord par vous mme de
programmer cet algorithme.
4
Il y a comme des ressemblances entre les deux fonctions vous ne trouvez pas.
Insrer un lment
Nous disposons dsormais de fonctions permettant d'ajouter un lment en dbut ainsi qu'en fin de liste.
Mais si l'on dsire ajouter un lment nimporte o dans notre liste ? Et bien nous allons justement crer une
fonction pour ceci. Comment allons-nous procder ? Posons-nous et rflchissons cinq minutes. Tout
d'abord, nous aurons besoin de parcourir notre liste. Nous aurons aussi besoin d'un compteur (que l'on
nommera) i afin de nous arrter la position o nous souhaitons insrer notre nouvel lment. Jusqu'ici, rien
de bien sorcier. Il nous faut alors rflchir des diffrents cas de figure qui peuvent intervenir lorsque nous
aurons trouv notre position:
Soit nous sommes en fin de liste
Soit nous sommes en dbut de liste
Soit nous sommes en milieu de liste
Cependant, les deux premiers cas sont trs faciles trater. Enfin, nous disposons de fonctions permettant
d'ajouter un lment en dbut et en fin de liste, il nous suffit donc de les raliser. Le plus gros de notre travail
sera alors de grer le cas o nous nous trouvons en milieu de liste. Voici un petit schma permettant de
mieux cerner la situation:
Le chanage va tre lgrement plus compliqu. En effet, nous devrons tout d'abord relier nos lments
suivant et prcdent notre nouvel lment puis, inversement, nous devrons relier notre nouvel lment aux
lments suivant et prcdent. Le chanage va alors se drouler en 4 tapes. A noter qu'il sera ncessaire
d'avoir pralablement cr un nouvel lment sans quoi le chanage ne pourra pas avoir lieu.
Pour parcourir notre liste, nous rcuprerons le pointeur vers notre dbut de liste dans un pointeur
temporaire. C'est ce pointeur temporaire qui nous servira parcourir notre liste. Schmatiquement, notre liste
sera parcourue de gauche droite. Notre compteur sera bien videmment incrment lors du parcours de
chaque maillon de la liste.
Voici ce que cela donne:
Code : C
1 Dlist *dlist_insert(Dlist *p_list, int data, int position)
2{
3
if (p_list != NULL)
4
{
5
struct node *p_temp = p_list->p_head;
6
int i = 1;
7
while (p_temp != NULL && i <= position)
8
{
9
if (position == i)
10
{
11
if (p_temp->p_next == NULL)
12
{
13
p_list = dlist_append(p_list, data);
14
}
15
else if (p_temp->p_prev == NULL)
16
{
17
p_list = dlist_prepend(p_list, data);
18
}
19
else
20
{
21
struct node *p_new = malloc(sizeof *p_new);;
22
if (p_new != NULL)
23
{
24
p_new->data = data;
25
p_temp->p_next->p_prev = p_new;
26
p_temp->p_prev->p_next = p_new;
27
p_new->p_prev = p_temp->p_prev;
28
p_new->p_next = p_temp;
29
p_list->length++;
30
}
31
}
32
}
33
else
34
{
35
p_temp = p_temp->p_next;
36
}
i++;
}
}
return p_list;
Si vous avez compris les codes prcdemment tablis, je ne pense pas que vous aurez des difficults
comprendre celui-ci. Cependant, voici quelques explications supplmentaires: pour notre parcours de liste,
nous utilisons un pointeur nomm p_temp. Au tout dbut, celui-ci pointe vers le premier lment de notre
liste (p_list->p_head). Pour parcourir notre liste, nous utilisons une structure de type while. Tant que nous
n'avons pas atteint la fin de liste (p_temp != NULL) et tant que nous ne sommes pas la position o nous
voulons insrer notre lment (position <= i), nous bouclons. Ds lors que nous avons atteint notre position
(position == i), nous devons alors effectuer nos trois tests :
Si nous sommes en fin de liste (p_temp->p_next == NULL), nous utilisons notre fonction
dlist_append
Sinon, si nous sommes en dbut de liste (p_temp->p_prev == NULL), nous utilisons notre fonction
dlist_prepend
Sinon, nous devons crer un nouvel lment et raliser notre chanage sans oublier de stoquer la
donne dans notre champ data
Logiquement, grce au schma prcdent et aux explications fournies, vous ne devriez pas avoir de mal
comprendre le chanage.
Enfin, si nous n'avons pas encore atteint notre position, nous passons l'lment suivant (p_temp = p_temp>p_next).
Dans ce code, nous pouvons remarquer qu'une petite chose change par rapport nos codes prcdents. Dans
cette fonction, nous utilisons un double pointeur. En effet, notre fonction delete doit directement effectuer
les modifications sur notre liste. Autrement dit, celle-ci doit faire des modifications sur un objet de type Dlist
*. C'est pour cela que nous ne devons pas disposer d'un pointeur simple, mais bel et bien d'un pointeur
double.
En premier lieu, nous vrifions si la liste que nous avons rcupre n'est pas NULL. Si celle-ci venait tre
NULL et que nous essayions de la manipuler, nous aboutirions un beau plantage. Nous parcourons ensuite
chaque lment de la liste comme dans notre fonction prcdente ( noter la prsence de parenthses afin de
rsoudre la priorit de l'oprateur -> sur l'oprateur *). Seulement, nous prenons garde de sauvegarder
l'lment courant dans un pointeur p_del (qui sera comme vous l'aurez compris l'lment que nous
7
Exercice
Passons maintenant un petit exercice afin de mettre en uvre vos connaissances. Ecrivez une fonction
dlist_display prenant en paramtre une liste et affichant tous les lments de la liste spars par des '>'.
Lorsque la fin de liste sera atteinte, NULL sera affich. Exemple :
Code : Console
4 > 8 > 15 > 16 > 23 > 42 > NULL
Correction:
Code : C
1 void dlist_display(Dlist *p_list)
2{
3
if (p_list != NULL)
4
{
5
struct node *p_temp = p_list->p_head;
6
while (p_temp != NULL)
7
{
8
printf("%d -> ", p_temp->data);
9
fflush(stdout);
10
p_temp = p_temp->p_next;
11
}
12
}
13
printf("NULL\n");
14 }
Comme vous pouvez le voir, ceci n'a rien de sorcier. Il suffit tout simplement de parcourir la liste
entirement puis d'afficher les lments un un. Lorsque la boucle est termine, nous terminons par afficher
"NULL".
Notre premier printf n'tant pas termin par un '\n', nous nous devons de forcer l'affichage des caractres par
l'utilisation de fflush(stdout).
Ici, nous dcidons de supprimer l'lment portant la valeur 15. Comment allons-nous nous y prendre ? Tout
d'abord, comme vous pourrez l'imaginer, il va nous falloir parcourir notre liste la rechercher de notre
lment supprimer. Ds que l'on aura trouv la valeur correspondante, trois possibilits s'offriront nous:
l'lment se trouve en fin de liste
l'lment se trouve en dbut de liste
l'lment se trouve en milieu de liste
Si l'lment se trouve en fin de liste, Alors il faudra faire pointer notre p_tail vers l'avant dernier lment et
faire pointer le pointeur vers l'lment suivant de l'avant dernier lment vers NULL.
Sinon, si l'lment se trouve en fin de liste, Alors il faudra faire pointer notre p_head vers le second lment
et faire pointer le pointeur vers l'lment prcdent du second lment vers NULL.
Sinon, il faudra relier l'lment prcdent l'lment que l'on veut supprimer vers l'lment suivant
l'lment que l'on veut supprimer et il faudra aussi relier l'lment suivant l'lment su l'on veut supprimer
vers l'lment prcdent l'lment que l'on veut supprimer.
Une fois ceci fait, il ne nous restera plus qu' supprimer notre lment trouver et dcrmenter la taille de
notre liste.
Tout ceci constitue notre algorithme. A sa lecture, celui-ci peut sembler rebutant mais lors de la ralisation
en langage C, il vous deviendra beaucoup plus clair.
Avec ces explications, vous devriez tre capable de raliser le code tout seul.
Notre fonction ne supprimera que le premier lment trouv.
Code : C
1 Dlist *dlist_remove(Dlist *p_list, int data)
2{
3
if (p_list != NULL)
4
{
5
struct node *p_temp = p_list->p_head;
6
int found = 0;
7
while (p_temp != NULL && !found)
8
{
9
if (p_temp->data == data)
10
{
11
if (p_temp->p_next == NULL)
12
{
13
p_list->p_tail = p_temp->p_prev;
14
p_list->p_tail->p_next = NULL;
15
}
Vous voyez donc que traduit en langage C, l'algorithme devient plus intuitif. A noter cependant que nous
utilisons une variable supplmentaire nomme found pour nous arrter au premier lment trouv. Lorsque
l'lment est trouv, cette variable change d'tat et prend la valeur 1, marquant ainsi l'arrt de la boucle de
parcours.
Supprimer un ensemble d'lments suivant une mme valeur
L'algorithme prcdent ne nous permettait de supprimer uniquement le premier lment trouv. Nous allons
maintenant crire un code supprimant toutes les valeurs trouves dans la liste. Et devinez quoi ? Et bien
comme vous pouvez vous en douter, il s'agit exactement du mme code que prcdemment, mis part le fait
qu'ici nous n'utilisons plus de variable found, mais nous parcourons notre liste dans sa totalit.
Code : C
1 Dlist *dlist_remove_all(Dlist *p_list, int data)
2{
3
if (p_list != NULL)
4
{
5
struct node *p_temp = p_list->p_head;
6
while (p_temp != NULL)
7
{
8
if (p_temp->data == data)
9
{
10
struct node *p_del = p_temp;
11
p_temp = p_temp->p_next;
12
if (p_del->p_next == NULL)
13
{
14
p_list->p_tail = p_del->p_prev;
15
p_list->p_tail->p_next = NULL;
16
}
17
else if (p_del->p_prev == NULL)
18
{
19
p_list->p_head = p_del->p_next;
20
p_list->p_head->p_prev = NULL;
21
}
22
else
23
{
24
p_del->p_next->p_prev = p_del->p_prev;
25
p_del->p_prev->p_next = p_del->p_next;
10
}
free(p_del);
p_list->length--;
}
else
{
p_temp = p_temp->p_next;
}
}
}
return p_list;
11
Comme vous le voyez, tant que nous n'avons pas atteint la fin de liste, et tant que nous ne sommes pas la
bonne position, nous bouclons et nous incrmentons notre variable i. Lorsque nous avons trouv la bonne
place (position == i), nous supprimons alors notre lment courant en suivant exactement la mme mthode
que prcdemment.
Je ne pense pas que ce code ncessite une explication supplmentaire, vous devriez aisment le comprendre
(nous utilisons juste une variable ret de type size_t pour retourner le rsultat. Par dfaut, nous renvoyons 0 si
la liste n'existe pas).
Maintenant que nous bnficions d'une fonction permettant d'effacer un lment selon sa position ainsi que
d'une fonction permettant de retourner la taille de notre liste, nous pouvons aisment construire deux autres
fonctions dont le rle sera de supprimer le premier ainsi que le dernier lment de la liste. Pour cela, j'ai
choisi d'employer les macros:
Code : C
1 #define dlist_remove_first(list) dlist_remove_id(list, 1)
2 #define dlist_remove_last(list) dlist_remove_id(list, dlist_length(list))
Ainsi, l'appel dlist_remove_first et dlist_remove_last sera en fait remplac directement dans le code par un
appel dlist_remove_id. Pratique, n'est-ce pas?
Rechercher un lment
Recherche un lment selon sa valeur
Pour complter notre petite bibliothque, il se peut que nous ayons besoin d'une fonction de recherche. Cette
fonction de recherche sera un tout petit peu particulire. En effet, celle-ci ne renverra pas l'lment qu'elle
aura trouv mais une liste contenant l'lment qu'elle aura trouv. Ceci facilitera de ce fait notre gestion.
Comment faire ? Vous vous souvenez de la fonction de suppression ? Et bien nous allons utiliser le mme
style. Nous allons parcourir notre liste tant que nous n'aurons pas trouv notre lment (variable found). Si
nous trouvons notre lment, nous utilisons alors les fonctions dj notre disposition (dlist_new et
dlist_append) pour crer notre liste qui sera retourn.
Code : C
1 Dlist *dlist_find(Dlist *p_list, int data)
12
Dsormais, cette fonction doit vous paratre anodine. Si jamais nous ne trouvons aucun lment
correspondant une valeur donne, nous retournons alors une liste nulle.
Recherche un ensemble d'lments selon une mme valeur
Pour complter notre fonction de recherche, nous allons maintenant crer une fonction qui non pas s'arrte
au premier lment trouv, mais qui retourne tous les lments trouvs. Et devinez quoi ? Et bien oui, il
s'agit exactement du mme code que prcdemment, mais cette fois ci, sans l'utilisation de la variable found.
Ainsi, toute la liste est parcourue:
Code : C
1 Dlist *dlist_find_all(Dlist *p_list, int data)
2{
3
Dlist *ret = NULL;
4
if (p_list != NULL)
5
{
6
struct node *p_temp = p_list->p_head;
7
while (p_temp != NULL)
8
{
9
if (p_temp->data == data)
10
{
11
if (ret == NULL)
12
{
13
ret = dlist_new();
14
}
15
ret = dlist_append(ret, data);
16
}
17
p_temp = p_temp->p_next;
18
}
19
}
20
return ret;
21 }
Bon, je vous l'accorde, il y a quand mme une toute petite diffrence avec l'autre code. En effet, nous ne
pouvons pas crer notre liste de retour chaque fois que nous trouvons un lment. Il faut alors la crer ds
lorsque l'on aura trouv notre lment. Ensuite, nous nous contenterons d'ajouter tous les autres lments
13
Et bien voil, nous en avons maintenant termin avec nos fonctions de manipulation de listes chanes.
Passons maintenant un petit exercice pour vrifier vos acquis.
Exercice
Le but de cet exercice est de crer une fonction dlist_reverse permettant "d'inverser" une liste chane. Tous
les lments doivent alors tre inverss un par un (le premier doit se retrouver dernier, le deuxime avantdernier, ...). De plus, notre structure de liste nous donne une flexibilit incroyable car nous pouvons de
choisir de partir soit en dbut de liste, soit en fin.
Correction:
Code : C
1 Dlist *dlist_reverse(Dlist *p_list)
2{
3
Dlist *ret = NULL;
4
if (p_list != NULL)
5
{
6
struct node *p_temp = p_list->p_tail;
7
ret = dlist_new();
8
while (p_temp != NULL)
9
{
10
ret = dlist_append(ret, p_temp->data);
11
p_temp = p_temp->p_prev;
12
}
13
}
14
return ret;
15 }
Comme vous le voyez, j'ai choisi de partir en fin de liste puis d'ajouter en dbut de liste. Mais l'inverse est
aussi tout fait envisageable.
14
Ajout avant P
Suppression la position P
16
Suppression en queue
source :
http://www.mongosukulu.com/index.php/en/contenu/informatique-et-reseaux/algorithme/664-les-listesdoublements-chainees
17