Les listes chaînées en C++

19 Nov 2021 19 Nov 2021 21356 vues ESSADDOUKI Mostafa 9 min de lecture
Introduction et syntaxe de base
1 Introduction au langage C++ 2 Entrée-sortie en C++ - cin et cout 3 Inférence de type avec le mot-clé auto en C++ 4 Classe std::string et les chaînes de caractères en C++ 5 Les structures conditionnelles (if et switch) en C++ (C++17 et C++20) 6 Les boucles en C++ (C++17 et C++20) 7 La gestion des fichiers en C++
Pointeurs et fonctions
8 Introduction aux pointeurs en C++ - Déclaration et interêts 9 Les références en C++ - déclaration et interêts 10 Les tableaux en C++ - Déclaration et interêts 11 Introduction aux fonctions en C++ 12 Passer des arguments à une fonction en C++ 13 Déclarer un paramètre const en C++ 14 Les fonctions Lambda en C++ 15 Fonctions utiles (Mathématiques et caractères) en C++
Programmation OO
16 Classes et objets en C++ 17 Spécificateurs d'accès en C++ 18 Constructeurs et destructeur d'une classe en C++ 19 Fonctions membres en C++ 20 Membres statiques d'une classe en C++ 21 Fonctions en ligne en C++ - inline 22 Fonctions et classes amies en C++ - friend 23 Surcharge des fonctions en C++ 24 Surcharge des opérateurs en C++ 25 Héritage en C++ 26 La gestion d'exceptions en C++ : déclaration, utilisation et personnalisation 27 fonctions et classes templates en C++ 28 Les nouveautés C++20 pour améliorer les templates en C++
Structures de données
29 Introduction aux structures de données 30 Les structures en C++ et la différence avec les structures en C 31 Les listes chaînées en C++ 32 Les piles en C++ 33 File d'attente en C++ 34 Arbre binaire de recherche : définition et mise en oeuvre en C++
La bibliothèque standard (STL)
35 Introduction à la bibliothèque de Template Standard STL 36 Les itérateurs en C++ - définition, déclaration et exemples 37 La classe array en C++ (bibliothèque STL) <array> 38 La classe vector de la bibliothèque STL <vector> 39 La classe deque en C++ ( Bibliothèque STL) 40 La classe list en C++ (bibliothèque STL) <list> 41 La classe stack (Pile) en C++ (bibliothèque STL) <stack> 42 La classe queue (File d'attente) en C++ (bibliothèque STL) <queue> 43 La file d'attente prioritaire (classe priority_queue) - Bibliothèque STL 44 Les ensembles en C++ (Classe set <set> - Bibliothèque STL) 45 Les dictionnaires en C++ : Classe map (Bibliothèque STL) 46 Introduction aux algorithmes de la bibliothèque STL (programmation compétitive) 47 Tri et méthodes associées en C++ - Bibliothèque STL 48 Recherche dichotomique et méthodes associées en C++ - Bibliothèque STL 49 Appliquer un prédicat ou une fonction aux éléments d'une séquence en C++ - Bibliothèque STL 50 Recherche dans une séquence et méthodes associées en C++ - Bibliothèque STL

Liste chaînée simple

Une liste chaînée est une structure de données linéaire dans laquelle les éléments ne sont pas stockés dans des emplacements mémoire contigus. Chaque objet est lié uniquement à l'objet suivant par un pointeur.

Nœud L'unité de base d'une liste chaînée s'appelle un nœud. Il se compose de deux parties :
  • données — la valeur stockée dans le nœud.
  • suivant — un pointeur vers le nœud suivant de la liste.
Dans une liste simplement chaînée, on ne peut avancer que vers le nœud suivant. Une liste doublement chaînée ajoute un pointeur precedent pour naviguer dans les deux sens.

Conception

Objets Liste et Nœud

Pour concevoir une liste chaînée simple, on utilise deux types d'objets distincts : l'objet liste et l'objet nœud.

Objets Liste et Nœud

Création des objets en mémoire

L'objet liste est créé dans la mémoire de pile (une seule instance). Les nœuds sont créés dans la mémoire de tas car leur nombre varie dynamiquement à chaque insertion ou suppression.

Structure générale d'une liste chaînée simple

L'objet liste contient un pointeur entete vers le premier nœud et un entier compteur indiquant le nombre de nœuds. Le nœud est défini comme une struct (membres publics par défaut) pour permettre un accès direct depuis la classe liste.

Implémentation

Définition du nœud et de la classe Liste

Le nœud est un struct template avec deux membres publics : donnees et suivant.

template <typename T>
struct Noeud {
    T donnees;
    Noeud<T>* suivant;
    // Pour une liste doublement chaînée, ajouter :
    // Noeud<T>* precedent;
};

La classe Liste possède deux membres privés (entete et compteur), une méthode privée CreerNoeud, et sept méthodes publiques.

template <typename T>
class Liste {
private:
    Noeud<T>* entete;
    int compteur;
    Noeud<T>* CreerNoeud(const T& valeur);

public:
    Liste();
    ~Liste();
    void inserer(int pos, const T& valeur);
    void supprimer(int pos);
    T& getNoeud(int pos) const;
    void afficher() const;
    int taille() const;
};

Créer un nouveau nœud

template <typename T>
Noeud<T>* Liste<T>::CreerNoeud(const T& valeur) {
    Noeud<T>* temp = new Noeud<T>;
    temp->donnees = valeur;
    temp->suivant = NULL;
    return temp;
}

Constructeur

template <typename T>
Liste<T>::Liste() : entete(NULL), compteur(0) {}

Destructeur

Le destructeur parcourt la liste et libère chaque nœud un par un depuis la mémoire de tas.

Destruction d'une liste à deux nœuds

template <typename T>
Liste<T>::~Liste() {
    Noeud<T>* del = entete;
    while (entete) {
        entete = entete->suivant;
        delete del;
        del = entete;
    }
}

Taille de la liste

template <typename T>
int Liste<T>::taille() const {
    return compteur;
}

Insérer un nœud

L'insertion peut se faire à trois positions :

Insertion au début

Deux opérations suffisent après la création du nœud.

Insertion en tête

Insertion au milieu

On avance un pointeur courant jusqu'au nœud précédant la position d'insertion, puis on relie le nouveau nœud.

Insertion au milieu

Insertion à la fin

Cas particulier de l'insertion au milieu : courant atteint le dernier nœud.

template <typename T>
void Liste<T>::inserer(int pos, const T& valeur) {
    if (pos < 0 || pos > compteur) {
        cout << "Erreur! La position est invalide." << endl;
        return;
    }
    Noeud<T>* nouveau = CreerNoeud(valeur);

    if (pos == 0) {                        // Insertion en tête
        nouveau->suivant = entete;
        entete = nouveau;
    } else {                               // Insertion au milieu / fin
        Noeud<T>* courant = entete;
        for (int i = 1; i < pos; i++) {
            courant = courant->suivant;
        }
        nouveau->suivant  = courant->suivant;
        courant->suivant  = nouveau;
    }
    compteur++;
}

Supprimer un nœud

Suppression du premier nœud

Suppression en tête

Suppression au milieu ou en fin

Un pointeur courant se positionne sur le nœud précédant celui à supprimer, puis un pointeur del cible le nœud à supprimer avant de libérer sa mémoire.

Suppression au milieu

template <typename T>
void Liste<T>::supprimer(int pos) {
    if (pos < 0 || pos > compteur - 1) {
        cout << "Erreur! La position est invalide." << endl;
        return;
    }
    if (pos == 0) {                        // Suppression en tête
        Noeud<T>* del = entete;
        entete = entete->suivant;
        delete del;
    } else {                               // Suppression au milieu / fin
        Noeud<T>* courant = entete;
        for (int i = 0; i < pos - 1; i++) {
            courant = courant->suivant;
        }
        Noeud<T>* del     = courant->suivant;
        courant->suivant   = courant->suivant->suivant;
        delete del;
    }
    compteur--;
}

Afficher la liste

template <typename T>
void Liste<T>::afficher() const {
    if (compteur == 0) {
        cout << "La liste est vide!" << endl;
        return;
    }
    Noeud<T>* courant = entete;
    while (courant != 0) {
        cout << courant->donnees << endl;
        courant = courant->suivant;
    }
}

Retourner la valeur d'un nœud

template <typename T>
T& Liste<T>::getNoeud(int pos) const {
    if (pos < 0 || pos > compteur - 1) {
        cout << "Erreur! La position est invalide";
        assert(false);
    } else if (pos == 0) {
        return entete->donnees;
    } else {
        Noeud<T>* courant = entete;
        for (int i = 0; i < pos; i++) {
            courant = courant->suivant;
        }
        return courant->donnees;
    }
}

Fichiers complets et programme principal

#ifndef LISTE_H
#define LISTE_H

#include <iostream>
#include <cassert>
using namespace std;

template <typename T>
struct Noeud {
    T donnees;
    Noeud<T>* suivant;
};

template <typename T>
class Liste {
private:
    Noeud<T>* entete;
    int compteur;
    Noeud<T>* CreerNoeud(const T& valeur);

public:
    Liste();
    ~Liste();
    void inserer(int pos, const T& valeur);
    void supprimer(int pos);
    T& getNoeud(int pos) const;
    void afficher() const;
    int taille() const;
};
#endif
#ifndef LISTE_CPP
#define LISTE_CPP

#include "liste.h"

template <typename T>
Liste<T>::Liste() : entete(NULL), compteur(0) {}

template <typename T>
Liste<T>::~Liste() {
    Noeud<T>* del = entete;
    while (entete) {
        entete = entete->suivant;
        delete del;
        del = entete;
    }
}

template <typename T>
Noeud<T>* Liste<T>::CreerNoeud(const T& valeur) {
    Noeud<T>* temp = new Noeud<T>;
    temp->donnees = valeur;
    temp->suivant = NULL;
    return temp;
}

template <typename T>
void Liste<T>::inserer(int pos, const T& valeur) {
    if (pos < 0 || pos > compteur) {
        cout << "Erreur! La position est invalide." << endl;
        return;
    }
    Noeud<T>* nouveau = CreerNoeud(valeur);
    if (pos == 0) {
        nouveau->suivant = entete;
        entete = nouveau;
    } else {
        Noeud<T>* courant = entete;
        for (int i = 1; i < pos; i++) courant = courant->suivant;
        nouveau->suivant = courant->suivant;
        courant->suivant = nouveau;
    }
    compteur++;
}

template <typename T>
void Liste<T>::supprimer(int pos) {
    if (pos < 0 || pos > compteur - 1) {
        cout << "Erreur! La position est invalide." << endl;
        return;
    }
    if (pos == 0) {
        Noeud<T>* del = entete;
        entete = entete->suivant;
        delete del;
    } else {
        Noeud<T>* courant = entete;
        for (int i = 0; i < pos - 1; i++) courant = courant->suivant;
        Noeud<T>* del    = courant->suivant;
        courant->suivant  = courant->suivant->suivant;
        delete del;
    }
    compteur--;
}

template <typename T>
T& Liste<T>::getNoeud(int pos) const {
    if (pos < 0 || pos > compteur - 1) {
        cout << "Erreur! La position est invalide";
        assert(false);
    } else if (pos == 0) {
        return entete->donnees;
    } else {
        Noeud<T>* courant = entete;
        for (int i = 0; i < pos; i++) courant = courant->suivant;
        return courant->donnees;
    }
}

template <typename T>
void Liste<T>::afficher() const {
    if (compteur == 0) { cout << "La liste est vide!" << endl; return; }
    Noeud<T>* courant = entete;
    while (courant != 0) {
        cout << courant->donnees << endl;
        courant = courant->suivant;
    }
}

template <typename T>
int Liste<T>::taille() const { return compteur; }

#endif
#include "liste.cpp"
#include <string>

int main() {
    Liste<string> liste;

    liste.inserer(0, "Mostafa");
    liste.inserer(1, "Omar");
    liste.inserer(2, "Sara");
    liste.inserer(3, "Mohamed");
    liste.inserer(4, "Moneim");
    liste.inserer(5, "Dounia");
    liste.inserer(6, "Abdelmalek");

    cout << "Afficher la liste" << endl;
    liste.afficher();
    cout << '\n';

    cout << "Afficher les données de quelques nœuds" << endl;
    cout << liste.getNoeud(1) << endl;
    cout << liste.getNoeud(3) << endl;
    cout << liste.getNoeud(4) << endl;
    cout << '\n';

    cout << "Supprimer quelques nœuds et afficher la liste" << endl;
    liste.supprimer(0);
    liste.supprimer(3);
    liste.afficher();
    cout << '\n';

    cout << "Nombre de nœuds dans la liste : " << liste.taille();
    return 0;
}
Sortie
Afficher la liste
Mostafa
Omar
Sara
Mohamed
Moneim
Dounia
Abdelmalek

Afficher les données de quelques nœuds
Omar
Mohamed
Moneim

Supprimer quelques nœuds et afficher la liste
Omar
Sara
Mohamed
Dounia
Abdelmalek

Nombre de nœuds dans la liste : 5

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.