MFormations
Modern Algorithms Engineering

Chapitre 3

03 — Structures de Données

> **Objectif** : Comprendre la distinction entre types abstraits (ADT) et implémentations concrètes, connaître les interfaces fondamentales et savoir choisir la structure adaptée à chaque besoin. ---

03 — Structures de Données : Cours complet

Niveau : Université / Ingénierie — Durée de lecture : ~50 min


Table des matières

  1. ADT vs implémentation
  2. Les interfaces fondamentales
  3. Contiguë vs chaînée
  4. Choix de structure : les critères
  5. Généralités sur l'indexation
  6. Analyse des trade-offs
  7. Diagrammes de décision
  8. Résumé

1. ADT vs implémentation

1.1 Définition

Un Abstract Data Type (ADT) décrit un comportement : un ensemble d'opérations et leurs contrats (pré/post conditions), sans spécifier la représentation interne.

Une implémentation est la structure de données concrète qui réalise l'ADT avec des coûts précis.

Diagramme en cours de génération...

1.2 Exemple fondateur : l'ADT « List »

NiveauContenu
ADT Listsize(), get(i), set(i, v), add(i, v), remove(i), indexOf(v)
Implémentation tableauaccès O(1), insertion/milieu O(n)
Implémentation liste chaînéeaccès O(n), insertion O(1) (si nœud connu)

Le point central : le code client dépend de l'interface, pas de l'implémentation. C'est la base du polymorphisme et des design patterns (Strategy, Adapter).

1.3 Pourquoi cette distinction est cruciale

  1. Indépendance : on peut changer d'implémentation sans toucher le code client.
  2. Analyse : les coûts sont attachés à l'implémentation, pas à l'interface.
  3. Standardisation : les langages fournissent des interfaces standard (Java List, C++ std::vector vs std::list).

1.4 En pratique dans les langages

LangageADTImplémentations
Pythonlisttableau dynamique uniquement
JavaList<T>ArrayList (tableau), LinkedList
C++concept SequenceContainerstd::vector, std::deque, std::list
Go[]Tslice (tableau dynamique)
TypeScriptT[]tableau dynamique

2. Les interfaces fondamentales

2.1 List

Collection ordonnée, adressable par index, avec doublons autorisés.

OpérationContrat
add(x) / add(i, x)insère en fin / à la position i
get(i) / set(i, x)lit / remplace à la position i
remove(i) / remove(x)retire par position / valeur
size()nombre d'éléments
indexOf(x)première position de x

Implémentations : tableau dynamique (O(1) accès, O(n) insert milieu), liste chaînée (O(n) accès, O(1) insertion si nœud).

2.2 Set

Collection sans doublons, sans notion d'ordre (ou ordre défini).

OpérationContrat
add(x)ajoute si absent (retourne false si présent)
contains(x)présence O(1) amorti (hash)
remove(x)retire si présent
union, intersection, differenceopérations ensemblistes

Implémentations : hash set (O(1) amorti), arbre de recherche équilibré (O(log n), ordonné).

2.3 Map (dictionnaire / associative array)

Association clé → valeur, clés uniques.

OpérationContrat
put(k, v)associe / remplace
get(k)valeur associée
containsKey(k)présence de la clé
remove(k)retire la paire

Implémentations : hash map, tree map (ordonné), tableau (si clés entières petites).

2.4 Stack (pile)

LIFO — Last In First Out.

OpérationContrat
push(x)empile
pop()retire le sommet
peek() / top()consulte le sommet sans retirer
isEmpty()test

2.5 Queue (file)

FIFO — First In First Out.

OpérationContrat
enqueue(x) / offer(x)ajoute en queue
dequeue() / poll()retire la tête
peek() / front()consulte la tête

2.6 Deque (double-ended queue)

Ajout et retrait aux deux extrémités : addFirst, addLast, removeFirst, removeLast.

2.7 Priority Queue (file de priorité)

Retire toujours l'élément de priorité extrême (min ou max).

OpérationContrat
insert(x) / push(x)ajoute avec priorité
extractMin() / pop()retire le minimum
peek() / getMin()consulte le minimum
decreaseKey(x, k)diminue la priorité

Implémentation canonique : tas binaire → O(log n) insert/extract, O(1) peek.

2.8 Synthèse des interfaces

Diagramme en cours de génération...

3. Contiguë vs chaînée

3.1 Les deux grandes familles

CaractéristiqueStockage contigu (tableau)Stockage chaîné (nœuds + pointeurs)
Mémoireun seul blocun nœud par élément + pointeurs
Accès indexéO(1)O(n)
Insertion/suppression au milieuO(n) (décalage)O(1) (si nœud en main)
Localité de cacheexcellentemauvaise (nœuds dispersés)
Coût mémoire par élémentminimal+1-2 pointeurs (8-16 octets)
Redimensionnementnécessaire (copie)naturel (pas de bloc)

3.2 Localité de cache : la vraie différence

Sur les machines modernes, la mémoire cache rend le coût d'accès mémoire non uniforme :

  • Tableau : accès séquentiels → préfetch → très rapide.
  • Liste chaînée : sauts de pointeur aléatoires → cache misses → 10-100× plus lent sur les grands volumes.

Règle pratique : quand les données tiennent en cache et que les accès sont séquentiels, le tableau gagne presque toujours. La liste chaînée ne gagne que pour des insertions/suppressions fréquentes au milieu avec nœud déjà localisé.

3.3 Exemple : parcourir 10⁶ éléments

StructureCoûtTemps typique
Tableau (accès séquentiels)O(n), cache-friendly~1 ms
Liste chaînée (parcours)O(n), cache-hostile~10-30 ms

