Vous êtes sur la page 1sur 12

Les Pointeurs & Listes Chainées 8

L a Mémoire Centrale (MC) est formée par des cases numérotées. Chaque case peut stocker un ou
plusieurs octets. Une variable est une zone contigüe en MC (une case ou un ensemble de cases
qui se suivent). Sa taille (en nombre d’octets) dépend du type de la variable (ex: un entier
occupe 4 octets, un réel occupe 8 octets, ...etc). L'adresse d'une variable est le numéro de sa première
case.

Un programme qui s’exécute utilise au moins deux zones


mémoires, appelées segments :
 le segment de code : contient les instructions du
programme;
 le segment de données : contient les données utilisées
par le programme. Le segment de données est
découpé en trois parties :
 la zone statique : contient les variables statiques,
c’est-à-dire les variables ayant une durée de vie TAS
égale à celle du programme : c’est le cas des
variables globales
 la pile (Stack): sert à gérer les éléments du
programme dont la durée de vie est limitée à un
bloc : c’est le cas des paramètres des appels de
fonctions/procédures, des variables locales Elle
sert également à renvoyer les résultats des
fonctions.
 le tas (Heap): tout ce qui reste... Un programme ne peut accéder directement aux
emplacements du tas. Par contre, on peut allouer des emplacements dans le tas et les
référencer ensuite à l’aide de pointeurs. La gestion du tas est à la charge du programmeur.

Les variables statiques possèdent les caractéristiques suivantes :


 Leurs formes et leurs tailles sont prédéterminées (déclarées) à l’avance dans le programme.
 Elles existent en mémoire durant toute l’exécution du bloc (programme ou procédure) dans
lequel elles sont définies.
 Les variables statiques sont référencées directement par des identificateurs (nom de la
variable).

Contrairement aux variables statiques, les variables dynamiques :


 Peuvent être créées et utilisées uniquement au fur et à mesure des besoins.
 Peuvent être détruites à tout moment afin de récupérer l’espace mémoire devenu inutile.
 Permettent l’insertion et la suppression d’éléments sans toucher au reste des données.

En revanche les variables dynamiques ne peuvent être adressées directement par un identificateur.
Elles sont accessibles par l’intermédiaire d’une variable statique spéciale (dite « pointeur ») contenant
l’adresse mémoire de la variable dynamique. On dit que le pointeur pointe vers cette variable
dynamique ou que cette variable dynamique est pointée par le pointeur.
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

