Stack Tutorial FR
Stack Tutorial FR
• Fonction "Annuler" (Undo) : Dans un éditeur de texte, chaque action est empilée. Quand vous appuyez sur Ctrl+Z, la
dernière action est dépilée et annulée.
• Navigation web : Le bouton "Retour" de votre navigateur utilise une pile. Chaque page visitée est empilée, et "Retour" dépile
la dernière page.
• Évaluation d'expressions mathématiques : 3 + 5 * 2 nécessite de mémoriser temporairement des valeurs.
• Appels de fonctions : Quand une fonction appelle une autre fonction, le système doit "se souvenir" où retourner.
Définition mathématique :
Soit S une pile. On peut la représenter comme une séquence ordonnée :
S = ⟨e1 , e2 , e3 , ..., en ⟩
où :
Principe LIFO :
Opérations possibles :
→ PUSH(8) : Ajouter 8 au sommet
→ POP() : Retirer 5 du sommet
int main() {
int valeur = calculer(5);
printf("%d", valeur);
return 0;
}
Analyse Mathématique
Soit n la profondeur d'appels de fonctions. La pile d'appels nécessite :
Exemple d'exécution :
Expression: ( ( a + b ) * ( c + d ) )
PUSH(sommet_initial)
Tant que pile non vide :
sommet = POP()
visiter(sommet)
Pour chaque voisin de sommet :
PUSH(voisin)
Représentation mathématique :
États possibles :
Après PUSH(5) :
sommet = 0
┌─────────┐
│ 5 │ ← sommet
└─────────┘
Après PUSH(3) :
sommet = 1
┌─────────┐
│ 3 │ ← sommet
├─────────┤
│ 5 │
└─────────┘
Pseudocode d'Initialisation
ALGORITHME InitialiserPile()
ENTRÉE : Rien
SORTIE : Une pile vide P
DÉBUT
Créer une structure Pile P
[Link] ← -1 // ou NULL pour chaînée
[Link] ← 0
Retourner P
FIN
Complexité :
Condition mathématique :
PilePleine(P ) ⟺ P .sommet = P .capacite − 1
Visualisation
sommet = 4 → ┌─────────┐
│ 8 │ ← Position 4 (PLEINE!)
├─────────┤
│ 2 │ ← Position 3
├─────────┤
│ 7 │ ← Position 2
├─────────┤
│ 1 │ ← Position 1
├─────────┤
│ 3 │ ← Position 0
└─────────┘
Pseudocode
ALGORITHME PilePleine(P)
ENTRÉE : Pile P
SORTIE : VRAI si pleine, FAUX sinon
DÉBUT
SI [Link] = [Link] - 1 ALORS
Retourner VRAI
SINON
Retourner FAUX
FIN SI
FIN
Complexité :
Important : Pour une pile chaînée, cette opération n'a pas de sens (capacité limitée seulement par la mémoire disponible).
Pseudocode
ALGORITHME PileVide(P)
ENTRÉE : Pile P
SORTIE : VRAI si vide, FAUX sinon
DÉBUT
SI [Link] = -1 ALORS // ou [Link] = NULL
Retourner VRAI
SINON
Retourner FAUX
FIN SI
FIN
Complexité :
• Temps : O(1)
• Espace : O(1)
Invariant de sécurité :
Transformation mathématique :
Étapes Logiques
1. Vérifier que la pile n'est pas pleine (débordement)
2. Incrémenter le pointeur de sommet
3. Insérer l'élément à la nouvelle position
4. Mettre à jour la taille (optionnel)
Étape 1 : PUSH(9)
Vérification : pile non pleine ✓
Étape 3 : Insérer 9
┌─────────┐
│ 9 │ ← nouveau sommet
├─────────┤
│ 7 │
├─────────┤
│ 3 │
└─────────┘
État final :
sommet = 2, taille = 3
Pseudocode Détaillé
DÉBUT
// Étape 1 : Vérification
SI PilePleine(P) ALORS
Afficher "ERREUR : Débordement de pile"
Retourner FAUX
FIN SI
// Étape 3 : Insertion
[Link][[Link]] ← element
Retourner VRAI
FIN
Analyse de Complexité
Complexité temporelle :
T (n) = O(1)
Justification :
• Vérification pleine : O(1)
• Incrémentation : O(1)
• Affectation : O(1)
• Total : O(1)
Complexité spatiale :
S(n) = O(1)
Invariant de PUSH :
Transformation mathématique :
Étapes Logiques
1. Vérifier que la pile n'est pas vide (sous-dépassement)
2. Récupérer l'élément au sommet
3. Décrémenter le pointeur de sommet
4. Retourner l'élément récupéré
Étape 1 : POP()
Vérification : pile non vide ✓
État final :
┌─────────┐
│ 7 │ ← nouveau sommet
├─────────┤
│ 3 │
└─────────┘
Retourne : 9
Mémoire physique :
┌─────────┐
│ 9 │ ← Toujours là, mais "oublié"
├─────────┤
│ 7 │ ← sommet logique
├─────────┤
│ 3 │
└─────────┘
Le prochain PUSH écrasera cette valeur. C'est une suppression logique, pas physique.
Pseudocode Détaillé
ALGORITHME Pop(P)
ENTRÉE : Pile P
SORTIE : Élément retiré ou erreur
DÉBUT
// Étape 1 : Vérification
SI PileVide(P) ALORS
Afficher "ERREUR : Sous-dépassement de pile"
Retourner NULL // ou valeur sentinelle
FIN SI
// Étape 2 : Récupération
element ← [Link][[Link]]
// Étape 3 : Décrémenter
[Link] ← [Link] - 1
Retourner element
FIN
Analyse de Complexité
Complexité temporelle :
T (n) = O(1)
Complexité spatiale :
S(n) = O(1)
Invariant de POP :
Opération mathématique :
Visualisation
État avant Sommet() :
sommet = 2
┌─────────┐
│ 9 │ ← sommet
├─────────┤
│ 7 │
├─────────┤
│ 3 │
└─────────┘
Pseudocode
ALGORITHME Sommet(P)
ENTRÉE : Pile P
SORTIE : Élément au sommet ou erreur
DÉBUT
// Vérification
SI PileVide(P) ALORS
Afficher "ERREUR : Pile vide"
Retourner NULL
FIN SI
Complexité :
• Temps : O(1)
• Espace : O(1)
Invariant :
Structure en Mémoire
Mémoire physique :
Structure Pile
Inconvénients
1. Taille fixe : Capacité définie à l'avance
2. Gaspillage mémoire : Si la pile n'est jamais pleine
3. Débordement : Impossible d'ajouter au-delà de la capacité
Formules Mathématiques
Espace mémoire requis :
Taux d'utilisation :
taille
Utilisation = × 100%
capacite
4.2 Représentation Chaînée (Liste Chaînée)
Principe
La pile est implémentée avec une liste chaînée où :
• Chaque élément est un nœud contenant une valeur et un pointeur vers le nœud suivant
• Le sommet pointe vers le premier nœud
• Pas de limite de capacité (sauf la mémoire totale)
Structure en Mémoire
Mémoire dispersée :
sommet → ┌──────────────┐
│ valeur: 9 │ Adresse: 0x2050
│ suivant: ────┼─→ ┌──────────────┐
└──────────────┘ │ valeur: 7 │ Adresse: 0x3020
│ suivant: ────┼─→ ┌──────────────┐
└──────────────┘ │ valeur: 3 │ Adresse: 0x1080
│ suivant: NULL│
└──────────────┘
suivant NULL
suivant Nœud 3
donnee 9
Inconvénients
1. Overhead mémoire : Chaque nœud stocke un pointeur supplémentaire
2. Allocation dynamique : Coût de malloc/free à chaque opération
3. Fragmentation : Données dispersées en mémoire
4. Cache-unfriendly : Moins efficace pour le CPU
Formules Mathématiques
Espace mémoire par élément :
Overhead mémoire :
sizeof(pointeur)
Overhead = × 100%
sizeof(donnee) + sizeof(pointeur)
8
Overhead = × 100% = 66.67%
4+8
5. Implémentation en Langage C
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
Explication :
/**
* Crée et initialise une pile vide
* Complexité : O(1)
*/
void initialiser_pile(Pile* p) {
p->sommet = -1; // Pile vide
p->capacite = CAPACITE_MAX; // Taille maximale
Logique :
/**
* Vérifie si la pile est vide
* Complexité : O(1)
*/
bool est_vide(Pile* p) {
return p->sommet == -1;
}
/**
* Vérifie si la pile est pleine
* Complexité : O(1)
*/
bool est_pleine(Pile* p) {
return p->sommet == p->capacite - 1;
}
Logique :
return true;
}
Logique détaillée :
Trace d'exécution :
Pile p;
initialiser_pile(&p);
push(&p, 3); // sommet=0, elements[0]=3
push(&p, 7); // sommet=1, elements[1]=7
push(&p, 9); // sommet=2, elements[2]=9
return true;
}
Logique détaillée :
Note importante : On ne supprime pas physiquement la valeur ! Elle sera écrasée au prochain push.
/**
* Consulte l'élément au sommet sans le retirer
* Complexité : O(1)
*
* @param p Pointeur vers la pile
* @param valeur Pointeur pour stocker la valeur consultée
* @return true si succès, false si pile vide
*/
bool sommet(Pile* p, int* valeur) {
if (est_vide(p)) {
printf("ERREUR : Pile vide\n");
return false;
}
return true;
}
Affichage (Utilitaire)
/**
* Affiche le contenu de la pile (pour débogage)
* Complexité : O(n) où n est le nombre d'éléments
*/
void afficher_pile(Pile* p) {
if (est_vide(p)) {
printf("Pile vide\n");
return;
}
// Test 1 : Empilage
printf("Test 1 : Empilage\n");
push(&ma_pile, 10);
push(&ma_pile, 20);
push(&ma_pile, 30);
afficher_pile(&ma_pile); // 30 20 10
// Test 3 : Dépilage
printf("\nTest 3 : Dépilage\n");
if (pop(&ma_pile, &valeur)) {
printf("Valeur dépilée : %d\n", valeur);
}
afficher_pile(&ma_pile); // 20 10
// Test 5 : Sous-dépassement
printf("\nTest 5 : Tentative de dépilage sur pile vide\n");
pop(&ma_pile, &valeur); // Affiche erreur
// Test 6 : Débordement
printf("\nTest 6 : Test de débordement\n");
for (int i = 0; i < CAPACITE_MAX + 1; i++) {
if (!push(&ma_pile, i)) {
printf("Débordement détecté après %d éléments\n", i);
break;
}
}
return 0;
}
Sortie attendue :
=== Test de la Pile Contiguë ===
Test 1 : Empilage
Pile (sommet -> fond) : 30 20 10
Test 3 : Dépilage
Valeur dépilée : 30
Pile (sommet -> fond) : 20 10
Explication :
Initialisation
/**
* Initialise une pile chaînée vide
* Complexité : O(1)
*/
void initialiser_pile_chainee(PileChaînee* p) {
p->sommet = NULL; // Pile vide
p->taille = 0;
}
/**
* Vérifie si la pile chaînée est vide
* Complexité : O(1)
*/
bool est_vide_chainee(PileChaînee* p) {
return p->sommet == NULL;
}
Note : Pour une pile chaînée, il n'y a pas de test "est pleine" (capacité illimitée).
/**
* Ajoute un élément au sommet de la pile chaînée
* Complexité : O(1)
*
* @param p Pointeur vers la pile
* @param valeur Valeur à empiler
* @return true si succès, false si allocation échoue
*/
bool push_chainee(PileChaînee* p, int valeur) {
// Étape 1 : Allouer un nouveau nœud
Noeud* nouveau = (Noeud*)malloc(sizeof(Noeud));
if (nouveau == NULL) {
printf("ERREUR : Allocation mémoire échouée\n");
return false;
}
return true;
}
Logique détaillée :
Avant Push(9) :
sommet → [7] → [3] → NULL
Visualisation ASCII :
Avant :
sommet
↓
┌───┬──┐ ┌───┬──┐ ┌───┬────┐
│ 7 │ ●┼──→│ 3 │ ●┼──→│ ? │NULL│
└───┴──┘ └───┴──┘ └───┴────┘
Après Push(9) :
sommet
↓
┌───┬──┐ ┌───┬──┐ ┌───┬──┐ ┌───┬────┐
│ 9 │ ●┼──→│ 7 │ ●┼──→│ 3 │ ●┼──→│ ? │NULL│
└───┴──┘ └───┴──┘ └───┴──┘ └───┴────┘
return true;
}
Logique détaillée :
Avant Pop() :
sommet → [9] → [7] → [3] → NULL
↑
ancien_sommet
Après free :
sommet → [7] → [3] → NULL
Important : Il faut toujours libérer le nœud retiré pour éviter les fuites mémoire.
*valeur = p->sommet->donnee;
return true;
}
Affichage (Utilitaire)
/**
* Affiche le contenu de la pile chaînée
* Complexité : O(n)
*/
void afficher_pile_chainee(PileChaînee* p) {
if (est_vide_chainee(p)) {
printf("Pile vide\n");
return;
}
Libération de la Mémoire
/**
* Libère toute la mémoire de la pile
* Complexité : O(n)
* CRUCIAL pour éviter les fuites mémoire !
*/
void detruire_pile_chainee(PileChaînee* p) {
int valeur;
while (!est_vide_chainee(p)) {
pop_chainee(p, &valeur); // Libère chaque nœud
}
printf("Pile détruite, mémoire libérée\n");
}
// Test 1 : Empilage
printf("Test 1 : Empilage\n");
push_chainee(&ma_pile, 100);
push_chainee(&ma_pile, 200);
push_chainee(&ma_pile, 300);
afficher_pile_chainee(&ma_pile);
// Test 2 : Consultation
printf("\nTest 2 : Consultation\n");
int valeur;
if (sommet_chainee(&ma_pile, &valeur)) {
printf("Sommet : %d\n", valeur);
}
// Test 3 : Dépilage
printf("\nTest 3 : Dépilage\n");
while (pop_chainee(&ma_pile, &valeur)) {
printf("Dépilé : %d\n", valeur);
}
// Nettoyage
detruire_pile_chainee(&ma_pile);
return 0;
}
Sortie attendue :
=== Test de la Pile Chaînée ===
Test 1 : Empilage
Pile (sommet -> fond) : 300 200 100
Test 2 : Consultation
Sommet : 300
Test 3 : Dépilage
Dépilé : 300
Dépilé : 200
Dépilé : 100
Pile Contiguë :
Mcontiguë = capacitˊe × 4 + 12 octets (mˊetadonnˊees)
Pour n = 100 :
Mchaı̂nˊee = 12 × 100 + 16 = 1216 octets
Graphique de comparaison :
Choix de l'Implémentation
Utilisez Contiguë si :
Utilisez Chaînée si :
• Taille imprévisible
• Insertions/suppressions fréquentes
• Pas de limite de capacité souhaitée
• Partage de nœuds entre structures
Structure de la Classe
#include <iostream>
#include <stdexcept>
/**
* Classe représentant une pile contiguë
* Template permet de gérer différents types de données
*/
template <typename T>
class PileContigue {
private:
T* elements; // Tableau dynamique
int sommet; // Index du sommet
int capacite; // Capacité maximale
public:
// Constructeur
PileContigue(int cap = 100);
// Destructeur
~PileContigue();
// Méthodes principales
void push(const T& valeur);
T pop();
T top() const;
// Méthodes de vérification
bool estVide() const;
bool estPleine() const;
int taille() const;
// Affichage
void afficher() const;
};
Correspondance avec C :
Implémentation du Constructeur
/**
* Constructeur : initialise une pile avec capacité donnée
* Complexité : O(capacite) pour allocation
*/
template <typename T>
PileContigue<T>::PileContigue(int cap) : capacite(cap), sommet(-1) {
// Allocation dynamique
elements = new T[capacite];
std::cout << "Pile créée avec capacité " << capacite << std::endl;
}
Logique :
Implémentation du Destructeur
/**
* Destructeur : libère automatiquement la mémoire
* Complexité : O(1)
*/
template <typename T>
PileContigue<T>::~PileContigue() {
delete[] elements; // Libération du tableau
Avantage majeur : Le destructeur est appelé automatiquement à la fin de la portée, contrairement au C où on doit appeler free
manuellement.
void fonction() {
PileContigue<int> p(50); // Constructeur appelé
[Link](10);
[Link](20);
// ... code ...
} // Destructeur appelé automatiquement ici !
Méthodes de Vérification
/**
* Vérifie si la pile est vide
* Complexité : O(1)
*/
template <typename T>
bool PileContigue<T>::estVide() const {
return sommet == -1;
}
/**
* Vérifie si la pile est pleine
* Complexité : O(1)
*/
template <typename T>
bool PileContigue<T>::estPleine() const {
return sommet == capacite - 1;
}
/**
* Retourne le nombre d'éléments
* Complexité : O(1)
*/
template <typename T>
int PileContigue<T>::taille() const {
return sommet + 1;
}
Note : Le mot-clé const après la déclaration indique que ces méthodes ne modifient pas l'objet.
Méthode Push
/**
* Ajoute un élément au sommet
* Complexité : O(1)
* Lance une exception si pile pleine
*/
template <typename T>
void PileContigue<T>::push(const T& valeur) {
// Vérification débordement
if (estPleine()) {
throw std::overflow_error("Débordement de pile : capacité maximale atteinte");
}
// Incrémentation et insertion
sommet++;
elements[sommet] = valeur;
}
Différences avec C :
// C++ : Try-catch
try {
int val = [Link]();
} catch (const std::underflow_error& e) {
std::cout << "Erreur : " << [Link]() << std::endl;
}
Méthode Pop
/**
* Retire et retourne l'élément au sommet
* Complexité : O(1)
* Lance une exception si pile vide
*/
template <typename T>
T PileContigue<T>::pop() {
// Vérification sous-dépassement
if (estVide()) {
throw std::underflow_error("Sous-dépassement de pile : aucun élément");
}
// Récupération et décrémentation
T valeur = elements[sommet];
sommet--;
return valeur;
}
Méthode Top
/**
* Consulte l'élément au sommet sans le retirer
* Complexité : O(1)
*/
template <typename T>
T PileContigue<T>::top() const {
if (estVide()) {
throw std::underflow_error("Pile vide : aucun élément à consulter");
}
return elements[sommet];
}
Méthode Afficher
/**
* Affiche le contenu de la pile
* Complexité : O(n)
*/
template <typename T>
void PileContigue<T>::afficher() const {
if (estVide()) {
std::cout << "Pile vide" << std::endl;
return;
}
try {
// Création de la pile
PileContigue<int> pile(5);
// Test 1 : Empilage
std::cout << "Test 1 : Empilage" << std::endl;
[Link](10);
[Link](20);
[Link](30);
[Link]();
std::cout << "Taille : " << [Link]() << std::endl;
// Test 2 : Consultation
std::cout << "\nTest 2 : Consultation du sommet" << std::endl;
std::cout << "Sommet : " << [Link]() << std::endl;
[Link](); // Inchangé
// Test 3 : Dépilage
std::cout << "\nTest 3 : Dépilage" << std::endl;
std::cout << "Dépilé : " << [Link]() << std::endl;
[Link]();
// Test 4 : Débordement
std::cout << "\nTest 4 : Test de débordement" << std::endl;
[Link](40);
[Link](50);
[Link](60);
[Link](70); // Devrait lancer une exception
return 0;
}
Sortie attendue :
=== Test Pile Contiguë C++ ===
Test 3 : Dépilage
Dépilé : 30
Pile (sommet -> fond) : 20 10
// Constructeur
Noeud(const T& val) : donnee(val), suivant(nullptr) {}
};
/**
* Classe représentant une pile chaînée
*/
template <typename T>
class PileChainee {
private:
Noeud<T>* sommet;
int taille_pile;
public:
// Constructeur
PileChainee();
// Destructeur
~PileChainee();
// Opérateur d'affectation
PileChainee<T>& operator=(const PileChainee<T>& autre);
// Méthodes principales
void push(const T& valeur);
T pop();
T top() const;
// Méthodes de vérification
bool estVide() const;
int taille() const;
// Affichage
void afficher() const;
};
En C++, quand une classe gère des ressources dynamiques (pointeurs), elle doit définir :
Sinon, on risque des copies superficielles (shallow copy) qui causent des problèmes.
Implémentation du Constructeur
/**
* Constructeur : initialise une pile vide
* Complexité : O(1)
*/
template <typename T>
PileChainee<T>::PileChainee() : sommet(nullptr), taille_pile(0) {
std::cout << "Pile chaînée créée" << std::endl;
}
Implémentation du Destructeur
/**
* Destructeur : libère tous les nœuds
* Complexité : O(n)
*/
template <typename T>
PileChainee<T>::~PileChainee() {
while (!estVide()) {
pop(); // Dépile et libère chaque nœud
}
std::cout << "Pile chaînée détruite" << std::endl;
}
Constructeur de Copie
/**
* Constructeur de copie : crée une copie profonde
* Complexité : O(n)
*/
template <typename T>
PileChainee<T>::PileChainee(const PileChainee<T>& autre)
: sommet(nullptr), taille_pile(0) {
if ([Link]()) {
return;
}
if (precedent == nullptr) {
sommet = nouveau; // Premier nœud
} else {
precedent->suivant = nouveau;
}
precedent = nouveau;
courant_autre = courant_autre->suivant;
taille_pile++;
}
}
Logique détaillée :
Itération 1 : Copier 9
nouveau → [9] → nullptr
sommet → [9]
Itération 2 : Copier 7
nouveau → [7] → nullptr
sommet → [9] → [7]
Itération 3 : Copier 3
nouveau → [3] → nullptr
sommet → [9] → [7] → [3] → NULL
Opérateur d'Affectation
/**
* Opérateur d'affectation : affecte une pile à une autre
* Complexité : O(n + m) où n est la taille de this, m celle de autre
*/
template <typename T>
PileChainee<T>& PileChainee<T>::operator=(const PileChainee<T>& autre) {
// Vérification d'auto-affectation
if (this == &autre) {
return *this;
}
if (precedent == nullptr) {
sommet = nouveau;
} else {
precedent->suivant = nouveau;
}
precedent = nouveau;
courant_autre = courant_autre->suivant;
taille_pile++;
}
}
return *this;
}
Méthode Push
/**
* Ajoute un élément au sommet
* Complexité : O(1)
*/
template <typename T>
void PileChainee<T>::push(const T& valeur) {
// Créer nouveau nœud
Noeud<T>* nouveau = new Noeud<T>(valeur);
taille_pile++;
}
Comparaison C vs C++ :
// C
Noeud* nouveau = (Noeud*)malloc(sizeof(Noeud));
nouveau->donnee = valeur;
nouveau->suivant = p->sommet;
// C++
Noeud<T>* nouveau = new Noeud<T>(valeur); // Constructeur fait tout
nouveau->suivant = sommet;
Méthode Pop
/**
* Retire et retourne l'élément au sommet
* Complexité : O(1)
*/
template <typename T>
T PileChainee<T>::pop() {
if (estVide()) {
throw std::underflow_error("Pile vide : impossible de dépiler");
}
// Libérer la mémoire
delete ancien_sommet;
taille_pile--;
return valeur;
}
Méthodes Auxiliaires
/**
* Vérifie si la pile est vide
* Complexité : O(1)
*/
template <typename T>
bool PileChainee<T>::estVide() const {
return sommet == nullptr;
}
/**
* Retourne le nombre d'éléments
* Complexité : O(1)
*/
template <typename T>
int PileChainee<T>::taille() const {
return taille_pile;
}
/**
* Consulte le sommet sans le retirer
* Complexité : O(1)
*/
template <typename T>
T PileChainee<T>::top() const {
if (estVide()) {
throw std::underflow_error("Pile vide : aucun élément");
}
return sommet->donnee;
}
/**
* Affiche le contenu de la pile
* Complexité : O(n)
*/
template <typename T>
void PileChainee<T>::afficher() const {
if (estVide()) {
std::cout << "Pile vide" << std::endl;
return;
}
try {
// Test 1 : Opérations de base
std::cout << "Test 1 : Opérations de base" << std::endl;
PileChainee<int> pile1;
[Link](100);
[Link](200);
[Link](300);
[Link]();
std::cout << "Taille : " << [Link]() << std::endl;
// Test 6 : Sous-dépassement
std::cout << "\nTest 6 : Test de sous-dépassement" << std::endl;
PileChainee<int> pile_vide;
pile_vide.pop(); // Lance une exception
} catch (const std::underflow_error& e) {
std::cout << "Exception : " << [Link]() << std::endl;
}
return 0;
}
Sortie attendue :
Exemples :
VALIDES :
- "()" → Paire simple
- "([{}])" → Imbrication correcte
- "{[()()]}" → Imbrication multiple
- "" → Chaîne vide (valide par convention)
INVALIDES :
- "(()" → Manque une fermante
- "())" → Trop de fermantes
- "([)]" → Mauvais ordre d'imbrication
- "}{" → Commence par fermante
Pseudocode détaillé :
ALGORITHME VerifierParentheses(chaine)
ENTRÉE : chaine de caractères
SORTIE : VRAI si équilibré, FAUX sinon
DÉBUT
Créer une pile P vide
ouvrante ← POP(P)
Fin de chaîne
Pile finale : ( ( → NON VIDE → INVALIDE ✗
Implémentation en C++
#include <iostream>
#include <string>
#include <stack> // Pile de la STL
/**
* Vérifie si un caractère est une parenthèse ouvrante
*/
bool estOuvrante(char c) {
return (c == '(' || c == '[' || c == '{');
}
/**
* Vérifie si un caractère est une parenthèse fermante
*/
bool estFermante(char c) {
return (c == ')' || c == ']' || c == '}');
}
/**
* Vérifie si deux parenthèses correspondent
*/
bool correspondent(char ouvrante, char fermante) {
return (ouvrante == '(' && fermante == ')') ||
(ouvrante == '[' && fermante == ']') ||
(ouvrante == '{' && fermante == '}');
}
/**
* Vérifie si les parenthèses sont équilibrées
* Complexité : O(n) où n est la longueur de la chaîne
* Espace : O(n) dans le pire cas (toutes ouvrantes)
*/
bool verifierParentheses(const std::string& expression) {
std::stack<char> pile;
} else if (estFermante(c)) {
// Vérifier les fermantes
if ([Link]()) {
return false; // Fermante sans ouvrante
}
if (!correspondent(ouvrante, c)) {
return false; // Mauvaise correspondance
}
}
// Ignorer les autres caractères
}
return 0;
}
Sortie :
Analyse de Complexité
Complexité temporelle :
T (n) = O(n)
Justification :
Complexité spatiale :
S(n) = O(n)
Pire cas : Toutes les parenthèses sont ouvrantes → pile contient n éléments
Pourquoi postfixe ?
Notations Comparées
Règles de priorité :
Prioritˊe : ∗ / > +− > (
Pseudocode :
ALGORITHME InfixeVersPostfixe(expression)
ENTRÉE : Expression infixe (string)
SORTIE : Expression postfixe (string)
DÉBUT
Créer une pile P pour les opérateurs
Créer une chaîne sortie vide
EST_PARENTHESE_OUVRANTE '(' :
PUSH(P, s)
EST_PARENTHESE_FERMANTE ')' :
TANT QUE sommet(P) ≠ '(' FAIRE
Ajouter POP(P) à sortie
FIN TANT QUE
POP(P) // Retirer '('
EST_OPERATEUR (+, -, *, /) :
TANT QUE P non vide ET
priorité(sommet(P)) >= priorité(s) FAIRE
Ajouter POP(P) à sortie
FIN TANT QUE
PUSH(P, s)
FIN CAS
FIN POUR
Retourner sortie
FIN
Vérification :
Évaluation de "3 5 2 * +" :
3 → Pile : [3]
5 → Pile : [3, 5]
2 → Pile : [3, 5, 2]
* → Pop 2, Pop 5, Push (5*2=10) → Pile : [3, 10]
+ → Pop 10, Pop 3, Push (3+10=13) → Pile : [13]
Résultat : 13 ✓
Vérification :
Résultat : 16 ✓
Implémentation en C++
#include <iostream>
#include <string>
#include <stack>
#include <cctype>
#include <map>
/**
* Retourne la priorité d'un opérateur
*/
int priorite(char op) {
static std::map<char, int> priorites = {
{'+', 1},
{'-', 1},
{'*', 2},
{'/', 2}
};
return priorites[op];
}
/**
* Vérifie si un caractère est un opérateur
*/
bool estOperateur(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/');
}
/**
* Convertit une expression infixe en postfixe
* Complexité : O(n) où n est la longueur de l'expression
* Espace : O(n) pour la pile
*/
std::string infixeVersPostfixe(const std::string& expression) {
std::stack<char> pile;
std::string sortie = "";
if (std::isdigit(c)) {
// Opérande : ajouter à la sortie
sortie += c;
sortie += ' ';
} else if (c == '(') {
// Parenthèse ouvrante : empiler
[Link](c);
} else if (c == ')') {
// Parenthèse fermante : dépiler jusqu'à '('
while (![Link]() && [Link]() != '(') {
sortie += [Link]();
sortie += ' ';
[Link]();
}
[Link](); // Retirer '('
} else if (estOperateur(c)) {
// Opérateur : dépiler selon priorité
while (![Link]() &&
[Link]() != '(' &&
priorite([Link]()) >= priorite(c)) {
sortie += [Link]();
sortie += ' ';
[Link]();
}
[Link](c);
}
}
return sortie;
}
int main() {
std::string expressions[] = {
"3 + 5",
"3 + 5 * 2",
"(3 + 5) * 2",
"3 * 5 + 2",
"3 * (5 + 2)",
"((3 + 5) * 2) / 4"
};
return 0;
}
Sortie :
=== Conversion Infixe → Postfixe ===
Infixe : 3 + 5
Postfixe: 3 5 +
Infixe : 3 + 5 * 2
Postfixe: 3 5 2 * +
Infixe : (3 + 5) * 2
Postfixe: 3 5 + 2 *
Infixe : 3 * 5 + 2
Postfixe: 3 5 * 2 +
Infixe : 3 * (5 + 2)
Postfixe: 3 5 2 + *
Infixe : ((3 + 5) * 2) / 4
Postfixe: 3 5 + 2 * 4 /
Pseudocode :
ALGORITHME EvaluerPostfixe(expression)
ENTRÉE : Expression postfixe (string)
SORTIE : Résultat (nombre)
DÉBUT
Créer une pile P de nombres
Résultat final : 13 ✓
État avant * :
┌────┐
│ 2 │ ← sommet (operande2)
├────┤
│ 5 │ ← (operande1)
├────┤
│ 3 │
└────┘
Étape 3 : resultat = 5 * 2 = 10
Étape 4 : PUSH(10)
┌────┐
│ 10 │ ← sommet
├────┤
│ 3 │
└────┘
/**
* Applique un opérateur à deux opérandes
*/
int appliquerOperateur(int operande1, char op, int operande2) {
switch (op) {
case '+': return operande1 + operande2;
case '-': return operande1 - operande2;
case '*': return operande1 * operande2;
case '/':
if (operande2 == 0) {
throw std::runtime_error("Division par zéro");
}
return operande1 / operande2;
default:
throw std::invalid_argument("Opérateur inconnu");
}
}
/**
* Évalue une expression postfixe
* Complexité : O(n) où n est le nombre de symboles
* Espace : O(n) dans le pire cas (tous les symboles sont des opérandes)
*/
int evaluerPostfixe(const std::string& expression) {
std::stack<int> pile;
std::istringstream iss(expression);
std::string symbole;
if ([Link]() != 1) {
throw std::runtime_error("Expression invalide : résultat incorrect");
}
return [Link]();
}
int main() {
std::string expressions[] = {
"3 5 +", // 3 + 5 = 8
"3 5 2 * +", // 3 + (5 * 2) = 13
"3 5 + 2 *", // (3 + 5) * 2 = 16
"15 7 1 1 + - / 3 * 2 1 1 + + -" // Complexe
};
return 0;
}
Sortie :
Expression : 3 5 2 * +
Résultat : 13
Expression : 3 5 + 2 *
Résultat : 16
Expression : 15 7 1 1 + - / 3 * 2 1 1 + + -
Résultat : 5
Analyse de Complexité
Complexité temporelle :
T (n) = O(n)
Justification :
Complexité spatiale :
S(n) = O(n)
G = (V , E)
où :
Exemple de graphe :
A
/|\
B C D
| |
E F
V = {A, B, C, D, E, F}
E = {(A,B), (A,C), (A,D), (B,E), (D,F)}
Pseudocode :
ALGORITHME DFS_Iteratif(graphe, depart)
ENTRÉE : Graphe G, sommet de départ s
SORTIE : Ordre de visite des sommets
DÉBUT
Créer une pile P
Créer un tableau visite[] initialisé à FAUX
PUSH(P, depart)
Graphe :
A
/|\
B C D
| |
E F
Adjacences :
A → [B, C, D]
B → [E]
C → []
D → [F]
E → []
F → []
Ordre de visite : A → D → F → C → B → E
Implémentation en C++
#include <iostream>
#include <vector>
#include <stack>
#include <unordered_set>
/**
* Classe représentant un graphe non orienté
*/
class Graphe {
private:
int nbSommets;
std::vector<std::vector<int>> adjacence;
public:
Graphe(int n) : nbSommets(n) {
[Link](n);
}
/**
* Parcours en profondeur itératif avec pile
* Complexité : O(V + E) où V = sommets, E = arêtes
* Espace : O(V) pour la pile et le tableau de visite
*/
void DFS_Iteratif(int depart) {
std::stack<int> pile;
std::unordered_set<int> visite;
[Link](depart);
while (![Link]()) {
int sommet = [Link]();
[Link]();
if ([Link](sommet) == [Link]()) {
// Marquer comme visité
[Link](sommet);
std::cout << sommet << " ";
/**
* Pour comparaison : DFS récursif
* Complexité : O(V + E)
* Espace : O(V) pour la pile d'appels (récursion)
*/
void DFS_Recursif(int sommet, std::unordered_set<int>& visite) {
[Link](sommet);
std::cout << sommet << " ";
int main() {
// Créer le graphe exemple
Graphe g(6); // Sommets 0-5 représentent A-F
return 0;
}
Sortie :
=== Parcours en Profondeur (DFS) ===
Graphe :
0(A)
/|\
1 2 3 (B C D)
| |
4 5 (E F)
int factorielle(int n) {
if (n <= 1) return 1;
return n * factorielle(n - 1);
}
┌─────────────────────────┐
│ Paramètres : n │
│ Variables locales │
│ Adresse de retour │
│ Valeur de retour │
└─────────────────────────┘
Trace de factorielle(4) :
Étape 1 : Appel factorielle(4)
┌─────────────────┐
│ n = 4 │
│ retour = ? │
└─────────────────┘
Étape 6 : Retour 2
┌─────────────────┐
│ n = 3 │ ← Sommet
│ retour = ? │
├─────────────────┤
│ n = 4 │
│ retour = ? │
└─────────────────┘
Calcul : 3 * 2 = 6
Étape 7 : Retour 6
┌─────────────────┐
│ n = 4 │ ← Sommet
│ retour = ? │
└─────────────────┘
Calcul : 4 * 6 = 24
Étape 8 : Retour 24
┌─────────────────┐
│ [vide] │
└─────────────────┘
Résultat final : 24
/**
* Calcul de factorielle de manière itérative avec pile
* Simule la récursion
* Complexité : O(n) temps, O(n) espace
*/
int factorielleIteratif(int n) {
std::stack<CadreFactorielle> pile;
[Link](CadreFactorielle(n));
int resultatFinal = 1;
while (![Link]()) {
CadreFactorielle& cadre = [Link]();
if (cadre.n <= 1) {
// Cas de base
[Link] = 1;
[Link] = false;
resultatFinal = [Link];
[Link]();
} else if ([Link]) {
// Premier passage : "descente"
[Link] = false;
[Link](CadreFactorielle(cadre.n - 1));
} else {
// Deuxième passage : "remontée"
[Link] = cadre.n * resultatFinal;
resultatFinal = [Link];
[Link]();
}
}
return resultatFinal;
}
/**
* Version récursive classique pour comparaison
*/
int factorielleRecursif(int n) {
if (n <= 1) return 1;
return n * factorielleRecursif(n - 1);
}
int main() {
std::cout << "=== Calcul de Factorielle ===" << std::endl;
for (int i = 0; i <= 10; i++) {
int res1 = factorielleIteratif(i);
int res2 = factorielleRecursif(i);
std::cout << i << "! = " << res1 << " (itératif) = "
<< res2 << " (récursif)" << std::endl;
}
return 0;
}
Sortie :
Graphique de comparaison :
Mémoire (octets)
│
2000│ ╱ Chaînée
│ ╱
1500│ ╱
│ ╱
1000│ ╱
│ ╱
500│ ╱─────────────────────── Contiguë (cap=100)
│╱
0└──────────────────────────> n (nombre d'éléments)
0 25 50 75 100
Observation :
Tableau de décision :
Contexte | Recommandation
--------------------------------|----------------
Calculatrice RPN (taille ≤ 100) | Contiguë
Historique navigateur (illimité)| Chaînée
Pile d'appels système | Contiguë
Parser XML/JSON (profond) | Chaînée
Évaluation d'expressions | Contiguë
DFS sur grand graphe | Chaînée
Structure :
Implémentation C++ :
class PileAvecMin {
private:
std::stack<int> pile;
std::stack<int> mins;
public:
void push(int valeur) {
[Link](valeur);
void pop() {
if ([Link]()) {
throw std::underflow_error("Pile vide");
}
[Link]();
[Link]();
}
int main() {
PileAvecMin p;
[Link](7);
std::cout << "Min après push(7) : " << [Link]() << std::endl; // 7
[Link](1);
std::cout << "Min après push(1) : " << [Link]() << std::endl; // 1
[Link](5);
std::cout << "Min après push(5) : " << [Link]() << std::endl; // 1
[Link](3);
std::cout << "Min après push(3) : " << [Link]() << std::endl; // 1
[Link]();
std::cout << "Min après pop() : " << [Link]() << std::endl; // 1
[Link]();
std::cout << "Min après pop() : " << [Link]() << std::endl; // 1
[Link]();
std::cout << "Min après pop() : " << [Link]() << std::endl; // 7
return 0;
}
Complexité :
• push() : O(1)
• pop() : O(1)
• getMin() : O(1)
• Espace : O(n) pour la pile auxiliaire
void redimensionner() {
int nouvelleCapacite = capacite * 2;
T* nouveauTableau = new T[nouvelleCapacite];
delete[] elements;
elements = nouveauTableau;
capacite = nouvelleCapacite;
public:
PileDynamique(int cap = 2) : capacite(cap), sommet(-1) {
elements = new T[capacite];
}
~PileDynamique() {
delete[] elements;
}
T pop() {
if (sommet < 0) {
throw std::underflow_error("Pile vide");
}
return elements[sommet--];
}
int main() {
PileDynamique<int> pile(2); // Capacité initiale : 2
return 0;
}
Sortie :
Push(1), taille = 1
Push(2), taille = 2
Redimensionnement : 2 → 4
Push(3), taille = 3
Push(4), taille = 4
Redimensionnement : 4 → 8
Push(5), taille = 5
Push(6), taille = 6
Push(7), taille = 7
Push(8), taille = 8
Redimensionnement : 8 → 16
Push(9), taille = 9
Push(10), taille = 10
1. Principe LIFO simple mais puissant : Naturel pour inverser, mémoriser temporairement
2. Efficacité optimale : Toutes les opérations en O(1)
3. Utilisée partout :
• Systèmes d'exploitation (pile d'appels)
• Compilateurs (analyse syntaxique)
• Navigation web (historique)
• Éditeurs (Undo/Redo)
• Algorithmes de graphes (DFS)
avec ∀s ∈ S, Pop(Push(s)) = s
Niveau Intermédiaire : 4. Implémenter une pile qui retourne le maximum en O(1) 5. Trier une pile en utilisant une pile auxiliaire 6.
Convertir une expression infixe avec parenthèses en postfixe
Niveau Avancé : 7. Implémenter deux piles dans un seul tableau 8. Créer une pile thread-safe (multi-threading) 9. Simuler une
machine à pile (stack machine) pour bytecode
• Files (FIFO)
• Listes chaînées avancées
• Arbres (qui utilisent des piles pour le parcours)
• Graphes (DFS approfondi)
Concept C C++
#include <stack>
// Utilisation de std::stack
std::stack<int> pile;
[Link](10); // Empiler
[Link](20);
Avantages de std::stack :
FIN DU CHAPITRE 5
Ce chapitre a couvert en profondeur la structure de données Pile, depuis les concepts fondamentaux jusqu'aux applications
avancées, avec des implémentations complètes en C et C++. La compréhension de la pile est essentielle pour aborder les
structures de données plus complexes et les algorithmes qui les utilisent.