Rappel : Type de Données Abstrait (TDA)
Une structure de données ou type de données abstrait est un moyen d’organiser et de manipuler les données en mémoire. Un TDA est donc définit par :
- Son nom ;
- Sa spécification, c’est à dire la liste des manipulations/opérations que l’on peut ou pas effectuer. La spécification indique généralement la complexité de chacune des opérations prévues par le TDA.
Un TDA peut être implémenté de plusieurs façons différentes.
Qu’est-ce qu’une pile ?
Une pile est une structure de données abstraite dans laquelle les données sont organisées comme le seraient des assiettes dans une pile d’assiettes contenue dans une boite de profondeur quelconque mais étroite (ce qui empêche de manipuler les assiettes par le côté).
On peut donc seulement :
- Empiler une nouvelle assiette au-dessus des autres assiettes (ou d’aucune assiette si la pile est vide) ;
- Retirer l’assiette située tout en haut de la pile ;
- Regarder l’assiette située tout en haut de la pile ;
- Regarder si la pile est vide.
Spécification d’une Pile de valeurs de type T
- Créer une pile (type) :
- fonction :
creer_pile()
$\longrightarrow$ retourne une pile vide ; - complexité idéale : $O(0)$ (pas tout à fait vrai mais suffisant pour comprendre la suite).
- fonction :
- Empiler une valeur en haut de la pile :
- fonction :
empiler(p: Pile[T], valeur: T)
$\longrightarrow$None
; - complexité idéale : 1 opération élémentaire.
- fonction :
- Accéder à la valeur située tout en haut de la pile :
- fonction :
lire(p: Pile[T])
$\longrightarrow$ retourne la dernière valeur de la pile ; - Complexité idéale : 1 opération élémentaire.
- fonction :
- Retirer la dernière valeur de la pile :
- fonction :
depiler(p: Pile[T])
$\longrightarrow$T
; - complexité idéale : 1 opération élémentaire.
- fonction :
- Tester si une pile est vide :
- fonction :
est_vide(p: Pile[T])
$\longrightarrow$ retourne un booléen ; - complexité idéale : 1 opération élémentaire.
- fonction :
La spécification d’une pile montre qu’il n’existe aucune opération élémentaire pour :
- accéder à une donnée qui n’est pas sur le haut de la pile ;
- déterminer combien de données contient la pile.
- …
Exemple d’utilisation d’une pile
Tout navigateur possède un bouton permettant de retourner sur des pages que l’on a déjà consultées.
Expliquer quelle structure de données permet de réaliser cette opération simplement et décrire les opérations en jeu.
Implémentation d’une structure de Pile à l’aide d’une liste Python
L’idée est de définir le type Pile
à l’aide d’une classe ne possédant qu’un seul attribut nommé contenu
, qui référence une liste, puis de créer les méthodes de la spécification.
On peut, par la suite, utiliser les méthodes des listes de Python.
-
Définir une classe nommée
Pile
et faire en sorte qu’à l’initialisation de tout objet de typePile
, l’attributcontenu
référence une liste vide. -
Écrire le code de la méthode
est_vide
de la spécification. -
Écrire le code de la méthode
empiler
de la spécification. -
Écrire le code de la méthode
depiler
de la spécification.
Cette méthode doit lever une exception de typeIndexError
lorsqu’on essaie de dépiler une pile vide. -
Écrire le code de la méthode
lire
de la spécification.
Cette méthode doit lever une exception de typeIndexError
lorsqu’on essaie de lire une pile vide. -
Tester le code.
-
Vérifier que la complexité de chacune des méthodes est égale à la complexité annoncée dans la spécification.
append
et pop
, s’exécutent en temps constant. Tous les langages ne présentent cependant pas la structure de tableau dynamique et une autre implémentation doit alors être mise en œuvre.
Jeu de tests possible
|
|
Implémentation d’une structure de Pile à l’aide d’une liste chaînée
L’idée est de définir le type Pile
à l’aide d’une classe ne possédant qu’un seul attribut nommé contenu
, qui référence une liste chaînée, puis de créer les méthodes de la spécification.
-
Écrire le code de la classe
Cellule
, cellule d’une liste chaînée. -
Définir une classe nommée
Pile
et faire en sorte qu’à l’initialisation de tout objet de typePile
, l’attributcontenu
référence la liste vide. -
Écrire le code de la méthode
est_vide
de la spécification. -
Écrire le code de la méthode
empiler
de la spécification.
Attention à bien choisir la façon d’empiler la valeur. On peut passer d’une complexité en $O(1)$ à une complexité en $O(N)$ ! -
Écrire le code de la méthode
depiler
de la spécification.
Cette méthode doit lever une exception de typeIndexError
lorsqu’on essaie de dépiler une pile vide. -
Écrire le code de la méthode
lire
de la spécification.
Cette méthode doit lever une exception de typeIndexError
lorsqu’on essaie de lire une pile vide. -
Tester le code.
-
Vérifier que la complexité de chacune des méthodes est égale à la complexité annoncée dans la spécification.
Jeu de tests possible
|
|
À retenir
Exercices
Exercice 1
- Compléter le code de la classe
Pile
(version liste chaînée) en ajoutant les méthodesvider
ettaille
. - Évaluer approximativement le nombre d’opérations élémentaires de la structure
Pile
effectuées par la méthodetaille
.
Exercice 2
Pour améliorer la complexité de la méthode taille
on ajoute l’attribut taille
à la classe Pile
.
Reprendre le code de la classe Pile
en modifiant toutes les méthodes qui doivent l’être.
Solutions
Exercice 3
L’écriture polonaise inverse des expressions mathématiques place l’opérateur après ses opérandes. Cette notation ne nécessite ni l’utilisation des parenthèses ni la définition des règles de priorité des opérateurs. Ainsi l’expression l’expression $(1 + 2 \times 3) \times 4$ s’écrit $1\ 2\ 3\ \times\ +\ 4\ \times$.
La valeur d’une telle expression peut être calculée facilement en utilisant une pile pour stocker les résultats intermédiaires. Pour cela, on observer un à un les éléments de l’expression et on effectue les actions suivantes :
- si on voit un nombre, on le place sur la pile ;
- si on voit un opérateur binaire, on récupère les deux nombres au sommet de la pile, on leur applique l’opérateur, et on replace le résultat au sommet de la pile.
Si l’expression est bien écrite, il doit toujours y avoir deux nombres sur la pile lorsqu’un opérateur apparaît et à la fin du processus il ne doit rester qu’un seul nombre sur la pile. Ce nombre est le résultat.
L’opérateur unaire $-$ (l’opposé) devra être considéré comme un opérateur binaire dans cette implémentation : $-3$ sera donc écrit $0\ 3\ -$.
Écrire une fonction prenant en paramètre une chaîne de caractères représentant une expression en notation polonaise inverse composée d’additions et de multiplications de nombres entiers et renvoyant la valeur de cette expression. On supposera que les éléments de cette expression sont séparés par des espaces. Attention, cette fonction ne doit pas renvoyer de résultat si l’expression est mal écrite.