Exemple
Type
personne = record
Nss: integer; /* 4 Octets
nom: string[25]; /* 25 Octets
prenom: string[25]; /* 25 Octets
end;

Var
Liste : array [1..500] of personne; /* 500x[4+20+20]=27000
octets = 27KO

Ce tableau à 500 éléments est réservé tout au long de l’exécution du programme ; c.-à-d., l’espace
occupé ne sera libéré qu’à la fin de l’exécution du programme.

Si on veut passer ce tableau en paramètre à une procédure, le compilateur vous renverrait une
erreur du type : Structure too large car il lui est impossible de réserver plus de 16 Ko en
mémoire pour les variables des sous-programmes. D'où l'intérêt des pointeurs car quel que
soit la grosseur de la variable pointée, la place en mémoire du pointeur est toujours la même :
4 octets (8 octets pour systèmes 64 bits).

La déclaration d'un pointeur permet donc de réserver une petite place de la mémoire qui
pointe vers une autre qui peut être très volumineuse. L'intérêt des pointeurs est que la variable
pointée ne se voit pas réserver de mémoire, ce qui représente une importante économie de
mémoire permettant de manipuler un très grand nombre de données. Puisque la Pile
normalement destinée aux variables des sous-programmes est trop petite (16 Ko), on utilise
donc le Tas réservé au pointeur qui nous laisse jusqu'à 64 Ko, soit quatre fois plus !

Avant d'utiliser une variable de type pointeur, il faut déclarer ce type en fonction du type de
variable que l'on souhaite pointer.

Définition
Un pointeur est une variable qui contient l’adresse d’une donnée contenue en mémoire (autre variable
stockée en mémoire). La déclaration d’une variable pointeur réserve 4 octets nécessaires au codage
de l’adresse mémoire de la donnée pointée mais ne réserve aucune mémoire pour la donnée pointée
Quel que soit le type de la donnée pointée, la taille mémoire du pointeur est toujours la même : 4
octets.

L’adressage indirect permet une gestion dynamique de la mémoire

2|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

Soit P le pointeur et P^ la variable "pointée" par le pointeur. La déclaration d'une variable


pointeur réserve 4 octets nécessaires au codage de l'adresse mémoire mais ne réserve aucune
mémoire pour la variable pointée.

Jusqu'alors nous avions vu que la déclaration d'une variable provoque automatiquement la


réservation d'un espace mémoire qui est fonction du type utilisé. Voir Chapitre 1 ("Différents
types de variables") pour la taille en mémoire de chacun des types de variables utilisés ci-
après.

Exemple 1 :
Var P : ^Integer;

On déclare une variable P de type ^Integer qui est en fait un pointeur pointant vers un
Integer (à noter la présence indispensable de l'accent circonflexe!). Donc la variable
(pointeur) P contient une adresse mémoire, celle d'une autre variable qui est-elle, de type
Integer. Cette déclaration n’effectue pas de réservation  Elle réserve bien un emplacement
nommé P mais uniquement pour pouvoir y placer plus tard une adresse : celle de la valeur pointée.

Ainsi l'adresse mémoire contenue dans P est l'endroit où se trouve le premier octet de la
variable de type Integer. Il est inutile de préciser l'adresse mémoire de fin de
l'emplacement de la variable de type Integer car une variable de type connu quel que soit
sa valeur occupe toujours le même espace. Le compilateur sachant à l'avance combien de
place tient tel ou tel type de variable, il lui suffit de connaître grâce au pointeur l'adresse
mémoire du premier octet occupé et de faire l'addition adresse mémoire contenue dans le
pointeur + taille mémoire du type utilisé pour définir totalement l'emplacement mémoire de la
variable pointée par le pointeur.

Tout ça c'est très bien mais comment fait-on pour accéder au contenu de la variable pointée
par le pointeur ? Il suffit de manipuler l'identificateur du pointeur à la fin duquel on rajoute un
accent circonflexe en guise de variable pointée.
Exemple :
P^ := 128 ;
Donc comprenons-nous bien, P est le pointeur contenant l'adresse mémoire d'une variable et
P^ (avec l'accent circonflexe) contient la valeur de la variable pointée. On passe donc du
pointeur à la variable pointée par l'ajout du symbole spécifique ^ à l'identificateur du
pointeur.

1. OPERATIONS SUR LES VARIABLES DYNAMIQUES


1.1.CREATION D’UNE VARIABLE DYNAMIQUE
 New (p) : alloue dynamiquement une zone de mémoire (zone réservée au cours de l’exécution
du programme, et non à sa compilation). La variable p pointe sur cette zone qui pourra être utilisée
comme n’importe quel variable.

P P^
P^ : permet d’accéder à la variable pointée par P ;

3|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

Exemple
Var
ptr1 : ^Integer ;
ptr2 : ^Integer ;
Begin
New (ptr1);
ptr1^:=2;
ptr2:=ptr1;
End.

Par rapport à nos explications sur le segment de données, le pointeur lui-même est alloué dans la partie statique
du segment de données tandis-que l’emplacement pointé est alloué sur le tas.

1.2.LA CONSTANTE NIL :


 Le constant pointeur NIL (en Pascal) indique l'absence d'adresse. Donc, par exemple en Pascal,
l'affectation p := NIL, veut dire que p ne pointe aucune variable.
 Il ne faut jamais utiliser l'indirection (^ en pascal) avec un pointeur ne contenant pas l'adresse
d'une variable, il y aura une erreur. Pour tester si un pointeur pointe vers un emplacement, il
suffira alors de tester s’il est égal à NIL :
If P = NIL Then ...
 Un pointeur valant NIL existe (il est déclaré) mais ne pointe sur rien.

1.3.DESTRUCTION D’UNE VARIABLE DYNAMIQUE


 Dispose (p) : permet de libérer l'espace mémoire alloué (réservé) pour p et de la rendre au
système. Ceci signifie que p ne peut plus être utilisé à moins qu'il ne soit associé de nouveau à une
instruction new (p).

P ?

Nous sommes revenus à la situation de départ.

1.4.COMPARAISON ET AFFECTATION
Un pointeur d’un type T peut être comparé avec tout autre pointeur de type T ou avec la valeur nil. Il
en va de même pour l’affectation.

Exemple
Program pointeurs2;
Type
T_Pointeur_Entiers = ^integer;
Var Quelles sont les 2 valeurs affichées
Ptr_Int1, Ptr_Int2 : T_Pointeur_Entiers; pour Ptr_Int2^ ?
Begin
New(Ptr_Int1);
Ptr_Int1^ := 10;
Ptr_Int2 := Ptr_Int1;
Writeln('Ptr_Int2 pointe sur ', Ptr_Int2^);
Dispose(Ptr_Int1);
Writeln('Ptr_Int2 pointe sur ', Ptr_Int2^);
End.

1.5.UTILISATION SANS ALLOCATION


On peut également utiliser les pointeurs sans avoir recours à l’allocation dynamique : ils servent alors
d’alias (‫ )اسم مستعار‬pour des variables déjà créées. Pour ce faire, on peut utiliser l’opérateur

4|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

d’indirection : placé devant un nom de variable, il renvoie l’adresse mémoire de celle-ci, qui peut donc
être affectée à un pointeur. La forme générale est : ptr := @variable ;

Les pointeurs sont typés ce qui signifie qu’ils ne peuvent recevoir que des adresses d’emplacements du type pour
lequel ils ont été définis. On ne peut affecter une adresse de réel à un pointeur d’entier, par exemple.

Exemple
Program POINTEURS_1;
Var
Ent_1, ent_2 : INTEGER;
ptr_ent_1, ptr_ent_2 : ^INTEGER;
Begin
ent_1 := 10; ent_2 := 100;
ptr_ent_1 := @ent_1;
ptr_ent_2 := @ent_2; Après l’exécution du
writeln('ptr_ent_1 pointe sur ', ptr_ent_1^); programme on aura
writeln('ptr_ent_2 pointe sur ', ptr_ent_2^);
ptr_ent_1 := ptr_ent_2;
writeln('ptr_ent_1 pointe sur ', ptr_ent_1^);
writeln('ptr_ent_2 pointe sur ', ptr_ent_2^);
ptr_ent_1^ := 2000;
writeln('entier_1 = ', ent_1,'ent_2 = ', ent_2);
ptr_ent_1^ := ptr_ent_1^ + ent_1;
writeln('entier_2 = ', ent_2);
ent_2 := ptr_ent_2^ * 10;
writeln('entier_2 = ', ent_2);
End.

1.6.UTILISATION DES POINTEURS POUR LES TYPES NON STANDARDS


Les variables dynamiques peuvent être de n'importe quel type, simple ou complexe. Voici un exemple
en Pascal montrant l'allocation dynamique de tableaux, enregistrement …etc.

1.6.1. POINTEUR DE TABLEAUX


Type
T_tab = array[1..100] of integer; { def d'un type tableau de 100 entiers }

Var
p : ^T_tab; {allocation statique de la variable p (un pointeur)}
i : integer; {allocation statique de la variable i (un entier)}
Begin
New(p);{un tableau de 100 entiers vient d'être alloué dynamiquement, son adr est dans p }
p^[1] := 10;
p^[2] := 3;
p^[3] := p^[1] * 2 + 5;
dispose( p );{ maintenant le tableau n'existe plus, on a plus le droit de manipuler p^ }
{mais on peut créer un autre tableau on utilisant le même pointeur}
new(p);
For i:=1 to 100 do
p^[i] := i*i;
End.

5|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

1.6.2. POINTEUR D’ENREGISTREMENTS


Type
TSexePersonne = (Feminin, Masculin);
TPersonne = record
Nom,Prenom,CodePostal,Adresse, Ville: string;
Sexe: TSexePersonne;
End;
PPersonne = ^TPersonne;
Il faudra donc éviter cela : array[1..1000] of TPersonne au profit de cela : array[1..1000] of PPersonne

1.6.3. LES STRUCTURES RECURSIVES


Une structure récursive est une structure dont l’un des champs est du type de la structure. La
définition suivante n’est cependant pas correcte :
Type T_Personne = record
nom : string;
pere, mere : T_Personne;
End;

T_Personne n’est connu qu’après le end; de la définition : Pascal ne connaît donc pas la taille mémoire qu’il
faut réserver pour les champs père et mère. Les pointeurs, et la possibilité d’anticiper la définition d’un type
permettent de corriger ce problème :

Type
T_Ptr_Personne = ^T_Personne;
T_Personne = record
nom : string;
pere, mere : T_Ptr_Personne; {pointeur}
End;

C’est cette méthode que l’on utilisera pour toutes les structures de données gérée dynamiquement,
telles que les listes.

2. LISTES CHAINEES
Les listes sont des structures de données informatiques qui permettent, au même titre que les tableaux
par exemple, de garder en mémoire des données en respectant un certain ordre : on peut ajouter,
enlever ou consulter un élément en début ou en fin de liste, vider une liste ou savoir si elle contient un
ou plusieurs éléments.

2.1.IMPLANTATION DES LISTES.


Il existe plusieurs méthodes pour implémenter des listes. Les plus courantes sont l’utilisation de
tableaux et de pointeurs.

2.1.1. UTILISATION DE TABLEAUX (IMPLEMENTATION CONTIGÜE)


Implémenter une liste à l’aide d’un tableau n’est pas très compliqué. Les éléments de la liste sont
simplement rangés dans le tableau à leur place respective. Cependant, l’utilisation de tableaux possède
quelques inconvénients :
 La dimension d’un tableau doit être définie lors des déclarations et ne peut donc pas être modifiée
« dynamiquement » lors de l’exécution d’un programme. La solution consiste donc à définir un
tableau dont la taille sera suffisante pour accueillir la plus grande liste pouvant être utilisée, et
d’associer au tableau une variable indiquant le nombre d’éléments contenus dans le tableau.
 Le tableau étant surdimensionné, il encombre en général la mémoire de l’ordinateur.
 Si la taille maximum venait à être augmentée, il faudrait modifier le programme et recompiler.
 Lorsque l’on retire un élément du tableau, en particulier en début de liste, il est nécessaire de
décaler tous les éléments situés après l’élément retiré.

6|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

2.1.2. UTILISATION DE POINTEURS (IMPLEMENTATION CHAINEE)


Les pointeurs définissent une adresse dans la mémoire de l’ordinateur, adresse qui correspond à
l’emplacement d’une autre variable. Il est possible à tout moment d’allouer cet espace dynamiquement
lors de l’exécution du programme

C’est l’implémentation la plus naturelle et la plus utilisée. Dans cette représentation, chaque élément
de la liste est stocké dans une cellule et les cellules sont reliées par des pointeurs. Chaque cellule
contient, en plus de la donnée à traiter, l’adresse de la cellule suivante. A chaque cellule est associée
une adresse mémoire. Les Listes Linéaires Chaînées (LLC) font appel à la notion de variable
dynamique. Cette dernière peut y être créée ou détruite (c’est-à- dire, on lui alloue un espace mémoire
à occuper et qu’il sera libéré après son utilisation).

2.2.LA NOTION DE LISTE


Une liste est une structure de données qui permet de stocker une séquence d’objets d’un même type.
En cela, les listes ressemblent aux tableaux. La séquence d’entiers 3, 7, 2, 4 peut être représentée à la
fois sous forme de tableau ou de liste. La notation [3, 7, 2, 4] représentera la liste qui contient cette
séquence. Il y a cependant des différences fondamentales entre listes et tableaux :
 Dans un tableau on a accès immédiat à n’importe quel élément par son indice (accès dit aléatoire
ou directe), tandis que dans une liste chaînée on a accès aux éléments un après l’autre, à partir du
premier élément (accès dit séquentiel).
 Un tableau a une taille fixe, tandis qu’une liste peut augmenter en taille indéfiniment (on peut
toujours rajouter un élément à une liste).

2.3.DEFINITION (RECURSIVE)
En considérant que la liste la plus simple est la liste vide (notée [ ]), qui ne contient aucun élément, on
peut donner une définition récursive aux listes chaînées d’éléments de type T :
 La liste vide [ ] est une liste ;
 Si e est un élément de type T et L est une liste d’éléments de type T, alors le couple (e, L) est
aussi une liste, qui a comme premier élément e et dont le reste des éléments (à partir du
second) forment la liste L.
Cette définition est récursive, car une liste est définie en fonction d’une autre liste. Une telle définition
récursive est correcte, car une liste est définie en fonction d’une liste plus courte, qui contient un
élément de moins. Cette définition permet de construire n’importe quelle liste en partant de la liste
vide.
Conclusion : la liste chaînée est une structure de données récursive.

2.4.REPRESENTATION DES LISTES LINEAIRES OU SIMPLEMENT CHAINEES (LLC)


Avant de présenter les différents algorithmes manipulant une LLC, il est utile de montrer un schéma
représentant graphiquement l’organisation des éléments de la LLC. Dans la figure ci-dessous L est une
LLC de quatre cellules. Chaque cellule comporte deux parties : la première partie représente la donnée
à traiter (l’élément de la liste) notée info ou Valeur, et la deuxième partie indique l’adresse de la
cellule suivante (une référence vers la cellule suivante), notée suivant. Ce schéma met en évidence un
certain nombre de points :

 Une LLC est caractérisée par l’adresse de son premier élément tête(L), (La liste donne
accès à la première cellule, le reste de la liste est accessible en passant de cellule en cellule,
suivant leur enchaînement).
 Nil constitue l’adresse qui ne pointe sur aucune cellule. Elle est utilisée pour marquer la fin de
la liste,
 Une LLC vide est une liste qui ne contient aucun élément, elle est représentée par Nil,
 Si la place P est référencée par le pointeur P dans L, le suivant de P sera référencé par
P^.suivant. Dans l’exemple, si P occupe la position 2 (la cellule qui contient la valeur18),
P^.suivant pointe sur la cellule 3 (la cellule qui contient la valeur 12),

7|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

 Pour accéder à la valeur stockée dans la cellule C de la liste L, il suffit d’écrire C^.valeur.
Dans l’exemple, si C représente la cellule 2, alors C^.valeur donne la valeur 18.

Une liste chaînée est une collection d’éléments possédant chacun deux composantes : un pointeur vers le prochain élément
de la liste et une « valeur » de n’importe quel type.

2.5.DECLARATION D’UNE LLC


Il faut tout d’abord commencer par définir le type de variable pour chaque élément de la LLC. En
langage pascal, ceci se fait comme suit :
Type
Liste =^cellule
Cellule = Record
Info : type_info ; /*n’importe quel type*/
Suivant : liste ;
End ;
Var P, tete : liste ;/* Une fois le type liste est défini, on peut déclarer une
variable de type pointeur sur liste */

