Une telle chaîne circulaire s'appelle un anneau. L'intérêt d'une telle
structure est de pouvoir atteindre tous les éléments de la chaîne depuis
n'importe quel élément. Nous pouvons alors considérer la chaîne comme
n'ayant ni début ni fin. Si l'on désire toutefois pouvoir reconnaître le premier élément, il suffit de tester si le pointeur vers cet élément est égal au
pointeur "Dernier^.Suivant".
7.4.1.1. Manipulations sur les anneaux
Les algorithmes d'insertion, de suppression et de parcours d'éléments sont
très similaires à ceux que nous avons vus pour les chaînes linéaires. Il faut
juste faire attention, lors d'insertion ou de suppression en fin, de mettre
correctement à jour le pointeur dernier. De plus, lors d'insertion après un
élément donné, il faut tester si l'insertion s'est faite en fin d'anneau pour
ajuster le pointeur Dernier en conséquence. Pour une insertion avant un
élément donné, il faut renoncer au "truc" de l'insertion "après" avec échange des contenus, si l'élément donné se trouve en fin de chaîne.
La plupart des algorithmes d'insertion doivent traiter le cas particulier de l'anneau vide. Cela peut se faire à l'aide des instructions suivantes :
if Dernier = nil then begin {anneau vide} new(Dernier); with Dernier^ do begin Info := ...; { l'él. est son propre succ.} Suivant := Dernier; end; { with } end; { if }Pour le parcours d'un anneau il faut prendre en compte le fait qu'il n'y a plus de pointeur à nil pour indiquer la fin de la chaîne. Cela donne un algorithme de parcours tel que :
procedure Parcours(dernier: VersElement; Traite: procedure); var Courant, premier: VersElement; begin { Parcours } if Dernier = nil then { anneau vide } else begin Premier := Dernier^.Suivant; Courant:= Premier; repeat { traitement quelconque à appliquer à chaque élément } Traite(Courant); Courant := Courant^.Suivant; until Courant = Premier; end; end; { Parcours }
La déclaration contient un pointeur supplémentaire chargé d'indiquer
l'emplacement du prédécesseur. Il faut donc que, lors de l'insertion ou de
la suppression d'un élément, l'on tienne à jour les deux pointeurs des éléments impliqués dans l'opération.
7.4.2.1. Manipulations sur les chaînes bidirectionnelles
Exemple d'insertion en début (ressemble beaucoup à l'exemple vu au paragraphe 1) :
type VersElement = ^Element; Element = record Info: ...; Precedent, Suivant : VersElement; end; { Element } procedure InsertionDebut(var Debut: VersElement; Information: ...); var Nouveau: VersElement; begin { InsertionDebut } new(Nouveau); with Nouveau^ do begin Info := Information; Suivant := Debut; Precedent := nil; end; { with } if Debut <> nil then Debut^.Precedent := Nouveau; Debut := Nouveau; end; { InsertionDebut }Exemple d'insertion avant un élément donné (il n'est plus nécessaire d'utiliser le "truc" de l'insertion après avec échange des contenus) :
procedure InsertionAvant(var Debut: VersElement; Courant: VersElement; Information: ...); var Nouveau: VersElement; begin { InsertionAvant } { création d'un nouvel élément } new(Nouveau); with Nouveau^ do begin Info := Information; Suivant := Courant; Precedent := Courant^.Precedent; end; { with } { modification des liens des éléments existants } Courant^.Precedent := Nouveau; if Debut = Courant then Debut := Nouveau else Nouveau^.Precedent^.Suivant := Nouveau; end; { InsertionAvant }Exemple de suppression d'un élément donné :
procedure SuppressionMilieu(var Debut: VersElement; var Courant: VersElement); begin { SuppressionMilieu } { mise-à-jour du pointeur Suivant du prédécesseur et du pointeur Precedent du successeur } if Courant = Debut then begin Debut := Debut^.Suivant; if Debut <> nil then Debut^.Precedent := nil; end else with Courant^ do begin Precedent^.Suivant := Suivant; if Suivant <> nil then Suivant^.Precedent := Precedent; end; { with } { destruction de l'élément courant } dispose(Courant); end; { SuppressionMilieu }Pour une chaîne bidirectionnelle, l'insertion ou la suppression en fin implique toujours un parcours complet de la chaîne depuis son début, à moins que, comme pour les chaînes monodirectionnelles, l'on gère un pointeur Fin.
Cette structure bénéficie des avantages combinés qu'apportent le pointeur "Prédécesseur" et la circularité. Les opérations de suppression d'éléments et les manipulations en fin de chaîne s'en trouvent nettement simplifiées.
On peut d'ailleurs résumer la facilité avec laquelle on peut effectuer les opérations d'insertion, de destruction et de déplacement dans une chaîne, ou dans les différentes variantes que l'on vient de voir, de la manière suivante (F=facile, D=difficile) :
7.5. Utilisation d'un élément sans données
Table des
matières.