Listes chaînées en Java - java.util.LinkedList

13 Sep 2019 13 Sep 2019 18133 vues ESSADDOUKI Mostafa 16 min de lecture
Introduction
1 Nouveautés de Java 11 2 Différences entre JDK, JRE et JVM 3 Structure d'un programme Java - Hello World 4 Mots clés et conventions de dénomination en Java 5 Types de données intégrés en Java 6 Les variables en Java 7 Classes enveloppe - Number, Integer, Double ... 8 Lire les entrées clavier en Java
Structures de contrôle
9 Les opérateurs en Java 10 Les structures conditionnelles en Java 11 Les boucles en Java 12 Instructions de contrôle de boucle - break, continue
Chaines de caractères
13 Les chaines en Java - API String 14 Les chaines en Java - StringBuffer et StringBuilder 15 Les expressions régulières en Java
Programmation OO
16 Objets et classes en Java 17 Modificateurs d'accès Java - public, private, protected et package 18 Méthodes et surcharge des méthodes en Java 19 les constructeurs en Java 20 L'héritage en Java 21 Classes abstraites en Java 22 Interfaces et héritage multiple en Java 23 Les classes imbriquées en Java 24 Les singletons en Java 25 Classes et méthodes génériques 26 Interface fonctionnelle et expressions Lambda en Java
Tableaux et collections
27 Les tableaux en Java 28 Classe Arrays - java.util.Arrays 29 Les listes dynamiques - java.util.ArrayList 30 Les listes chaînées en Java - java.util.LinkedList 31 HashSet en Java - java.util.HashSet 32 HashMap en Java - java.util.HashMap
Gestion des fichiers
33 Comprendre les fichiers informatiques 34 Utilisation des classes Path et Files en Java 35 Lecture et écriture dans un fichier en Java 36 Fichiers à accès aléatoire en Java
Gestion d'exceptions
37 Gestion d'exceptions en Java 38 Créez vos propres classes d'exception en Java
Programmation concurrente
39 Introduction à la programmation concurrente en Java - Multi-threads 40 classe java.lang.Thread 41 Synchronisation des threads en Java
Cours Java pour les débutants — Étape 30 sur 41

LinkedList — Listes chaînées en Java

  Prérequis

Maîtriser les concepts de base des collections Java. Comprendre le fonctionnement d'ArrayList. Connaître les génériques et l'interface List.

  Objectifs

Comprendre la structure de données des listes chaînées et leur implémentation en Java avec LinkedList. Savoir choisir entre ArrayList et LinkedList selon les besoins. Maîtriser les méthodes spécifiques aux listes chaînées (addFirst, addLast, removeFirst, removeLast, etc.).

1. Qu'est-ce qu'une LinkedList ?

LinkedList

LinkedList est une structure de données linéaire où les éléments (nœuds) ne sont pas stockés dans des emplacements mémoire contigus. Chaque nœud contient une donnée et une référence (pointeur) vers le nœud suivant (et parfois précédent).

Tête Donnée 1 [ref] Donnée 2 [ref] Donnée 3 null Queue Liste chaînée simple
Types de listes chaînées
  • Liste chaînée simple : chaque nœud pointe vers le nœud suivant
  • Liste doublement chaînée : chaque nœud pointe vers le suivant ET le précédent (c'est l'implémentation de Java)
  • Liste circulaire : le dernier nœud pointe vers le premier

2. Hiérarchie et déclaration

Hiérarchie de LinkedList

LinkedList<E> étend AbstractSequentialList<E> et implémente les interfaces List<E>, Deque<E>, Queue<E> et Cloneable.

   
Syntaxe — Déclaration Java
import java.util.LinkedList;  // ou import java.util.*;

// Constructeur par défaut
LinkedList<String> noms = new LinkedList<>();

// À partir d'une autre collection
LinkedList<Integer> nombres = new LinkedList<>(autreCollection);

// Polymorphisme (via l'interface List)
List<String> liste = new LinkedList<>();

// Polymorphisme (via l'interface Deque)
Deque<String> deque = new LinkedList<>();

3. Méthodes principales

3.1 Méthodes héritées de List

Méthode Description Complexité
boolean add(E e) Ajoute à la fin O(1)
void add(int index, E e) Insère à une position O(n)
E remove(int index) Supprime à un index O(n)
boolean remove(Object o) Supprime la première occurrence O(n)
E get(int index) Retourne l'élément à l'index O(n)
E set(int index, E e) Remplace l'élément à l'index O(n)
int size() Nombre d'éléments O(1)
boolean isEmpty() Vérifie si vide O(1)

3.2 Méthodes spécifiques aux listes chaînées (Deque)