2.6.OPERATIONS SUR LES LLC


Dans cette section, on présentera quelques opérations de base sur les LLC. Parmi ces opérations, nous
citons :
 La création d’une LLC,
 Le parcours d’une LLC,
 L’insertion d’un élément dans une LLC,
 La suppression d’un élément d’une LLC.

Lors de la création, attention à bien initialiser la tête de la liste à Nil, sinon la chaine n’aura pas de buttoir final et il ne sera
pas possible de repérer sa fin. Une LLC est connue par l’adresse de son premier élément. Si cette adresse n’est pas
mémorisée, la liste disparait. Donc, il faut toujours avoir l’adresse de la tête mémorisée.

2.6.1. CREATION D’UNE LISTE


La création d’une LLC consiste à créer et enchaîner, au fur et à mesure, les éléments constituant la
LLC de telle sorte que le pointeur tête pointe sur le premier élément. Le premier élément pointe sur le
second et ainsi de suite. Le dernier élément ne pointe sur aucun autre élément, donc, sa partie suivant
pointe sur Nil.

Exemple 1 : Création d’une LLC composée de 2 éléments entiers.


Program creation ;
Type liste =^cellule
Cellule = Record
Valeur : integer ;
Suivant : liste ;
End ;
Var L, P, Q : liste ;
Begin
| L:= Nil ; /* Pour l’instant la liste est vide*/
| New(P) ; /* Réserver un espace mémoire pour le premier élément */
| Read (P^.valeur) ; /* Stocker dans valeur de l’élément pointé par P la valeur saisie */
| P^.suivant := Nil ; /* Il n’y a pas d’élément suivant */
| L := P; /* Le pointeur Tete pointe sur le premier élément P */
| /* Il faut ajouter le 2ieme élément */
| New(Q) ; /* Réserver un espace mémoire pour le second élément */
| read (Q^.valeur) ; /* Stocker dans valeur de l’élément pointé par P la valeur saisie */
| P^.Suivant := Q; /* Relier le premier élément avec le deuxième élément */
| Q^.Suivant := Nil ; /* Mettre Nil dans la partie adresse du dernier élément*/
End.

