Bonjour.
Dans notre leçon précédente, nous avons établi un principe fondamental : la transformation d'un algorithme récursif non terminal en un algorithme itératif requiert la simulation de la pile d'appels à l'aide d'une structure de données explicite. Cette prise de conscience nous amène naturellement à l'étude de ces structures.
Aujourd'hui, nous allons construire de nos propres mains l'une des structures de données les plus fondamentales en informatique : la liste chaînée. L'objectif est d'implémenter une liste chaînée simple avec les opérations d'insertion, de suppression et de recherche. La maîtrise de cette structure est indispensable. Non seulement elle est un sujet récurrent, mais elle constitue également le socle conceptuel sur lequel reposent des structures plus complexes comme les piles, les files, et même les arbres et les graphes que nous aborderons plus tard.
1. Pourquoi utiliser une liste chaînée ?
Avant de nous lancer dans l'implémentation, il est crucial de comprendre la raison d'être des listes chaînées. En Python, nous disposons des listes (list), qui sont en réalité des tableaux dynamiques. Pourquoi s'encombrer d'une nouvelle structure ? La vidéo suivante expose de manière très claire le compromis fondamental entre tableaux et listes chaînées.
Linked List - Data Structures & Algorithms Tutorials in Python #4
Cette vidéo de la chaîne codebasics met en évidence les problèmes de performance des tableaux (listes Python) lors des insertions en début de structure, justifiant ainsi l'existence des listes chaînées.
Regardez les sections de 00:19 à 05:33, puis de 06:37 à 07:58. Concentrez-vous sur les points suivants : Le coût des opérations d'insertion dans un tableau (décalage des éléments). Le mécanisme de réallocation mémoire lorsque la capacité d'un tableau dynamique est dépassée. Comment la structure à base de pointeurs de la liste chaînée résout ces problèmes pour l'insertion en début de liste. Le tableau comparatif des complexités (Big O) qui synthétise le compromis entre les deux structures.
En résumé, les tableaux offrent un accès en temps constant à n'importe quel élément par son indice, mais l'insertion ou la suppression d'un élément en début ou milieu de tableau a un coût linéaire , car elle nécessite de décaler tous les éléments suivants. Les listes chaînées, quant à elles, excellent pour les insertions/suppressions en tête () mais sacrifient l'accès direct, qui devient une opération en .
2. Anatomie d'une liste chaînée
Une liste chaînée est une séquence de nœuds (ou maillons). Chaque nœud est un objet simple contenant deux informations essentielles :
- La valeur (les données que l'on souhaite stocker).
- Un pointeur (une référence) vers le nœud suivant.
Le dernier nœud de la liste a un pointeur qui ne mène nulle part, classiquement une valeur None en Python. La liste entière est accessible via une seule référence : celle qui pointe vers son premier nœud, la tête (head).

Pour implémenter cela en Python, nous utiliserons deux classes :
- Une classe
Nodepour représenter un maillon. - Une classe
LinkedListqui encapsulera la logique de la liste et ne contiendra qu'une référence vers la tête.
La vidéo suivante met en place ces deux classes de base.
Linked List - Data Structures in Python #1
Nous allons maintenant suivre le début de l'implémentation pratique avec cette vidéo de NeuralNine. Elle est très complète et nous guidera pas à pas.
Regardez de 01:15 à 06:15. Cette section introduit le concept, puis implémente la classe Node et l'initialisation de la classe LinkedList. Notez la simplicité de la classe Node : une valeur et un next initialisé à None.
Voici le squelette de notre code basé sur la vidéo :
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LinkedList:
def __init__(self):
self.head = None
# Nos méthodes d'insertion, suppression et recherche viendront ici.
3. Implémentation des opérations fondamentales
Nous allons maintenant implémenter les opérations requises. Pour chaque opération, nous décrirons d'abord la logique, puis vous visionnerez le segment vidéo correspondant pour voir le code prendre vie.
Insertion
L'insertion consiste à créer un nouveau nœud et à manipuler les pointeurs next pour l'intégrer dans la chaîne.
a) Insertion en tête de liste (prepend)
C'est l'opération la plus efficace d'une liste chaînée.
- Créez un nouveau nœud avec la valeur donnée.
- Faites pointer le
nextde ce nouveau nœud vers la tête actuelle de la liste. - Mettez à jour la tête de la liste pour qu'elle pointe vers ce nouveau nœud.

