Opérations avancées
Il existe d'autres opérations courantes sur les listes.
Insertion d'une liste d'éléments
Pour l'insertion d'une autre liste ou sous-liste dans la liste, le principe est plus ou moins le même que l'insertion d'un élément. Il suffit de fournir l'élément avant (ou après) lequel placer la sous-liste, ainsi que le premier et le dernier élément de ladite sous-liste. Visuellement, l'opération ressemble à ceci :
Je vous laisse coder cela.
Tri
Le tri n'est pas une opération sur liste à proprement parler. Néanmoins, il est moins simple de trier une liste qu'un tableau en C, car impossible d'utiliser
qsort().
A vous de choisir un algorithme efficace (le
tri fusion est assez adapté aux listes) et de l'écrire si vous avez besoin d'une telle fonction.
Nombre d'éléments
On peut remarquer l'absence de possibilité de connaitre efficacement la taille de la liste. Selon l'implémentation proposée dans ce tuto, la fonction
nombreElements() devra être codée comme suit :
Code : C | size_t nombreElements (Liste_Circulaire_Doublement_Chainee* liste)
{
size_t n = 0;
Liste_Circulaire_Doublement_Chainee* it;
for ( it = liste->suiv; it != liste; it = it->suiv )
n++;
return n;
}
|
Ce code n'est pas performant, car pour connaitre le nombre d'éléments présents dans la liste, on doit la parcourir tous ses éléments. Cette limitation peut devenir gênante selon les cas d'utilisation.
Pour stocker proprement la taille de la liste, il vaut mieux encapsuler cette dernière comme ceci :
Encapsulation
Code : C 1
2
3
4
5
6
7
8
9
10
11
12 | typedef struct _e
{
int val; /* données */
struct _e* prec; /* pointeur sur l'élément précédent */
struct _e* suiv; /* pointeur sur l'élément suivant */
} _elem;
typedef struct
{
_elem *racine; /* pointeur sur la racine de la liste */
size_t nb_elements; /* nombre d'éléments dans la liste */
} Liste_Circulaire_Doublement_Chainee;
|
La structure Liste_Circulaire_Doublement_Chainee contient à présent l'élément racine et le nombre d'éléments présents dans la liste. Il nous faudra donc modifier le prototype des fonctions en conséquence.
La fonction creeListe devra initialiser le nombre d'éléments a zéro, et les fonction de modification (ajout/suppression d'éléments) devront respectivement incrémenter ou décrémenter le nombre d'éléments de la liste. Dés lors, toutes les fonctions permettant de modifier la liste doivent recevoir un pointeur sur la structure Liste_Circulaire_Doublement_Chainee...
L'ennui majeur avec ce type de methode, c'est que l'insertion d'un segment de liste dans une autre liste perd en performances : on doit parcourir pour connaitre le nombre d'éléments ajoutés.
Retour des fonctions
Une fonction modifiant un objet est dite "destructive". Les fonctions de modification de liste proposées ci-dessus sont toutes destructives, en accord avec la philosophie du langage C. C'est pourquoi le type de retour est
void.
Il est tout à fait possible d'écrire des versions non-destructives de ces fonctions, c.a.d que la fonction renvoie une copie modifiée de la liste sans altérer la liste originale. Dans ce cas, les fonctions renverront une liste.
Optimisation
La réutilisation des fonctions existantes est une bonne idée d'un point de vue lisibilité, mais pas d'un point de vue des performances. Si le code est appelé a être le plus performant possible, il vaut mieux ne pas imbriquer les fonctions.
Utilité
Enfin, l'un des avantages de ce type de liste, mis à part la simplicité d'implémentation, est de pouvoir momentanément supprimer un élément de la liste sans l'effacer, pour le restaurer ensuite à la même place (le cacher en quelque sorte). Cette opération, qui exige que la liste ne soit pas modifiée entre temps, se justifie dans certains algorithme que je n'aborderai pas ici, mais sachez qu'elle existe :
Code : C | void cacherElement (Liste_Circulaire_Doublement_Chainee* element)
{
element->prec->suiv = element->suiv;
element->suiv->prec = element->prec;
}
void restaurerElement(Liste_Circulaire_Doublement_Chainee* element)
{
element->prec->suiv = element;
element->suiv->prec = element;
}
|