8|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

Exemple 2 : Création d’une LLC composée de plusieurs éléments entiers.


Program creation_plusieurs_elets ;
Type
Liste =^cellule ;
Cellule = Record
Info : integer ;
Suivant : liste ;
End;
Var
L, P, Q : liste ;
reponse : string[3] ;

Begin
| L := Nil ; /* Pour l’instant la liste est vide*/
| New(P) ; /* Réserver un espace mémoire pour le premier élément */
| Read (P^.info) ; /* Stocker dans l’Info de l’élément pointé par P la valeur saisie */
| P^.suivant := Nil ; /* Il n’y a pas d’élément suivant */
| L := P; /* Le pointeur Tete pointe maintenant sur P */
| Repeat
| | New(Q) ; /* Réserver un espace mémoire pour l’élément suivant */
| | Read (Q^.info) ; /* Stocker dans l’Info de l’élément pointé par Q la valeur saisie */
| | Q^.suivant :=Nil ; /* Mettre Nil dans la partie adresse du dernier élément*/
| | P^.suivant := Q; /* Relier l’élément précédant avec l’élément suivant */
| | P :=Q; /* Le pointeur P pointe sur Q*/
| | Writeln (‘Voulez-vous créer un autre élément’) ;
| | Read (reponse) ;
| Until (reponse =’NON’);
End.

