Structure
Avant de parler de l'implémentation, nous allons parler d'abstraction.
Structure
Une structure est une implémentation à partir du moment où toutes les fonctions nécessaires au bon fonctionnement ont été réalisées dans un langage de programmation. Sinon, on parle de Type Abstrait de Données (TAD), et c'est ici que nous définissons les actions pour modifier ou agir sur notre structure.
Étape 1 : Concevoir le cahier des charges (TAD)
Nous devons définir la structure en indiquant ce qu'elle fait et comment elle fonctionne. Pour cela, nous utiliserons une structure de liste abstraite. Il s'agit d'un ensemble de règles qui ne vont pas dépendre de l'implémentation.
L'intérêt de la liste
Une liste est un type abstrait de données contenant un ensemble d'éléments qui sont tous accessibles.
Nous nommons notre structure de liste avec le nom Liste.
Les Primitives
Une primitive est une fonction de base qui définit un concept simple permettant de modifier notre structure (ici la liste).
On distingue deux types de primitives :
Les primitives de base :
creerListeVide(): Liste - Crée et renvoie une liste videestVide(): Liste -> Booléen - Renvoie Vrai si la liste est vide, Faux sinonajouterTete(): Liste, Élément -> Liste - Modifie la liste en ajoutant un élément en tête de listeretirerTete(): Liste -> Liste, Élément - Supprime l'élément de tête et renvoie sa valeurajouterQueue(): Liste, Élément -> Liste - Modifie la liste en ajoutant un élément en queue de listeretirerQueue(): Liste -> Liste, Élément - Supprime l'élément de queue et renvoie sa valeurobtenirTete(): Liste -> Élément - Renvoie la valeur de l'élément de têteobtenirQueue(): Liste -> Élément - Renvoie la valeur de l'élément de queue
Les primitives auxiliaires :
inserer(): Liste, Élément, Entier -> Liste - Insère l'élément à la position indiquée dans la listesupprimer(): Liste, Entier -> Liste - Supprime l'élément situé à la position indiquée dans la liste
Si on suppose cette structure implémentée en Python, voici ce que cela donnerait :
l = Liste()
l.estVide() # True
l.ajouterTete(3) # 3
l.ajouterTete(8) # 8 3
l.ajouterQueue(9) # 8 3 9
l.estVide() # False
l.retirerTete() # 3 9 - Retourne 8
l.ajouterTete(7) # 7 3 9
l.retirerQueue() # 7 3 - Retourne 9
l.estVide() # False
l.ajouterTete(4) # 4 7 3
l.retirerQueue() # 4 7 - Retourne 3
l.estVide() # False
Étape 2 : Proposer une implémentation
Liste Chaînée (simple)
Une Liste Chaînée (Simple), est une implémentation d'une liste dans laquelle :
- Tous les éléments sont appelés des Cellules et se composent de deux choses :
- une valeur
- un lien, en fait un pointeur / référence, vers l'élément suivant / successeur
- On suppose également connue la référence vers la première Cellule appelée Tête (et seulement cette référence)
Taquet vers le haut
Le symbole ⊥ appelé taquet vers le haut permet de représenter la fin de la liste (la queue) et indique donc qu'il n'y a pas de cellule suivante après le dernier élément la queue.
En Python, on utilise souvent None pour implémenter ce concept.
Variantes des listes chaînées
Il existe plusieurs variantes de listes chaînées, notamment deux que nous aurons l'occasion d'étudier :
- La liste cyclique où le dernier élément est lié au premier
- La liste doublement chaînée où chaque élément possède un lien vers l'élément précédent en plus de l'élément suivant
Exo : Représenter ces deux variantes avec un schéma sur la liste [1, 2, 3]
Homogénéité
Dans le cas où tous les éléments de la liste sont du même type, on parle de liste homogène. Il est recommandé d'avoir des listes homogènes, même si Python autorise l'usage de listes non homogènes.
Implémentation par une classe Cellule
La cellule va nous permettre de représenter nos différents chaînons pour notre liste.
class Cellule:
"""Représente un élément de notre liste"""
def __init__(self, v, s=None):
"""Crée un élément d'une liste"""
self.valeur = v
self.suivante = s
Chaque objet de la classe Cellule contient deux attributs :
valeurqui représente la valeur de l'élément de notre liste ;suivantequi représente l'élément suivant dans la liste ouNonele cas échéant.
La variable lst ci-dessous correspond donc à une liste contenant les valeurs [1, 2, 3].
Exercice : Implémentation d'une Liste Chaînée par une classe Cellule
- Implémenter une classe
Celluledisposant :- d'attributs :
- un attribut
valeurcontenant une valeur de la liste (par exemple un entier) - un pointeur/attribut
suivantevers la Cellule suivante (initialisé àNonepour une Cellule n'ayant pas de successeur)
- un attribut
- de méthodes :
- un getter
get_valeur()qui renvoie la valeur de la Cellule courante - un getter
get_suivante()qui renvoie le pointeur vers la Cellule suivante - un setter
set_suivante()qui modifie le pointeur vers la Cellule suivante
- un getter
- d'attributs :
- Implémenter une Liste Chaînée par cette classe Cellule :
On peut alors implémenter une Liste Chaînée (Simple)4 (tête) --> 2 --> 9 --> 5 (queue)par les instructions suivantes :# Version 1 : Créer chaque Cellule puis les relier entre-elles cell1 = Cellule(4) cell2 = Cellule(2) cell3 = Cellule(9) cell4 = Cellule(5) cell1.set_suivante(cell2) cell2.set_suivante(cell3) cell3.set_suivante(cell4) liste_finale = cell1 # Version 2 : Créer les Cellules dans l'ordre décroissant cell4 = Cellule(5) cell3 = Cellule(9) cell3.set_suivante(cell4) cell2 = Cellule(2) cell2.set_suivante(cell3) cell1 = Cellule(4) cell1.set_suivante(cell2) liste_finale = cell1 # Version 3 : En utilisant le constructeur liste_finale = Cellule(4, Cellule(2, Cellule(9, Cellule(5))))
Une Liste Chaînée (Simple) peut donc être implémentée par:
- soit la valeur
None - soit un objet de classe
Cellulecontenant:- un attribut
valeurcontenant la valeur de la Cellule - un attribut
suivanterenvoyant vers une Liste (Simplement) Chaînée
- un attribut
Écrire le code de la méthode __len__ qui calcule et affiche la longueur de la liste courante.
Écrire le code de la méthode __getitem__ qui renvoie la valeur de la i-ème cellule de la liste courante.
Implémentation par une classe Liste
Nous ne pouvons pas conserver notre liste comme une Cellule. En effet, on veut pouvoir conserver un accès vers la tête
et (pourquoi pas) vers la queue de notre liste. Pour cela, nous allons créer une classe Liste qui sera similaire à
notre TAD (Type Abstrait de Données).
class Liste:
def __init__(self):
"""Crée une liste vide (fonction abstraite creerListeVide())"""
self.tete = None
Quel est l'intérêt d'une telle implémentation ? Elle cache la représentation de la structure à l'utilisateur : ainsi,
en utilisant la classe Liste, l'utilisateur n'a plus à utiliser la classe Cellule.
Exercice : Implémentation d'une Liste Chaînée par une classe Liste
On va utiliser la classe Cellule précédente (qu'il faudra peut-être adapter) pour implémenter une classe Liste.
Implémenter une classe Liste, disposant :
- de 1 attribut :
- un attribut
tetecontenant la référence vers laCelluleen tête ouNonesi la liste est vide
- un attribut
- des méthodes suivantes énoncées en TAD :
creerListeVide(): Liste - Crée et renvoie une liste videestVide(): Liste -> Booléen - Renvoie Vrai si la liste est vide, Faux sinonajouterTete(): Liste, Élément -> Liste - Modifie la liste en ajoutant un élément en tête de listeretirerTete(): Liste -> Liste, Élément - Supprime l'élément de tête et renvoie sa valeurajouterQueue(): Liste, Élément -> Liste - Modifie la liste en ajoutant un élément en queue de listeretirerQueue(): Liste -> Liste, Élément - Supprime l'élément de queue et renvoie sa valeur
Étape 3 : Améliorer son implémentation
On remarque quelques problèmes sur l'implémentation précédente :
- L'ajout ou la suppression d'un élément en queue est long, car il faut parcourir la liste en entier
- De même pour le calcul de la longueur
Listes Chaînées (Doubles)
Une Liste Chaînée Double (comprendre à Double Extrémité) est une Liste Chaînée (Simple) contenant une référence :
- vers le premier élément (la tête) de la liste (comme d'habitude)
- vers le dernier élément (la queue) de la liste
class Liste:
def __init__(self):
"""Crée une liste vide (fonction abstraite creerListeVide())"""
self.tete = None
self.queue = None