Cette opération est en car elle ne dépend pas de la taille de la liste.
Linked List - Data Structures in Python #1
Voyons l'implémentation de la méthode prepend.
Regardez de 12:01 à 13:56. Observez la simplicité et l'efficacité de cette méthode.
b) Insertion en fin de liste (append)
- Si la liste est vide, l'insertion en fin est identique à l'insertion en tête.
- Sinon, parcourez la liste depuis la tête jusqu'à atteindre le dernier nœud (celui dont le
nextestNone). - Créez un nouveau nœud.
- Faites pointer le
nextde ce dernier nœud vers le nouveau nœud.
Cette opération est en car dans le pire des cas, il faut parcourir toute la liste.
Linked List - Data Structures in Python #1
Passons à l'implémentation de la méthode append.
Regardez de 09:15 à 12:01. Portez attention à la boucle while qui permet de traverser la liste jusqu'au dernier élément.
c) Insertion à un indice spécifique (insert)
- Si l'indice est 0, c'est une insertion en tête.
- Sinon, parcourez la liste jusqu'au nœud situé à
index - 1(le prédécesseur). - Créez un nouveau nœud.
- Faites pointer le
nextdu nouveau nœud vers lenextdu prédécesseur. - Faites pointer le
nextdu prédécesseur vers le nouveau nœud.
Cette opération est en en raison du parcours.
Linked List - Data Structures in Python #1
L'insertion à un indice est une généralisation. Voyons son implémentation.
Regardez de 13:56 à 17:57. La gestion des cas limites (index 0, index hors limites) est particulièrement importante ici.
Recherche
La recherche dans une liste chaînée se fait toujours par parcours séquentiel depuis la tête.
a) Recherche par valeur (contains)
Parcourez la liste nœud par nœud. Si la valeur du nœud courant correspond à la valeur cherchée, retournez True. Si vous atteignez la fin de la liste sans la trouver, retournez False. La complexité est .
Linked List - Data Structures in Python #1
L'opération de recherche par valeur (contains) est un parcours simple.
Regardez de 17:57 à 20:12. Il s'agit d'une application directe du parcours de liste.
b) Recherche par indice (get)
Parcourez la liste en maintenant un compteur. Lorsque le compteur atteint l'indice désiré, retournez la valeur du nœud courant. Si la fin de la liste est atteinte avant, l'indice est invalide. La complexité est .
Linked List - Data Structures in Python #1
La recherche par indice (get) est similaire, mais se base sur un compteur.
Regardez de 28:42 à 30:23. Notez la gestion d'erreur pour les indices invalides.
Suppression
La suppression est l'opération la plus délicate, car elle requiert de modifier le pointeur next du nœud précédant celui que l'on souhaite supprimer.
a) Suppression par valeur (delete)
- Cas particulier : si le nœud à supprimer est la tête, faites simplement pointer la tête sur le nœud suivant.
- Sinon, parcourez la liste en gardant une référence sur le nœud courant et son successeur.
- Lorsque la valeur du successeur est celle à supprimer, "court-circuitez" la chaîne : faites pointer le
nextdu nœud courant vers lenextde son successeur (courant.next = courant.next.next).
En Python, le garbage collector se chargera de libérer la mémoire du nœud qui n'est plus référencé.
La complexité est .
Linked List - Data Structures in Python #1
La suppression par valeur met en lumière la nécessité de connaître le prédécesseur.
Regardez de 22:47 à 26:33. L'explication visuelle à 25:01 est excellente pour comprendre la redirection du pointeur. Notez le petit bug corrigé à 34:02, qui est une erreur classique (oublier de progresser dans la liste dans le cas else).
b) Suppression par indice (pop)
La logique est identique à la suppression par valeur, mais la condition d'arrêt est basée sur un compteur d'indice. Vous devez vous arrêter au nœud d'indice index - 1 pour modifier son pointeur next. La complexité est .
Linked List - Data Structures in Python #1
La suppression par indice applique la même logique de 'court-circuit'.
Regardez de 26:33 à 28:42. C'est une variation de l'algorithme de suppression précédent.
4. Code complet et test
Après avoir exploré chaque méthode séparément, il est temps de voir la classe complète en action.
Linked List - Data Structures in Python #1
Pour finir, voyons un exemple d'utilisation de notre classe LinkedList pour vérifier que toutes les méthodes fonctionnent comme prévu.
Regardez de 32:44 à 35:11. Cette partie teste successivement append, prepend, insert, delete, pop et get.
Je vous encourage vivement à recopier le code, à le faire fonctionner sur votre machine et à le tester avec vos propres exemples. C'est en forgeant qu'on devient forgeron.
Conclusion
Aujourd'hui, nous avons non seulement compris l'intérêt des listes chaînées, mais surtout, nous les avons implémentées de A à Z en Python. C'est une étape majeure.
Points clés à retenir :
- Une liste chaînée est une structure dynamique composée de nœuds, chacun contenant une valeur et un pointeur
next. - Le principal compromis par rapport aux tableaux est un accès séquentiel en échange d'une insertion/suppression en tête en .
- Les opérations d'insertion et de suppression se résument à une manipulation précise des pointeurs
next. - Pour supprimer un nœud (ou en insérer un après un nœud donné), il est impératif d'avoir une référence à son prédécesseur.
Dans notre prochaine leçon, nous reviendrons sur les tableaux dynamiques. Nous définirons la notion de complexité amortie et l'appliquerons à l'analyse des opérations sur un tableau dynamique. Cela nous permettra de comprendre formellement pourquoi, en moyenne, l'ajout d'éléments à une liste Python est considéré comme efficace, malgré les coûteuses opérations de réallocation que nous avons évoquées au début de cette leçon.