Manipulation de listes chaînées : suppression d'éléments, conception et inversion

Ce jour marque la troisième journée d'exercices axés sur les structures de données de listes chaînées. Nous allons aborder trois problèmes classiques : la suppression d'éléments spécifiques, la conception d'une classe de liste chaînée et l'inversion d'une liste chaînée.

Suppression d'éléments d'une liste chaînée

Le premier problème consiste à supprimer toutes les occurrecnes d'une valeur donnée dans une liste chaînée. Une approche courante consiste à utiliser un pointeur factice (dummy node) en tête de liste pour simplifier la gestion des suppressions, y compris celle du premier élément.


/**
 * Définition d'une liste chaînée.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var supprimerElements = function(head, val) {
    // Créer un nœud factice pour simplifier la logique
    let noeudFactice = new ListNode(0, head);
    let pointeurPrecedent = noeudFactice;
    let pointeurCourant = head;

    while (pointeurCourant !== null) {
        if (pointeurCourant.val === val) {
            // Sauter le nœud courant
            pointeurPrecedent.next = pointeurCourant.next;
        } else {
            // Avancer le pointeur précédent seulement si le nœud courant n'est pas supprimé
            pointeurPrecedent = pointeurCourant;
        }
        // Avancer le pointeur courant dans tous les cas
        pointeurCourant = pointeurCourant.next;
    }

    // Retourner la tête de la liste modifiée (après le nœud factice)
    return noeudFactice.next;
};

Conception d'une classe de liste chaînée doublement chaînée

Le second problème est de concevoir une classe MaListeChainee qui supporte les opérations d'ajout, de récupération et de suppression d'éléments à des indices spécifiés, ainsi que l'ajout en tête et en queue. Nous implémenterons une liste chaînée doublement chaînée pour une efficacité optimale des opérations à la queue.


// Classe pour représenter un nœud de la liste chaînée
class NoeudListe {
    constructor(valeur, suivant = null, precedent = null) {
        this.valeur = valeur;
        this.suivant = suivant;
        this.precedent = precedent;
    }
}

// Classe principale pour la liste chaînée doublement chaînée
var MaListeChainee = function() {
    this.tete = null;
    this.queue = null;
    this.taille = 0;
};

/**
 * Obtient le nœud à un index donné.
 * @param {number} index L'index du nœud à récupérer.
 * @returns {NoeudListe | null} Le nœud à l'index spécifié ou null si l'index est invalide.
 */
MaListeChainee.prototype.obtenirNoeud = function(index) {
    if (index < 0 || index >= this.taille) {
        return null;
    }
    let noeud;
    // Optimisation : parcourir depuis la tête ou la queue selon l'index
    if (index < this.taille / 2) {
        noeud = this.tete;
        for (let i = 0; i < index; i++) {
            noeud = noeud.suivant;
        }
    } else {
        noeud = this.queue;
        for (let i = this.taille - 1; i > index; i--) {
            noeud = noeud.precedent;
        }
    }
    return noeud;
};

/**
 * Récupère la valeur du nœud à l'index spécifié.
 * @param {number} index L'index du nœud.
 * @return {number} La valeur du nœud, ou -1 si l'index est invalide.
 */
MaListeChainee.prototype.get = function(index) {
    const noeud = this.obtenirNoeud(index);
    return noeud ? noeud.valeur : -1;
};

/**
 * Ajoute un nœud avec la valeur donnée en tête de la liste.
 * @param {number} val La valeur à ajouter.
 * @return {void}
 */
MaListeChainee.prototype.addAtHead = function(val) {
    const nouveauNoeud = new NoeudListe(val, this.tete, null);
    if (this.tete !== null) {
        this.tete.precedent = nouveauNoeud;
    } else {
        // Si la liste était vide, le nouveau nœud est aussi la queue
        this.queue = nouveauNoeud;
    }
    this.tete = nouveauNoeud;
    this.taille++;
};

/**
 * Ajoute un nœud avec la valeur donnée en queue de la liste.
 * @param {number} val La valeur à ajouter.
 * @return {void}
 */
MaListeChainee.prototype.addAtTail = function(val) {
    const nouveauNoeud = new NoeudListe(val, null, this.queue);
    if (this.queue !== null) {
        this.queue.suivant = nouveauNoeud;
    } else {
        // Si la liste était vide, le nouveau nœud est aussi la tête
        this.tete = nouveauNoeud;
    }
    this.queue = nouveauNoeud;
    this.taille++;
};

/**
 * Ajoute un nœud avec la valeur donnée à un index spécifié.
 * @param {number} index L'index où ajouter le nœud.
 * @param {number} val La valeur du nœud à ajouter.
 * @return {void}
 */
MaListeChainee.prototype.addAtIndex = function(index, val) {
    if (index < 0 || index > this.taille) {
        return; // Index invalide
    }
    if (index === 0) {
        this.addAtHead(val);
    } else if (index === this.taille) {
        this.addAtTail(val);
    } else {
        // Obtenir le nœud précédent l'index d'insertion
        const noeudPrecedent = this.obtenirNoeud(index - 1);
        const nouveauNoeud = new NoeudListe(val, noeudPrecedent.suivant, noeudPrecedent);
        noeudPrecedent.suivant.precedent = nouveauNoeud;
        noeudPrecedent.suivant = nouveauNoeud;
        this.taille++;
    }
};

/**
 * Supprime le nœud à l'index spécifié.
 * @param {number} index L'index du nœud à supprimer.
 * @return {void}
 */
MaListeChainee.prototype.deleteAtIndex = function(index) {
    if (index < 0 || index >= this.taille) {
        return; // Index invalide
    }

    if (index === 0) {
        // Suppression de la tête
        this.tete = this.tete.suivant;
        if (this.tete !== null) {
            this.tete.precedent = null;
        } else {
            // La liste est devenue vide
            this.queue = null;
        }
    } else if (index === this.taille - 1) {
        // Suppression de la queue
        this.queue = this.queue.precedent;
        this.queue.suivant = null;
    } else {
        // Suppression d'un élément intermédiaire
        const noeudASupprimer = this.obtenirNoeud(index);
        noeudASupprimer.precedent.suivant = noeudASupprimer.suivant;
        noeudASupprimer.suivant.precedent = noeudASupprimer.precedent;
    }
    this.taille--;
};

Inversion d'une liste chaînée simple

Enfin, nous inversons une liste chaînée simple. L'algorithme itératif est efficace, nécessitant trois pointeurs : un pour le nœud précédent, un pour le nœud courant, et un temporaire pour stocker le nœud suivant avant de modifier le poitneur courant.


/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var inverserListe = function(head) {
    let pointeurPrecedent = null;
    let pointeurCourant = head;
    let noeudTemporaire = null;

    while (pointeurCourant !== null) {
        // Sauvegarder le prochain nœud avant de le modifier
        noeudTemporaire = pointeurCourant.next;
        // Inverser le pointeur 'next' du nœud courant
        pointeurCourant.next = pointeurPrecedent;
        // Avancer les pointeurs pour la prochaine itération
        pointeurPrecedent = pointeurCourant;
        pointeurCourant = noeudTemporaire;
    }

    // Le pointeur 'precedent' pointe maintenant vers la nouvelle tête de la liste inversée
    return pointeurPrecedent;
};

Ces exercices fournissent une base solide pour la compréhension et la manipulation des listes chaînées, des structures de données fondamentales en informatique.

Étiquettes: Listes chaînées Linked List Algorithme structure de données JavaScript

Publié le 24 juin à 02h05