Probleme – La liste doublement chaînée, et sa sentinelle
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 7 — Structures séquentielles : listes, piles, files
Énoncé
- Définir le maillon d'une liste doublement chaînée, et écrire l'insertion et la suppression.
- Combien de cas particuliers faut-il traiter ? Les supprimer tous au moyen d'une sentinelle circulaire.
- Quelles opérations deviennent qui ne l'étaient pas ? Lesquelles restent ?
- Que coûte le double chaînage ?
Corrigé
1. Le maillon, et les opérations naïves.
typedef struct noeud_s {
int valeur;
struct noeud_s* prec;
struct noeud_s* suiv;
} noeud;
Écrite sans sentinelle, la suppression d'un nœud demande quatre tests : est-il le premier ? le dernier ? les deux ? aucun ? Chacun donne un code différent, et l'oubli d'un seul produit un pointeur pendant. C'est beaucoup de code pour une opération qui, en principe, se dit en deux affectations.
2. La sentinelle circulaire. On ajoute un maillon qui n'appartient pas à la liste et qui ne disparaît jamais. La liste vide, c'est la sentinelle bouclée sur elle-même.
/* Cree une liste vide : une sentinelle qui se pointe elle-meme.
INVARIANT : la sentinelle est toujours presente ; en partant d'elle par
suiv on revient sur elle ; m->suiv->prec == m pour TOUT maillon. */
noeud* creer(void) {
noeud* s = malloc(sizeof(noeud));
if (s == NULL) { return NULL; }
s->prec = s; s->suiv = s;
return s;
}
/* Insere x juste apres m. Precondition : m appartient a la liste. */
noeud* inserer_apres(noeud* m, int x) {
noeud* n = malloc(sizeof(noeud));
if (n == NULL) { return NULL; }
n->valeur = x;
n->prec = m; n->suiv = m->suiv; /* on relie le neuf aux voisins */
m->suiv->prec = n; m->suiv = n; /* puis les voisins au neuf */
return n;
}
/* Retire m de la liste. Precondition : m n'est PAS la sentinelle.
AUCUN cas particulier, AUCUN parcours. */
void supprimer(noeud* m) {
m->prec->suiv = m->suiv;
m->suiv->prec = m->prec;
free(m);
}
Mesuré, en parcourant dans les deux sens à chaque étape :
vide avant [] arriere []
apres 10, 20, 30 avant [10 20 30] arriere [30 20 10]
apres suppression de 20 avant [10 30] arriere [30 10]
apres suppression de 10 avant [30] arriere [30] (c'etait le premier)
apres suppression de 30 avant [] arriere []
La suppression du premier élément n'a demandé aucun traitement particulier : son prec est la sentinelle, qui existe toujours et dont le champ suiv est modifiable comme n'importe quel autre.
L'ordre des quatre affectations de inserer_apres n'est pas libre. Il faut écrire les champs du maillon neuf avant de casser m->suiv : si l'on commence par m->suiv = n, l'ancien successeur est perdu et n->suiv = m->suiv range n dans son propre champ. C'est le même piège que le suiv retenu avant free du cours : on relie avant de délier.
3. Ce qui change de complexité.
| Opération | Simplement chaînée | Doublement chaînée |
|---|---|---|
| Supprimer un maillon donné | ||
| Insérer avant un maillon donné | ||
| Parcourir en sens inverse | ||
| Chercher une valeur | ||
| Lire la position |
La première ligne est celle qui compte, et il faut lire l'énoncé avec soin : supprimer un maillon dont on a l'adresse. En simple chaînage, il faut retrouver son prédécesseur, donc reparcourir depuis la tête. Le second lien évite ce parcours, et c'est tout ce qu'il fait. En revanche il ne donne aucun accès direct : chercher reste linéaire, et lire la position aussi.
4. Le prix. Un pointeur de plus par maillon — sur une machine à adresses de bits et pour des entiers de , la structure passe de à octets, soit de mémoire en plus pour la même donnée. Il faut aussi maintenir deux invariants au lieu d'un, et un seul champ oublié rend la liste incohérente dans un sens et cohérente dans l'autre — c'est-à-dire d'un diagnostic très pénible.
Le critère est donc net : on double le chaînage quand on possède déjà l'adresse des maillons à retirer. C'est le cas dans un cache à éviction, dans une liste de tâches où chaque tâche connaît son maillon, et dans les listes d'adjacence de certains algorithmes de graphes (chapitre chap:graphes-avances). Ce n'est pas le cas quand on cherche avant de supprimer : le parcours domine, et le second lien ne sert à rien.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.