Implémentation par des listes chaînées
Listes chaînées : description¶
Introduction¶
On rappelle que l'interface du type Liste est la suivante :
| Fonction | Description |
|---|---|
creer_vide() |
Renvoie une liste vide |
est_vide(l) |
Renvoie True si et seulement si la liste est vide |
tete(l) |
Renvoie le premier élément de la liste \(l\) (l'élément dit de tête) |
queue(l) |
Renvoie la liste constituée de tous les éléments de \(l\) à l'exception du premier |
ajoute(l, e) |
Renvoie la liste constituée de l'élément \(e\), suivi des éléments de \(l\) |
affiche(l) |
Affiche la liste des éléments de \(l\) sur la sortie standard, séparés par le caractère -. |
La liste vide est représentée par x. |
On se propose de donner une implémentation de cette interface via un objet nommé liste chaînée. En python, on peut représenter les listes de la manière suivante : chaque élément de la liste est associé à un objet, que nous appelerons un "maillon" (faisant partie d'une "chaîne" de maillons) qui comporte deux attributs :
- un attribut
valeurqui stocke la valeur de l'élément considéré ; - un attribut
suivantqui permet de déterminer quel est le prochain maillon dans la chaîne.
On représente un maillon vide par la valeur spéciale None. Ainsi, on pourra instancier un objet m de type Maillon de trois façons distinctes :
m = None: le maillon est vide ;m = Maillon(v, None): le maillon est le seul dans sa chaîne ;m = Maillon(v, m2): le prochain maillon de la chaîne est le maillonm2(supposé ici non vide).
Une chaîne de maillons ainsi constituée représente bien une liste d'éléments. La classe Liste possède donc un seul attribut : head une référence vers le premier maillon de la chaîne.
class Maillon:bksl-nl """ Un maillon d'une liste chainée. """bksl-nl def py-undpy-undinitpy-undpy-und(self, v, s):bksl-nl """ int, Maillon -> None """bksl-nl self.valeur = vbksl-nl self.suivant = sbksl-nlbksl-nlclass Liste:bksl-nl """ Une liste d'éléments. """bksl-nl def py-undpy-undinitpy-undpy-und(self, m):bksl-nl """ Maillon -> None """bksl-nl self.head = mbksl-nlbksl-nl
À l'aide de l'objet Maillon, on peut représenter la liste \(l = (3, 8, 42, 1)\). Pour chaque élément \(e\) de la liste, on créé un maillon qui contient la valeur de l'élément et le lien vers le maillon suivant.
1 2 3 4 5 6 7 8 | |
Créer des listes de cette façon est très fastidieux, en raison de l'utilisation de multiples variables. On peut condenser le code de la manière à n'utiliser qu'une seule variable :
1 2 3 4 5 6 7 8 9 10 | |
3
<__main__.Maillon object at 0x7eb3b895c5c0>
8
Premier exemple¶
Écrire les instructions python permettant de stocker à l'aide d'objets de type Maillon, ou de la valeur spéciale None les listes \(l_1 = ()\), \(l_2 = (-1)\), \(l_3 = (5, 6, 9, -1)\), \(l_4 = (-5, 9, 13)\).
maillon4 = Maillon(1, None)bksl-nlmaillon3 = Maillon(42, maillon4)bksl-nlmaillon2 = Maillon(8, maillon3)bksl-nlmaillon1 = Maillon(3, maillon2)bksl-nlbksl-nlprint(maillon2.valeur)bksl-nlprint(maillon2.suivant)bksl-nlprint(maillon2.suivant.valeur)bksl-nlbksl-nlm = Maillon(1, None)bksl-nlm = Maillon(42, m)bksl-nlm = Maillon(8, m)bksl-nlm = Maillon(3, m)bksl-nl# encore plus court :bksl-nl# m = Maillon(3, Maillon(8, Maillon(42, Maillon(1, None))))bksl-nl bksl-nlprint(m.valeur)bksl-nlprint(m.suivant)bksl-nlprint(m.suivant.valeur)bksl-nlbksl-nl# À compléterbksl-nl# m1 = ...bksl-nl# m2 = ...bksl-nl# m3 = ...bksl-nl# m4 = ...bksl-nlbksl-nl
Implémentation de l'interface de base des listes¶
Créer une liste vide¶
Écrire une fonction creer_vide qui renvoie, avec les conventions de l'énoncé, un maillon vide.
def creerpy-undvide():bksl-nl """ () -> Maillonbksl-nl Renvoie un maillon vide """bksl-nl passbksl-nlbksl-nl
Tester si un maillon est vide¶
Écrire une fonction est_vide étant donné un maillon m renvoie True si et seulement si celui-ci est vide.
def estpy-undvide(m):bksl-nl """ Maillon -> boolbksl-nl Renvoie True si et seulement si le maillon m est le maillon vide """bksl-nl passbksl-nlbksl-nl
Élément de tête¶
Écrire une fonction tete qui étant donné un maillon non vide m, renvoie la valeur stockée dans ce maillon.
def tete(m):bksl-nl """ Maillon -> intbksl-nl m est non videbksl-nl Renvoie l'attribut valeur du maillon m """bksl-nl passbksl-nlbksl-nl
Élément de queue¶
Écrire une fonction queue qui étant donné un maillon non-vide m, renvoie le maillon suivant de la chaîne de maillons.
def queue(m):bksl-nl """ Maillon -> Maillonbksl-nl m est non videbksl-nl Renvoie le maillon suivant dans la chaine de maillon de tête m. """bksl-nl passbksl-nlbksl-nl
Selon vous, quel est l'effet de l'instruction a is b ?
Ajout d'un maillon¶
Écrire une fonction ajoute qui étant donné un maillon m et un élément e renvoie le maillon m' dont l'attribut valeur est e et dont l'attribut suivant est m.
def ajoute(m, e):bksl-nl """ Maillon, int -> Maillonbksl-nl Renvoie le maillon dont l'attribut valeur est e et l'attribut suivant est m """bksl-nl passbksl-nlbksl-nl
Affichage d'une liste chaînée¶
Pour afficher une liste chaînée, il faut parcourir tous les maillons la composant. Pour cela, deux approches sont possibles : une méthode récursive et méthode itérative.
Méthode récursive¶
Si le maillon est vide alors on affiche un symbole indiquant qu'il s'agit du maillon vide. Sinon, on affiche la valeur contenue dans le maillon, puis on affiche les maillons suivants de la chaîne.
def affichepy-undrec(m):bksl-nl """ Maillon -> Nonebksl-nl Affiche les valeurs de la chaîne dont le premier maillon est m. """bksl-nl if estpy-undvide(m):bksl-nl print("x")bksl-nl else:bksl-nl print(tete(m), end = " - ")bksl-nl affichepy-undrec(queue(m))bksl-nlbksl-nlaffichepy-undrec(m3)bksl-nlbksl-nl
5 - 6 - 9 - -1 - x
- Que se passe-t-il si on échange les lignes
7et8du code de la fonctionaffiche_rec? - À quoi sert l'argument nommé
endde la fonctionprint? Modifier le code de la fonction précédente afin que l'affichage effectué par l'instructionaffiche_rec(m3)soit :5 | 6 | 9 | -1 | x
Méthode itérative¶
On initialise une variable maillon_courant qui stocke le maillon courant du parcours de la liste. Tant que le maillon courant n'est pas vide (ce qui veut dire que l'on n'est pas encore au bout de la chaîne), on affiche la valeur du maillon courant puis le maillon courant devient le maillon suivant dans la chaîne. Lorsque l'on atteint la fin de la chaîne, on affiche un symbole indiquant qu'il s'agit du maillon vide.
def affichepy-undi(m):bksl-nl """ Maillon -> Nonebksl-nl Affiche les valeurs de la chaîne dont le premier maillon est m """bksl-nl maillonpy-undcourant = mbksl-nl while not estpy-undvide(maillonpy-undcourant):bksl-nl print(tete(maillonpy-undcourant), end=' - ')bksl-nl maillonpy-undcourant = queue(maillonpy-undcourant)bksl-nl print("x")bksl-nlbksl-nlaffichepy-undi(m3)bksl-nlaffiche = affichepy-undibksl-nlbksl-nl
On utilisera dans la suite simplement l'instruction affiche pour afficher une chaîne de maillons.
Parcours d'une liste chaînée¶
Longueur d'une chaîne¶
On appelle longueur d'une chaîne de maillons le nombres de maillons non vide qui la composent.
Écrire une fonction itérative longueur qui étant donné un maillon m, détermine la longueur de la chaîne de maillons dont le premier maillon est m.
def longueur(m):bksl-nl """ Maillon -> intbksl-nl Compte le nombre de maillons présents dans la chaîne dont le premier maillon est m """bksl-nl # version itérativebksl-nl passbksl-nlbksl-nl
Élément d'indice i¶
On dit qu'un entier \(i\) est compatible avec la longueur d'une liste l constituée de \(n\) éléments si \(i < n\).
Écrire une fonction itérative element qui étant donné un maillon m, et un entier i, supposé compatible avec la longueur de la chaîne, renvoie la valeur stockée dans le maillon d'indice \(i\) de la chaine dont le premier maillon est m.
def element(m, i):bksl-nl """ Maillon, int -> intbksl-nl m est non vide, 0 <= i < longueur(m)bksl-nl Renvoie l'élément d'indice i de la chaîne de maillons dont le premier est m """bksl-nl passbksl-nlbksl-nl
Remplacer l'élément d'indice i¶
Écrire une fonction itérative remplace qui étant donné un maillon m non vide, un entier i, supposé compatible avec la longueur de la chaîne, et un élément e, modifie la valeur du maillon d'indice i par e. Ainsi, après l'exécution de remplace(m, i, e) :
- le premier maillon de la chaîne est toujours le maillon
m; - l'attribut
valeurdu maillon d'indiceide la chaine a été remplacé pare; - les autres maillons de la chaîne sont inchangés.
def remplace(m, i, e):bksl-nl """ Maillon, int, int -> Nonebksl-nl m est non vide, 0 <= i < longueur(m)bksl-nl Remplace par e la valeur du i-ème maillon de la chaine commençant par m """bksl-nl passbksl-nlbksl-nl
-
Écrire une fonction récursive
remplace_rnm(récursive non mutable) qui étant donné un maillonmnon vide, un entieri, supposé compatible avec la longueur de la chaîne, et un élémente, renvoie le premier maillon de la chaîne où la valeur du maillon d'indiceia été remplacée pare, sans modifier de maillon.1 2 3 4 5 6
affiche(m4) affiche(remplace_rnm(m4, 0, 54)) affiche(m4) affiche(m3) affiche(remplace_rnm(m3, 2, 24)) affiche(m3)24 - 9 - 13 - x 54 - 9 - 13 - x 24 - 9 - 13 - x 5 - 6 - 42 - -1 - x 5 - 6 - 24 - -1 - x 5 - 6 - 42 - -1 - x -
Écrire une version itérative de la fonction
remplacequi ne modifie aucun maillon.
Ajouter un élément à l'indice i¶
Écrire une fonction itérative ajoute_position qui étant donné un maillon m, un entier i, supposé compatible avec la longueur de la chaîne, et un élément e, ajoute un maillon à la chaine de telle sorte que :
- le premier maillon de la chaîne soit toujours le maillon
m; - le maillon d'indice
ide la chaîne ait pour valeure; - l'ordre d'apparition des valeurs des autres maillons de la chaîne est inchangé.
def ajoutepy-undposition(m, e, i):bksl-nl """ Maillon, int, int -> Nonebksl-nl Ajoute l'élément e en i-ième position de la chaine dont le premier maillon est m """bksl-nl passbksl-nlbksl-nl
Pour cela, on parcourera tous les maillons de la chaîne jusqu'à ce que le maillon_courant ait pour indice i (insertion en milieu de liste) ou que le maillon suivant de la chaîne soit vide (insertion en fin de liste).
-
Dans le cas où on insère en milieu de liste : on créé alors un
nouveaumaillon que l'on insère entre lemaillon_courantet le maillon suivant de la chaîne comme sur le schéma ci-dessous.
On s'assure que les attributs
valeurdes maillons ont été correctement modifiés : l'attributvaleurdu maillon d'indiceidoit êtree, l'attribut valeur dunouveaumaillon doit être l'ancienne valeur du maillon d'indicei. -
Dans le cas où on insère en fin de liste (cela veut dire que
ivautlongueur(m)) : il suffit juste de créer un nouveau maillon et de le relier au dernier maillon de la liste.
-
Écrire une fonction récursive
ajoute_position_rnmqui étant donné un maillonmnon vide, un entieri, supposé compatible avec la longueur de la chaîne, et un élémente, renvoie le premier maillon de la chaîne où on a inséré à l'indiceiun maillon de valeuresans modifier de maillon.1 2 3 4 5
affiche(m4) affiche(ajoute_position_rnm(m4, 0, 123)) affiche(m4) affiche(ajoute_position_rnm(m4, 3, 321)) affiche(m4)24 - 9 - 13 - x 123 - 24 - 9 - 13 - x 24 - 9 - 13 - x 24 - 9 - 13 - 321 - x 24 - 9 - 13 - x -
Écrire une version itérative de la fonction
ajoute_positionqui ne modifie aucun maillon.