Méthode Description Complexité
void addFirst(E e) Ajoute au début O(1)
void addLast(E e) Ajoute à la fin O(1)
E getFirst() Retourne le premier élément O(1)
E getLast() Retourne le dernier élément O(1)
E removeFirst() Supprime et retourne le premier O(1)
E removeLast() Supprime et retourne le dernier O(1)
E peekFirst() Retourne le premier (sans supprimer, null si vide) O(1)
E peekLast() Retourne le dernier (sans supprimer, null si vide) O(1)

4. Exemples d'utilisation

  Exemple 1 — Opérations de base

import java.util.LinkedList;

public class Test {
    public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<>();

        // Ajout d'éléments
        link.add("Mostafa");
        link.add("Ismail");
        link.add("Dounia");
        link.add("Badr");
        link.add("Omar");

        // Ajout spécifique aux LinkedList
        link.addLast("Mohamed");   // Ajoute à la fin
        link.addFirst("Fatima");   // Ajoute au début
        link.add(1, "Haitam");     // Ajoute à l'index 1

        System.out.println("Contenu original : " + link);

        // Suppression d'éléments
        link.remove("Badr");        // Suppression par objet
        link.remove(2);             // Suppression par index

        System.out.println("Après suppression : " + link);

        // Suppression du premier et du dernier
        link.removeFirst();
        link.removeLast();

        System.out.println("Après suppression premier/dernier : " + link);

        // Modification d'un élément
        String val = link.get(2);
        link.set(2, val + " (modifié)");

        System.out.println("Après modification : " + link);
    }
}
Sortie
Contenu original : [Fatima, Haitam, Mostafa, Ismail, Dounia, Badr, Omar, Mohamed]
Après suppression : [Fatima, Haitam, Ismail, Dounia, Omar, Mohamed]
Après suppression premier/dernier : [Haitam, Ismail, Dounia, Omar]
Après modification : [Haitam, Ismail, Dounia (modifié), Omar]

5. LinkedList comme pile (Stack)

  Exemple 2 — Utilisation comme pile (LIFO)

import java.util.LinkedList;

public class Pile {
    public static void main(String[] args) {
        LinkedList<Integer> pile = new LinkedList<>();

        // Empiler (push)
        pile.addFirst(10);
        pile.addFirst(20);
        pile.addFirst(30);
        System.out.println("Pile après push : " + pile);

        // Dépiler (pop)
        int sommet = pile.removeFirst();
        System.out.println("Élément dépilé : " + sommet);
        System.out.println("Pile après pop : " + pile);

        // Consulter le sommet (peek)
        System.out.println("Sommet de la pile : " + pile.getFirst());
    }
}
Sortie
Pile après push : [30, 20, 10]
Élément dépilé : 30
Pile après pop : [20, 10]
Sommet de la pile : 20

6. LinkedList comme file (Queue)

  Exemple 3 — Utilisation comme file (FIFO)

import java.util.LinkedList;
import java.util.Queue;

public class File {
    public static void main(String[] args) {
        // Utilisation de l'interface Queue
        Queue<String> file = new LinkedList<>();

        // Enfiler (offer / add)
        file.offer("Client 1");
        file.offer("Client 2");
        file.offer("Client 3");
        System.out.println("File : " + file);

        // Défiler (poll / remove)
        String client = file.poll();
        System.out.println("Client traité : " + client);
        System.out.println("File après traitement : " + file);

        // Consulter la tête (peek)
        System.out.println("Prochain client : " + file.peek());
    }
}
Sortie
File : [Client 1, Client 2, Client 3]
Client traité : Client 1
File après traitement : [Client 2, Client 3]
Prochain client : Client 2

7. Parcours d'une LinkedList

  Exemple 4 — Différentes façons de parcourir

import java.util.LinkedList;
import java.util.Iterator;
import java.util.ListIterator;

public class Parcours {
    public static void main(String[] args) {
        LinkedList<String> langages = new LinkedList<>();
        langages.add("Java");
        langages.add("Python");
        langages.add("JavaScript");
        langages.add("C++");

        // 1. Boucle for classique
        System.out.println("=== Boucle for classique ===");
        for (int i = 0; i < langages.size(); i++) {
            System.out.println(langages.get(i));  // O(n) par accès
        }

        // 2. Boucle for-each (recommandée)
        System.out.println("\n=== Boucle for-each ===");
        for (String lang : langages) {
            System.out.println(lang);
        }

        // 3. Avec Iterator
        System.out.println("\n=== Iterator ===");
        Iterator<String> it = langages.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // 4. Avec ListIterator (parcours bidirectionnel)
        System.out.println("\n=== ListIterator (ordre inverse) ===");
        ListIterator<String> lit = langages.listIterator(langages.size());
        while (lit.hasPrevious()) {
            System.out.println(lit.previous());
        }

        // 5. Avec forEach() + lambda
        System.out.println("\n=== forEach() avec lambda ===");
        langages.forEach(lang -> System.out.println(lang));
    }
}
Performance du parcours