2.6.2. PARCOURS D’UNE LISTE


Pour parcourir une LLC, il suffit avec un pointeur de prendre successivement l’adresse de chaque
cellule. Au départ, le pointeur prend l’adresse du premier élément qui est l’adresse de la tête de liste,
ensuite, il se déplace vers l’adresse du suivant et ainsi de suite.

Exemple 1 : Parcourir la liste L créée précédemment, en affichant le contenu du champ info.


Program parcours ;
Type liste =^cellule
cellule = Record
info : integer ;
suivant : liste ;
End;
Var L, P : liste ;
Begin
| If (L = Nil) then
| |writeln (‘L est vide’)
| else
| Begin
| | P := L ; /*Mémoriser l’adresse de la tête*/
| | While( P<>Nil) do /* Parcourir L jusqu’au dernier élément*/
| | Begin
| | | writeln (P^.info) ; /*Afficher le contenu du champ Info*/
| | | P := P^.suivant ; /* Passer d’un élément à l’autre*/
| | End;
| |
| End;
End.

9|Page
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

2.6.3. INSERTION D’UN ELEMENT DANS UNE LLC


Le fait d’insérer plutôt qu’ajouter un élément dans la liste suppose un classement. Il faut un critère
d’insertion, pourquoi ici et pas là ? Par exemple, nous allons insérer nos éléments de façon à ce que les
valeurs entières soient classées en ordre croissant. Trois cas sont possibles pour l’insertion : l’insertion
au début de la LLC, l’insertion au milieu de la LLC, l’insertion à la fin de la LLC.

