🗂️

Structures de données

Listes, piles, files, dictionnaires et listes chaînées

Structures Python Complexité O(1) / O(n)

🎯 Introduction

Les structures de données permettent d'organiser et de stocker les informations en mémoire. Le choix d'une structure adaptée est crucial pour l'efficacité d'un algorithme.

✅ Ce qu'il faut absolument savoir pour le bac

  • Distinguer les structures et leurs propriétés (LIFO, FIFO, accès par clé)
  • Connaître les complexités O(1) / O(n) de chaque opération
  • Implémenter une pile et une file avec une liste Python
  • Utiliser les dictionnaires Python (création, accès, test d'appartenance)
  • Comprendre les listes chaînées (nœuds, pointeurs)
  • Savoir choisir la bonne structure selon le problème

📋 Listes et tableaux

En Python, le type list est un tableau dynamique : les éléments sont stockés de façon contiguë en mémoire, et Python gère automatiquement le redimensionnement.

OpérationComplexitéExemple
Accès par indiceO(1)tab[i]
Ajout en finO(1) amortitab.append(x)
Insertion en débutO(n)tab.insert(0, x)
Suppression en finO(1)tab.pop()
Recherche d'une valeurO(n)x in tab
LongueurO(1)len(tab)
# Création et opérations courantes
tab = [3, 1, 4, 1, 5]
tab.append(9)         # [3,1,4,1,5,9]
tab.insert(0, 0)      # [0,3,1,4,1,5,9] — O(n) !
tab.pop()             # retire 9, O(1)
tab.pop(0)            # retire 0, O(n) !

# Compréhension de liste
carres = [x**2 for x in range(5)]  # [0,1,4,9,16]
pairs  = [x for x in tab if x % 2 == 0]

📚 Piles — LIFO

Une pile (stack) suit le principe LIFO : Last In, First Out (dernier entré, premier sorti). Comme une pile d'assiettes.

💡

Cas d'usage réels

Pile d'appels de fonctions (récursion), annulation dans un éditeur (Ctrl+Z), navigation web (bouton Retour), vérification des parenthèses.

# Implémentation d'une pile avec une liste Python
pile = []

# Empiler (push)
pile.append('A')   # pile = ['A']
pile.append('B')   # pile = ['A','B']
pile.append('C')   # pile = ['A','B','C']

# Dépiler (pop) — retire le DERNIER élément
sommet = pile.pop()   # sommet = 'C', pile = ['A','B']

# Consulter sans dépiler (peek)
if pile:              # vérifier non-vide avant !
    sommet = pile[-1]  # 'B' sans modifier la pile

# Classe Pile propre
class Pile:
    def __init__(self): self._data = []
    def est_vide(self): return len(self._data) == 0
    def empiler(self, x): self._data.append(x)
    def depiler(self):
        if self.est_vide(): raise IndexError("pile vide")
        return self._data.pop()
    def sommet(self): return self._data[-1]

🚌 Files — FIFO

Une file (queue) suit le principe FIFO : First In, First Out (premier entré, premier sorti). Comme une file d'attente.

⚠️

N'utilise pas une simple liste Python pour une file !

list.insert(0, x) est O(n) car il décale tous les éléments. Utilise collections.deque pour des opérations O(1) aux deux extrémités.

from collections import deque

file = deque()          # file vide

# Enfiler (enqueue) — ajouter en queue
file.append('A')         # file = deque(['A'])
file.append('B')         # file = deque(['A','B'])

# Défiler (dequeue) — retirer en tête — O(1) !
premier = file.popleft() # premier = 'A'

# Classe File propre
class File:
    def __init__(self): self._data = deque()
    def est_vide(self): return len(self._data) == 0
    def enfiler(self, x): self._data.append(x)
    def defiler(self):
        if self.est_vide(): raise IndexError("file vide")
        return self._data.popleft()

🗝️ Dictionnaires

Un dictionnaire (table de hachage) associe des clés à des valeurs. Les clés doivent être hashables (immuables : str, int, tuple).

OpérationComplexitéExemple
Accès/ModificationO(1) moy.d[cle]
Test d'appartenanceO(1) moy.cle in d
InsertionO(1) moy.d[cle] = val
SuppressionO(1) moy.del d[cle]
ParcoursO(n)for k, v in d.items()
# Création
notes = {'Alice': 18, 'Bob': 14, 'Chloe': 16}

# Accès (KeyError si absent → utiliser get)
n = notes.get('David', 0)   # renvoie 0 par défaut

# Test d'appartenance
if 'Alice' in notes:
    print(notes['Alice'])      # 18

# Parcours
for nom, note in notes.items():
    print(f"{nom} : {note}")

# Compréhension de dictionnaire
maj = {k.upper(): v for k, v in notes.items()}

# setdefault / defaultdict
from collections import defaultdict
freq = defaultdict(int)
for c in "bonjour": freq[c] += 1
✅

Depuis Python 3.7

Les dictionnaires préservent l'ordre d'insertion. C'est garanti par le langage.

🔗 Listes chaînées

Dans une liste chaînée, chaque élément (nœud) contient une valeur et un pointeur vers le nœud suivant. Contrairement aux tableaux, les éléments ne sont pas contigus en mémoire.

OpérationListe chaînéeTableau Python
Accès par indiceO(n)O(1)
Insertion en têteO(1)O(n)
Insertion en queueO(1) si pointeur finO(1) amorti
Suppression en têteO(1)O(n)
RechercheO(n)O(n)
class Noeud:
    def __init__(self, valeur):
        self.valeur = valeur
        self.suivant = None

class ListeChainee:
    def __init__(self):
        self.tete = None

    def inserer_tete(self, val):         # O(1)
        n = Noeud(val)
        n.suivant = self.tete
        self.tete = n

    def parcourir(self):
        courant = self.tete
        while courant is not None:
            print(courant.valeur)
            courant = courant.suivant
🚨

⚠️ Pièges classiques au bac

  • pop() sur une liste vide → IndexError ! Toujours tester if pile: avant.
  • list.insert(0, x) est O(n), pas O(1). Pour une file, utiliser deque.
  • Un dictionnaire avec doublon de clé : {'a':1,'a':2} → {'a':2}. La deuxième valeur écrase.
  • Les listes sont mutables : b = a ne copie pas ! Utiliser b = a.copy() ou b = a[:].
  • L'accès dict par clé absente → KeyError. Toujours utiliser d.get(cle) ou cle in d.

🏋️ Exercices

Facile Vérification de parenthèses

Écrire une fonction parentheses_ok(s) qui renvoie True si les parenthèses de la chaîne s sont bien équilibrées, False sinon. Exemple : "(a+(b*c))" → True, "(()" → False.

✅ Correction
def parentheses_ok(s):
    pile = []
    for c in s:
        if c == '(':
            pile.append(c)
        elif c == ')':
            if not pile:
                return False  # fermante sans ouvrante
            pile.pop()
    return len(pile) == 0  # vrai si tout est fermé

On utilise une pile : on empile chaque ( et on dépile à chaque ). Si on dépile sur une pile vide → faux. À la fin, la pile doit être vide.

Intermédiaire File avec deux piles

Implémenter une file (FIFO) en utilisant uniquement deux piles. Les opérations enfiler(x) et defiler() doivent être correctes.

✅ Correction
class FileAvecDeuxPiles:
    def __init__(self):
        self.entree = []   # pile pour enfiler
        self.sortie = []   # pile pour défiler

    def enfiler(self, x):
        self.entree.append(x)

    def defiler(self):
        if not self.sortie:  # si sortie vide, transvaser
            while self.entree:
                self.sortie.append(self.entree.pop())
        if not self.sortie:
            raise IndexError("file vide")
        return self.sortie.pop()

Astuce : on transvase la pile d'entrée dans la pile de sortie quand elle est vide. Cela inverse l'ordre, donnant l'effet FIFO. Complexité amortie O(1).

Niveau bac Fréquence de mots

Écrire une fonction frequences(texte) qui prend une chaîne de caractères et renvoie un dictionnaire associant chaque mot à son nombre d'occurrences. Les mots sont séparés par des espaces, en ignorant la casse.

✅ Correction
def frequences(texte):
    freq = {}
    for mot in texte.lower().split():
        freq[mot] = freq.get(mot, 0) + 1
    return freq

# Variante avec defaultdict
from collections import defaultdict
def frequences2(texte):
    freq = defaultdict(int)
    for mot in texte.lower().split():
        freq[mot] += 1
    return dict(freq)

📌 Fiche synthèse

LISTE (tableau)

  • Accès[i] → O(1)
  • append → O(1) amorti
  • insert(0,x) → O(n)
  • Recherche → O(n)

PILE (LIFO)

  • push = append() → O(1)
  • pop() → O(1)
  • peek = tab[-1] → O(1)
  • Usage : récursion, Ctrl+Z

FILE (FIFO)

  • enqueue = append() → O(1)
  • dequeue = popleft() → O(1)
  • Utiliser deque !
  • Usage : BFS, impression

DICTIONNAIRE

  • Accès/Insert → O(1) moy.
  • Clé in dict → O(1)
  • Clés hashables
  • Ordre depuis Python 3.7

🧠 QCM

Choisissez le nombre de questions et le niveau, puis lancez.

← Tous les chapitres Chapitre suivant : S02 — Bases de données →