Même complexité O(n), facteur 10-30 en pratique.


4. Choix de structure : les critères

4.1 Profil d'opérations

Le choix dépend des fréquences d'opérations de l'application :

  1. Accès par index fréquent → tableau.
  2. Recherche par valeur fréquente → hash set/map, ou arbre si ordre requis.
  3. Insertion/suppression au milieu fréquente → liste chaînée (nœud en main) ou skip list.
  4. Insertion/suppression aux extrémités → deque.
  5. Minimum/maximum fréquent → priority queue (heap).
  6. Parcours ordonné fréquent → arbre équilibré (AVL, rouge-noir).

4.2 La matrice de décision

Besoin dominantStructureCoût signature
Accès index O(1)Tableau dynamiqueget O(1), insert milieu O(n)
Insertion milieu O(1)Liste chaînéeget O(n), insert O(1)*
Recherche O(1)Hash tablecontains O(1) amorti
Min/max O(log n)Tas binaireextractMin O(log n)
Ordre + recherche O(log n)Arbre équilibrétoutes O(log n)
LIFOPile (tableau)push/pop O(1)
FIFOFile (circulaire)enqueue/dequeue O(1)

4.3 Exemple de raisonnement

Application : playlist d'un lecteur audio.

  • Ajout en fin : fréquent.
  • Lecture séquentielle : fréquente.
  • Suppression : rare.
  • Tableau dynamique : O(1) ajout fin, O(1) accès, bonne localité.

Application : historique de navigation (undo).

  • Ajout au sommet : fréquent.
  • Retrait du sommet : fréquent.
  • Pile (tableau suffit) : O(1) partout.

Application : index de recherche d'un moteur.

  • Recherche par mot-clé : très fréquente, volume énorme.
  • Hash map ou B-Tree (chapitre 06/07).

5. Généralités sur l'indexation

5.1 Indexer = transformer une recherche en accès

Rechercher par valeur coûte O(n) dans un tableau non trié. Les index accélèrent :

FamillePrincipeAccèsOrdre ?Mémoire
Index tableauclé = position (entier)O(1)nonO(n)
Index hashfonction de hachage → emplacementO(1) amortinonO(n)
Index arbreclés organisées en arbreO(log n)ouiO(n)

5.2 Exemples concrets

  • Base de données : index B+Tree (ordre + disque), index hash (égalité).
  • Caches : hash map clé → valeur.
  • Compilateurs : symbol table (hash map).
  • OS : inode table (index tableau) pour les fichiers.

5.3 Tableau → hash → arbre : le continuum

Diagramme en cours de génération...

Règle d'or : si vous avez besoin de parcourir dans l'ordre ou de faire des requêtes de plage (a ≤ x ≤ b), l'arbre gagne ; sinon le hash gagne.


6. Analyse des trade-offs

6.1 Le compromis temps/mémoire revisité

Diagramme en cours de génération...

6.2 Tableau récapitulatif des implémentations (worst-case, amorti)

Structuregetinsertcontainsmin/maxespace
Tableau (dynamique)O(1)O(n) au milieuO(n)O(n)O(n)
Liste chaînéeO(n)O(1)* nœudO(n)O(n)O(n)
Hash tableO(1) amortiO(1) amortiO(1) amortiO(n)O(n)
Arbre équilibréO(log n)O(log n)O(log n)O(log n)O(n)
Tas binaireO(log n)O(n)O(1) peekO(n)
Pile/File (tableau)O(1)O(1) amortiO(n)O(n)O(n)

6.3 Les pièges classiques du choix

  1. Choisir la liste chaînée « parce que insertion O(1) » sans avoir le nœud : l'insertion au milieu exige d'abord O(n) de recherche.
  2. Choisir le hash pour tout en oubliant le besoin d'ordre (parcours trié, range query).
  3. Oublier la mémoire : une hash map vide sur 1M de clés peut consommer plus qu'un tableau rempli.
  4. Confondre O(1) amorti et O(1) garanti : les rehashing coûteux peuvent gêner les systèmes temps réel.

7. Diagrammes de décision

7.1 Arbre de choix principal

Diagramme en cours de génération...

7.2 Choix selon la fréquence des opérations

Diagramme en cours de génération...

8. Résumé

  1. ADT = quoi (interface + contrat), implémentation = comment (coûts précis).
  2. Interfaces : List (index), Set (unicité), Map (clé→valeur), Stack (LIFO), Queue (FIFO), Deque (deux bouts), Priority Queue (min/max).
  3. Contigu vs chaîné : O(1) accès + cache vs O(1) insertion flexible + cache-hostile.
  4. Choisir selon le profil : fréquences d'opérations > intuition.
  5. Indexation : tableau (clé entière), hash (égalité, O(1)), arbre (ordre, O(log n), range queries).
  6. Pièges : insertion liste sans nœud = O(n), oubli de l'ordre, confusion amorti/garanti.
  7. Diagrammes Mermaid : arbres de décision guident le choix.

Exercices d'auto-évaluation

  1. Pourquoi ne peut-on pas « changer d'implémentation sans toucher le client » si on manipule un tableau brut ?
  2. Donner un ADT et deux implémentations de coûts différents.
  3. Une application lit T[i] 10⁶ fois/s et insère au milieu 10 fois/s : quelle structure ?
  4. Une application fait 10⁶ insertions au milieu/s et 10 lectures/s : quelle structure ?
  5. Pourquoi une hash map ne peut-elle pas répondre « liste les clés dans l'ordre » ?
  6. Citer 3 familles d'index et l'opération qu'elles accélèrent.

Passez au quiz puis aux TP.