2.6.3.1. INSERTION D’UN ELEMENT AU DEBUT D’UNE LLC


L’insertion d’un élément en tête de la liste est l’opération
essentielle sur les LLC, est appelée aussi constructeur.
Pour réaliser cette opération, il suffit de faire pointer le
champ suivant du nouvel élément de tête vers l’ancienne
LLC, qui devient ainsi la tête de la nouvelle (voir la
figure). Il y a deux actions, dans cet ordre, à réaliser (voir
le programme) :
 Créer le nouvel élément.
 Créer un lien entre le nouvel élément et la tête de
la LLC de telle sorte que la tête soit le suivant du
nouvel élément et le nouvel élément devient le
premier élément de la LLC.
Program Ajout_debut ;
Type liste =^cellule ;
cellule = record
info : integer ;
suivant : liste ;
End ;
Var L, nouveau : liste ;
Begin
| New(nouveau) ; /* Réserver un espace mémoire pour le premier élément*/
| read (nouveau^.info) ; /* Stocker dans l’Info de l’élément pointé par nouveau la valeur saisie*/
| nouveau^.suivant:= L ; /* Créer un lien entre le nouvel élément et la tête de la LLC */
| L := nouveau ; /* Le pointeur L pointe sur nouveau, et nouveau devient le premier élément de L */
End.