L'accès par index get(i) dans une LinkedList est en O(n) car il nécessite de parcourir la liste depuis le début. Pour parcourir tous les éléments, utilisez for-each, Iterator ou ListIterator qui sont en O(n) global.

8. ArrayList vs LinkedList — Quand utiliser quoi ?

Opération ArrayList LinkedList
Accès par index (get(i), set(i)) O(1) — très rapide O(n) — lent (parcours depuis la tête)
Ajout à la fin (add(e)) O(1) amorti (parfois redimensionnement) O(1)
Insertion au début (addFirst(e)) O(n) — décalage de tous les éléments O(1)
Insertion au milieu (add(i, e)) O(n) — décalage des éléments suivants O(n) — recherche de l'index + O(1)
Suppression au début (removeFirst()) O(n) — décalage O(1)
Suppression à la fin (removeLast()) O(1) O(1)
Suppression au milieu (remove(i)) O(n) — décalage O(n) — recherche de l'index
Mémoire par élément Faible (tableau contigu) Élevée (stockage des références prev/next)
Règle de choix
  • ArrayList : accès fréquent par index, peu d'insertions/suppressions au milieu.
  • LinkedList : nombreuses insertions/suppressions au début ou au milieu, pas besoin d'accès aléatoire rapide.
  • LinkedList comme pile ou file : idéale pour les structures LIFO (pile) ou FIFO (file).

9. Exemple complet — Gestion de playlist musicale

  Exemple 5 — Playlist avec navigation (liste doublement chaînée)

import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Scanner;

class Chanson {
    private String titre;
    private String artiste;
    private int duree;  // secondes

    public Chanson(String titre, String artiste, int duree) {
        this.titre = titre;
        this.artiste = artiste;
        this.duree = duree;
    }

    public String getTitre() { return titre; }
    public String getArtiste() { return artiste; }
    public int getDuree() { return duree; }

    @Override
    public String toString() {
        return String.format("%s - %s (%d:%02d)", artiste, titre, duree / 60, duree % 60);
    }
}

public class Playlist {
    private LinkedList<Chanson> playlist;
    private ListIterator<Chanson> iterator;
    private boolean avancer;

    public Playlist() {
        playlist = new LinkedList<>();
        iterator = playlist.listIterator();
        avancer = true;
    }

    public void ajouterChanson(Chanson chanson) {
        playlist.add(chanson);
        System.out.println("Ajouté : " + chanson);
    }

    public void ajouterApresCourante(Chanson chanson) {
        if (!playlist.isEmpty()) {
            if (avancer && iterator.hasNext()) {
                iterator.next();
            } else if (!avancer && iterator.hasPrevious()) {
                iterator.previous();
            }
            iterator.add(chanson);
            System.out.println("Ajouté après la chanson courante : " + chanson);
        } else {
            playlist.add(chanson);
            System.out.println("Ajouté à la playlist vide : " + chanson);
        }
    }

    public void chansonSuivante() {
        if (!avancer) {
            // Si on reculait, il faut avancer d'abord
            if (iterator.hasNext()) iterator.next();
            avancer = true;
        }
        if (iterator.hasNext()) {
            Chanson chanson = iterator.next();
            System.out.println("Lecture : " + chanson);
        } else {
            System.out.println("Fin de la playlist.");
        }
    }

    public void chansonPrecedente() {
        if (avancer) {
            if (iterator.hasPrevious()) iterator.previous();
            avancer = false;
        }
        if (iterator.hasPrevious()) {
            Chanson chanson = iterator.previous();
            System.out.println("Lecture : " + chanson);
        } else {
            System.out.println("Début de la playlist.");
        }
    }

    public void afficherPlaylist() {
        if (playlist.isEmpty()) {
            System.out.println("Playlist vide.");
            return;
        }
        System.out.println("\n=== PLAYLIST ===");
        for (int i = 0; i < playlist.size(); i++) {
            System.out.printf("%d. %s%n", i + 1, playlist.get(i));
        }
        System.out.println("Total : " + playlist.size() + " chanson(s)\n");
    }

    public void supprimerChanson(int index) {
        if (index >= 0 && index < playlist.size()) {
            Chanson supprimee = playlist.remove(index);
            System.out.println("✗ Supprimé : " + supprimee);
            // Réinitialiser l'itérateur
            iterator = playlist.listIterator();
            avancer = true;
        } else {
            System.out.println("Index invalide !");
        }
    }

