Les listes chainées ⛓️⚓︎
Crédits
@crédit de la page Pierre-Alain Sallard sous licence CC BY-SA
Attention au vocabulaire
Ne pas confondre la structure de données abstraites Liste chainée (ou simplement liste dans certains manuels) avec le type list
de Python.
Le type list
de Python s'apparente à une implémentation de la structure de tableau dynamique (hors-programme).
1. Notion de liste chainée⚓︎
La structure liste chainée fait partie, comme les files (d'attente) et les piles, des structures de données linéaires : les données sont "alignées" les unes après les autres.
Ce qui caractérise une liste chainée, ce n'est pas le mode de gestion des entrées et sorties des données (FIFO pour les files, LIFO pour les piles) mais c'est qu'elle offre un moyen de passer d'une donnée à la suivante.
L'idée de base de la notion de structure de liste chainée est :
- on forme une "chaine" avec des "maillons" ;
- un maillon de la chaine contient
- la donnée elle-même
- et un lien ou "pointeur" vers le maillon suivant ;
- et on "tient" la chaine par son premier maillon.
Exemple : liste chainée des rois de France
La dynastie des Bourbons, rois de France qui ont régné de 1589 à 1792, peut être représentée par la liste chainée suivante :
Définition récursive d'une liste en informatique
L'idée de base exposée plus haut laisse entrevoir qu'une liste, c'est :
- soit une liste vide,
- soit un élément suivi d'une autre liste (éventuellement vide).
On est donc devant une définition récursive d'une liste !
Exercice : utiliser la définition récursive d'une liste chainée
On donne la construction suivante :
- L est la liste dont le maillon vaut "Alice" et dont la liste suivante est vide ;
- M est la liste dont le maillon vaut "Bob" et dont la liste suivante est L ;
- N est la liste dont le maillon vaut "Gaston" et dont la liste suivante est M ;
- P est la liste dont le maillon vaut "Jeanne" et dont la liste suivante est N.
Dessiner la liste chainée P.
Solution
La liste chainée P est "Jeanne" → "Gaston" → "Bob" → "Alice" → ⟂.
Remarque : le symbole "⟂" représente une liste vide, ce qui marque la fin de la liste chainée.
Ceci illustre bien le principe : on "tient" la chaine par son premier maillon.
2. Interface d'une liste chainée⚓︎
L'interface minimale d'une liste chainée comporte les opérations (les primitives) suivantes :
Fonction | Description |
---|---|
créer_liste_vide() |
renvoyer une nouvelle liste chainée vide |
inserer_en_tete(lst, donnée) |
ajouter la valeur donnée en tête de la liste chainée lst |
tete(lst) ou head(lst) |
renvoyer la valeur du maillon qui est en tête de la liste chainée lst |
queue(lst) ou tail(lst) |
renvoyer la liste chainée lst sans son premier maillon |
est_vide(lst) |
renvoyer True si lst est vide et False sinon |
Exemple d'utilisation avec la liste chainée des rois de France
Si on note Bourbons
la liste chainée de l'exemple précédent, alors :
tête(Bourbons)
renvoie la valeur"Henri IV"
queue(Bourbons)
renvoie la liste chainée "Louis XIII" → "Louis XIV" → "Louis XV" → "Louis XVI" → ⟂, c'est-à-dire tout ce qui vient après la "tête" de la listeBourbons
.
3. Longueur d'une liste chainée⚓︎
Supposez que l'on vous demande la longueur d'une liste chainée maListeChainée
qui a une queue (tail(maListeChainée)
) de longueur 7 : vous répondrez sans hésitation que la longueur vaut 1+7= 8.
Cette "structure imbriquée", cette disposition en "poupée russe" d'une liste chainée permet de définir une fonction récursive de calcul de longueur d'une liste chainée :
FONCTION LongueurListeChainée(lst)
SI est_vide(lst) ALORS
RENVOYER 0 # cas de base
SINON
RENVOYER 1 + LongueurListeChainée(tail(lst))
En exercice, vous devrez coder en Python cette fonction pour chacun des deux modes de représentation (pour chaque implémentation) d'une liste chainée que l'on va proposer ci-dessous.
4. Implémentation d'une liste chainée : version impérative, à l'aide de tuples⚓︎
On peut choisir de représenter un maillon par un couple (type tuple
) de la forme : maillon = (valeur, maillon_suivant)
.
Quand il n'y a pas de maillon suivant, on indique le couple vide ()
à la place.
La variable qui désigne la liste chainée est alors simplement celle du premier maillon : souvenez-vous de l'idée de base "on tient toute la chaine grâce au premier maillon" !
Une implémentation de la liste chainée des rois de France
Avec cette implémentation d'une liste chainée sous forme de couples, on pourra donc écrire :
roi5 = ("Louis XVI", ()) # on commence par la fin de la liste chainée
roi4 = ("Louis XV", roi5)
roi3 = ("Louis XIV", roi4)
roi2 = ("Louis XIII", roi3)
roi1 = ("Henri IV", roi2)
Bourbons = roi1 # on tient la liste chainée par son premier maillon
ou bien de façon plus condensée, mais moins lisible :
Bourbons = ('Henri IV', ('Louis XIII', ('Louis XIV', ('Louis XV', ('Louis XVI', ())))))
Voici le contenu visuel du tuple Bourbons
(le 0
indique le contenu de la case d'indice 0 du tuple et le 1
le contenu de la case d'indice 1).
Avec ce choix d'implémentation d'une liste chainée à l'aide de tuples, voici des codes possibles pour les fonctions creer_liste_vide()
, inserer_en_tete(lst, donnée)
, head(lst)
, tail(lst)
et est_vide(lst)
spécifiées par l'interface :
class Liste :
def __init__(self) :
lst = () # tuple vide
def inserer_en_tete(self, donnée ) :
return (donnée, lst) # c'est le nouveau premier maillon de la chaine
def head( self ) :
return lst[0] # c'est ce qu'il y a en position 0 du couple L
def tail( self ) :
return lst[1] # c'est ce qu'il y a en position 1 du couple L
def est_vide( self ) :
return len( lst ) == 0
Une façon d'utiliser cette implémentation est alors :
# exemple d'utilisation (a reprendre POO)
Bourbons = ('Henri IV', ('Louis XIII', ('Louis XIV', ('Louis XV', ('Louis XVI', ())))))
roi = head(Bourbons)
print("La tête de la liste chaine : ", roi)
queLesLouis = tail(Bourbons)
print("Après la tête de la liste chainée, il y a ", queLesLouis)
Bourbons = inserer_en_tete(Bourbons, "Henri III")
print("On a rajouté un roi en tête de la chaine : ", Bourbons)
Exercice : une chaine alimentaire
On veut créer en Python la liste chainée qui représente la chaine alimentaire suivante :
1. On pourrait simplement saisir chaineAlimentaire = ("corn", ("mouse",("snake",("owl",()))))
mais, dans un but pédagogique, on vous demande de créer cette liste en utilisant les fonctions creer_liste_vide()
et inserer_en_tete(L, donnée)
.
2. Quelle est la valeur renvoyée par l'appel de la fonction head(tail(chaineAlimentaire))
?
Solution
-
Réponse :
🐍 Script PythonchaineAlimentaire = creer_liste_vide() inserer_en_tete(chaineAlimentaire, "owl") inserer_en_tete(chaineAlimentaire, "snake") inserer_en_tete(chaineAlimentaire, "mouse") inserer_en_tete(chaineAlimentaire, "corn")
-
Cela renvoie
mouse
.
5. Implémentation d'une liste chainée : version Programmation Orientée Objet⚓︎
Une autre façon d'implémenter une liste chainée est de créer un objet "Maillon", c'est-à-dire en Python de créer une classe Maillon qui a deux attributs :
- la donnée à stocker,
- et le maillon suivant, qui est lui-même un objet de la classe
Maillon
(éventuellement vide).
Un maillon vide est représenté par la valeur None
.
class Maillon :
def __init__(self, data = None, suivant = None) :
self.head= data
self.tail = suivant
On a mis
None
en valeur par défaut à la fois pour la donnée et pour le maillon suivant, ce qui permet de créer un maillon vide en écrivantmaillonVide = Maillon()
.
On peut noter que cette classe Maillon
n'a pas de méthodes dédiées.
Exemple avec la liste chainée des rois de France
On peut alors créer ("instancier") les objets suivants :
roi5 = Maillon("Louis XVI") # pas besoin de préciser le 2ème argument, c'est None par défaut
roi4 = Maillon("Louis XV", roi5)
roi3 = Maillon("Louis XIV", roi4)
roi2 = Maillon("Louis XIII", roi3)
roi1 = Maillon("Henri IV", roi2)
Bourbons = roi1 # on tient la liste par son premier maillon
Puis on peut utiliser ainsi la liste chainée :
class Maillon :
def __init__(self, data = None, suivant = None) :
self.head= data
self.tail = suivant
roi5 = Maillon("Louis XVI") # pas besoin de préciser le 2ème argument, c'est None par défaut
roi4 = Maillon("Louis XV", roi5)
roi3 = Maillon("Louis XIV", roi4)
roi2 = Maillon("Louis XIII", roi3)
roi1 = Maillon("Henri IV", roi2)
Bourbons = roi1 # on tient la liste par son premier maillon
# exemple d'utilisation
print("La tête de la liste chainée : ", Bourbons.head )
queLesLouis = Bourbons.tail
print("Après la tête de la liste chainée, on trouve ", queLesLouis.head )
Bourbons = Maillon("Henri III", Bourbons) # insertion en tête de la liste chainée
print("On rajoute un roi à notre chaine : en tête, on a maintenant ", Bourbons.head )
Remarque : implémentation simpliste
Le choix est fait ici de rester avec une version simple de l'implémentation en POO d'une liste chainée. L'interface d'une liste chainée n'est, à ce stade, pas complètement respectée : par exemple, la fonction est_vide
de l'interface n'a pas été écrite. Et les fonctions creer_liste_vide
et inserer_en_tete
n'ont pas été explicitées même si on peut faire la même chose en créant un objet de la classe Maillon
: par exemple l'instruction Bourbons = Maillon("Henri III", Bourbons)
a remplacé l'instruction Bourbons = inserer_en_tete(Bourbons, "Henri III")
.
On verra en exercice comment se conformer rigoureusement à l'interface demandée.
Exercice : la chaine alimentaire en version POO
Reprendre les questions de l'exercice "une chaine alimentaire" en utilisant cette fois l'implémentation en version POO.
Solution
On écrit le code suivant :
hibou = Maillon("owl")
serpent = Maillon("snake", hibou)
souris = Maillon("mouse", serpent)
chaineAlimentaire = Maillon("corn",souris)
6. Exercices et TP⚓︎
-
Généralités : télécharger le carnet Jupyter, à ouvrir sous Basthon.
-
Exercice type Bac :
- énoncé à faire d'abord sur feuille,
- puis codage dans un carnet Jupyter.
- en cas de blocage : éléments de correction ici (année 2022, sujet 5).