2.6.3.2. INSERTION D’UN ELEMENT AU MILIEU D’UNE LLC


Pour ce faire, il faut chercher la bonne place et conserver l’adresse de l’élément qui précède. Pour
repérer l’élément précédent, la liste est parcourue tant que l’élément courant n’est pas nulle et que le
bon emplacement n’est pas encore trouvé. Nous avons besoin de deux pointeurs : un pour l’élément
courant et un autre pour l’élément précédent. A chaque itération, avant d’avancer l’élément courant,
l’adresse de l’élément précédent est sauvegardée. Pour réaliser cette opération, il y a trois actions à
exécuter dans cet ordre (voir le programme) :
 Créer le nouvel élément.
 Localiser l’élément précédent.
 Relier l’élément précédent avec le nouvel élément.
Program Ajout_milieu ;
Type liste =^cellule ;
cellule = record
info : integrt;
suivant : liste ;
End;
Var L, precedent, nouveau: liste;
Begin
| New(nouveau) ; /* Réserve un espace mémoire pour le nouveau élément*/
| Read(nouveau^.info) ; /* Stocke dans l’Info de l’élément pointé par nouveau la valeur saisie*/
| precedent:=L ; /* Mémoriser la tête de L dans une autre liste pour ne pas la perdre */
| While( la condition d’arrêt n’est pas vérifiée) Do /* Localiser l’endroit d’insertion*/
| | precedent := precedent^.suivant ; /* Passer d’un élément à l’autre*/
|
| nouveau^.suivant :=precedent^.suivant ; /* Relier le nouveau élément avec son suivant */
| precedent^.suivant := nouveau ; /* Relier le nouveau élément avec son précédent */
End.

10 | P a g e
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

2.6.3.3 INSERTION D’UN ELEMENT A LA FIN D’UNE LLC