    public static void main(String[] args) {
        Playlist playlist = new Playlist();
        Scanner scanner = new Scanner(System.in);
        int choix;

        // Ajout de quelques chansons par défaut
        playlist.ajouterChanson(new Chanson("Bohemian Rhapsody", "Queen", 355));
        playlist.ajouterChanson(new Chanson("Imagine", "John Lennon", 183));
        playlist.ajouterChanson(new Chanson("Hotel California", "Eagles", 391));

        do {
            System.out.println("\n╔════════════════════════════════════╗");
            System.out.println("║         LECTEUR MUSICAL            ║");
            System.out.println("╠════════════════════════════════════╣");
            System.out.println("║ 1. Lecture suivante                ║");
            System.out.println("║ 2. Lecture précédente              ║");
            System.out.println("║ 3. Ajouter une chanson             ║");
            System.out.println("║ 4. Ajouter après chanson courante  ║");
            System.out.println("║ 5. Supprimer une chanson           ║");
            System.out.println("║ 6. Afficher la playlist            ║");
            System.out.println("║ 7. Quitter                         ║");
            System.out.println("╚════════════════════════════════════╝");
            System.out.print("Votre choix : ");
            choix = scanner.nextInt();
            scanner.nextLine();

            switch (choix) {
                case 1:
                    playlist.chansonSuivante();
                    break;
                case 2:
                    playlist.chansonPrecedente();
                    break;
                case 3:
                    System.out.print("Titre : ");
                    String titre = scanner.nextLine();
                    System.out.print("Artiste : ");
                    String artiste = scanner.nextLine();
                    System.out.print("Durée (secondes) : ");
                    int duree = scanner.nextInt();
                    playlist.ajouterChanson(new Chanson(titre, artiste, duree));
                    break;
                case 4:
                    System.out.print("Titre : ");
                    titre = scanner.nextLine();
                    System.out.print("Artiste : ");
                    artiste = scanner.nextLine();
                    System.out.print("Durée (secondes) : ");
                    duree = scanner.nextInt();
                    playlist.ajouterApresCourante(new Chanson(titre, artiste, duree));
                    break;
                case 5:
                    playlist.afficherPlaylist();
                    System.out.print("Numéro à supprimer : ");
                    int index = scanner.nextInt() - 1;
                    playlist.supprimerChanson(index);
                    break;
                case 6:
                    playlist.afficherPlaylist();
                    break;
                case 7:
                    System.out.println("Au revoir !");
                    break;
                default:
                    System.out.println("Choix invalide !");
            }
        } while (choix != 7);

        scanner.close();
    }
}

10. Exercice

Historique de navigation

Niveau : Intermédiaire

Implémenter un système d'historique de navigation web utilisant LinkedList.

Travail demandé

  1. Créer une classe HistoriqueNavigation avec deux LinkedList<String> : une pour l'historique arrière, une pour l'historique avant.
  2. Implémenter les méthodes :
    • void visiter(String url) — ajoute une nouvelle page
    • String retour() — retourne à la page précédente
    • String avancer() — avance à la page suivante
    • void afficherPageCourante()
    • void afficherHistorique()
  3. Tester avec un menu console simulant la navigation.

  L'essentiel en bref

Synthèse — LinkedList
  • LinkedList est une implémentation de liste doublement chaînée en Java.
  • Chaque nœud contient une donnée et des références vers le nœud suivant et précédent.
  • Avantages : insertions/suppressions efficaces au début et à la fin (O(1)), implémente Deque (pile/file).
  • Inconvénients : accès par index lent (O(n)), consommation mémoire plus élevée.
  • Méthodes spécifiques : addFirst(), addLast(), removeFirst(), removeLast(), getFirst(), getLast().
  • Parcours recommandé : for-each, Iterator ou ListIterator (pas get(i) en boucle).
  • Cas d'usage : piles (LIFO), files (FIFO), historiques, gestionnaires de playlist, etc.
  • Choix ArrayList vs LinkedList : dépend des opérations dominantes (accès aléatoire vs insertions/suppressions).
Un peu d'histoire

La structure de liste chaînée a été inventée en 1955-1956 par Allen Newell, Cliff Shaw et Herbert Simon pour le langage IPL (Information Processing Language). En Java, LinkedList a été introduite dans Java 1.2 (1998) avec le Java Collections Framework. Contrairement à ArrayList, elle implémente à la fois List et Deque, ce qui en fait une structure polyvalente pour les piles et les files. L'implémentation Java utilise une liste doublement chaînée, ce qui permet une navigation efficace dans les deux sens avec ListIterator.

Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.