Pour réaliser cette opération, il y a deux actions à exécuter dans cet ordre (voir le programme
 Créer le nouvel élément dont sa partie suivant est Nil.
 Récupérer l’adresse du dernier élément.
 Relier le dernier élément avec le nouvel élément de telle sorte que le nouvel élément devienne
le suivant du dernier élément.

Program Ajout_fin ;
Type liste =^cellule
cellule = Record
info : integer ;
suivant : liste ;
End;
Var L, nouveau, P : liste ;
Begin
| New(nouveau ) ; /* Réserver un espace mémoire pour le nouveau élément*/
| read(nouveau^.info) ; /* Stocker dans l’Info de l’élément pointé par nouveau la valeur saisie*/
| nouveau^.suivant := Nil ; /*Mettre Nil dans le suivant de nouveau,car, il sera le dernier élément de L.*/
| P :=L ; /* Mémoriser la tête de L dans une autre liste pour ne pas la perdre */
|while( P^.suivant < > Nil) do /* Parcourir L jusqu’à le dernier élément*/
| | P:= P^.suivant ; /* Passer d’un élément à l’autre*/
|
| P^.suivant:=nouveau ; /* Relier le dernier élément de L avec nouveau */
End.

2.6.4. SUPPRESSION D’UN ELEMENT D’UNE LLC


Pour supprimer un élément de la liste, il faut le repérer en premier lieu et le détruire s’il existe tout en
gardant le chaînage de la liste. Comme l’opération de l’insertion, trois cas sont possibles :
 La suppression du premier élément de la LLC,
 La suppression d’un élément se trouvant au milieu de la LLC,
 La suppression du dernier élément de la LLC.

2.6.4.1. SUPPRESSION DU PREMIER ELEMENT D’UNE LLC


La suppression du premier élément suppose de bien actualiser la valeur du pointeur premier qui
indique toujours le début de la liste. Pour réaliser cette opération, il y a deux actions à exécuter dans
cet ordre :
 Faire pointer la tête de liste sur le deuxième élément de la liste,
 Libérer l’espace mémoire occupé par l’élément supprimé.
Il est nécessaire de déclarer un pointeur P qui va pointer sur l’élément à supprimer et permettre de
libérer l’espace qu’il occupait.

Program supprimer_premier ;
Type liste =^cellule ;
cellule = Record
info : integer;
suivant : liste ;
End;
Var L, P : liste ;
Begin
| If (L<>Nil) alors /* La liste n’est pas vide on peut donc supprimer le premier élément*/
| Begin
| | P := L ; /* P pointe sur le premier élément de L*/
| | L := L^.suivant ; /* L pointe sur le deuxième élément */
| | Dispose (P) ; /* Libération de l’espace mémoire qu’occupait le premier élément */
| End
| Else
| | Write("la liste L est vide") ;
|
End.

11 | P a g e
Chapitre 8 : LES POINTEURS & LISTES CHAINEES

2.6.4.2 SUPPRESSION D’UN ELEMENT SE TROUVANT AU MILIEU D’UNE LLC


L’objectif est de supprimer un élément quelconque de la LLC en fonction d’un critère donné. Le
critère ici est que le champ info de l’élément soit égal à une valeur donnée. La recherche s’arrête si un
élément répond au critère et nous supprimons uniquement le premier élément trouvé s’il y en a un.
Comme pour la suppression précédente, la LLC ne doit pas être
vide. Pour réaliser cette opération, il faut :
 Trouver l’adresse P de l’élément à supprimer,
 Sauvegarder l’adresse Precedent de l’élément précédant l’élément pointé par P pour connaître
l’adresse de l’élément précédant l’élément à supprimer, puis faire pointer l’élément précédent
sur l’élément suivant l’élément à supprimer,
 Libérer l’espace mémoire occupé par l’élément supprimé.

Program supprimer_milieu ;
Type liste =^cellule
cellule = Record
info : integer;
suivant : liste ;
End ;
var L, P, precedent : liste ;
Begin
if (L <> Nil) Then /* La liste n’est pas vide on peut donc supprimer le premier élément */
| | Precedent := L ; /*Pointeur précédent */
| | P := L^.suivant ; /* Pointeur courant */
| | while( l’élément à supprimer n’est pas encore trouvé) do /* Localisation du l’élément à supprimer*/
| | | Precedent := P; /*On garde la position du précédent */
| | | P:= P^.suivant ; /* On passe à l’élément suivant dans la liste */
| | end;
| | Precedent^.suivant := P^.suivant ; /*On "saute" l’élément à supprimer */
| | Dispose(P); /*Libération de l’espace mémoire qu’occupait l’élément P*/
else
| | write ("la liste L est vide") ;
End.

2.6.4.2. SUPPRESSION D’UN ELEMENT SE TROUVANT A LA FIN D’UNE LLC


La suppression du dernier élément consiste à changer le pointeur de l’avant dernier élément à Nil, ce
qui le rend le nouveau dernier élément, puis de libérer l’espace occupé par le dernier élément. Les
différentes étapes, permettant de réaliser cette opération sont :
 Localiser le dernier élément ainsi que l’élément qui le précède,
 Libérer l’espace occupé par le dernier élément,
 Mettre Nil dans la partie suivant de l’élément avant le dernier.

Program supprimer_dernier ;
Type liste =^cellule
cellule = record
info : integer;
suivant : liste;
End;
Var L, P, precedent : liste ;
Begin
| if (L <> Nil) then /* La liste n’est pas vide on peut donc supprimer le premier élément */
| | Precedent :=L ; /*Pointeur précédent */
| | P :=L^.suivant ; /* Pointeur courant */
| | while (P^.suivant <> Nil) do/* Localisation du dernier élément*/
| | | Precedent := P; /*On garde la position du précédent */
| | | P:= P^.suivant ; /* On passe à l’élément suivant dans la liste */
| | end;
| | Dispose(P) ; /*Libération de l’espace mémoire qu’occupait l’élément P*/
| | Precedent^.suivant:= Nil /* Mettre Nil dans la partie suivant du précédent */
| else
| | write ("la liste L est vide") ;
End.

12 | P a g e

Vous aimerez peut-être aussi