diff --git a/slides/cours_11.md b/slides/cours_11.md
index b8a0c54fc7f95a8ea76ae4a4803ae8f724bc5207..ec097a1affb1b62b3cd295428258d0d9399cc647 100644
--- a/slides/cours_11.md
+++ b/slides/cours_11.md
@@ -425,516 +425,3 @@ void stack_destroy(stack *s) {
 }
 ```
 
-# La file d'attente
-
-\Huge La file d'attente
-
-# La file d'attente (1/2)
-
-* Structure de données abstraite permettant le stockage d'éléments.
-* *FIFO*: First In First Out, ou première entrée première sortie.
-* Analogue de la vie "réelle"":
-    * File à un guichet,
-    * Serveur d'impressions,
-    * Mémoire tampon, ...
-
-## Fonctionnalités
- 
- . . .
-
-* Enfiler: ajouter un élément à la fin de la file.
-* Défiler: extraire un élément au devant de la file.
-* Tester si la file est vide.
-
-. . .
-
-* Lire l'élément de la fin de la file.
-* Lire l'élément du devant de la file.
-* Créer une file vide.
-* Détruire une file.
-
-# La file d'attente (2/2)
-
-\footnotesize
-
-## Implémentation possible
-
-* La structure de file, contient un pointeur vers la tête et un autre vers le début de la file.
-* Entre les deux, les éléments sont stockés dans une liste chaînée.
-
-![Illustration d'une file d'attente.](figs/fig_queue_representation.png){width=80%}
-
-## Structure de données en C?
-
-. . .
-
-```C
-typedef struct _element {  // Elément de liste
-   int data;
-   struct _element* next;
-} element;
-typedef struct _queue {    // File d'attente
-   element* head;  //    tête de file d'attente
-   element* tail;  //    queue de file d'attente
-} queue;
-```
-
-# Fonctionnalités d'une file d'attente
-
-## Création et consultations
-
-. . .
-
-```C
-void queue_init(queue *fa); // head = tail = NULL
-bool queue_is_empty(queue fa); // fa.head == fa.tail == NULL
-int queue_tail(queue fa); // return fa.tail->data
-int queue_head(queue fa); // return fa.head->data
-```
-
-## Manipulations et destruction
-
-. . .
-
-```C
-void queue_enqueue(queue *fa, int val);
-// adds an element before the tail
-int queue_dequeue(queue *fa);
-// removes the head and returns stored value
-void queue_destroy(queue *fa);
-// dequeues everything into oblivion
-```
-
-# Enfilage
-
-## Deux cas différents:
-
-1. La file est vide (faire un dessin):
-
-. . .
-
-![Insertion dans une file d'attente vide.](./figs/fig_empty_queue_insert.png){width=40%}
-
-2. La file n'est pas vide (faire un dessin):
-
-. . .
-
-![Insertion dans une file d'attente non-vide.](./figs/fig_non_empty_queue_insert.png){width=70%}
-
-# Enfilage
-
-## Live (implémentation)
-
-. . .
-
-```C
-void queue_enqueue(queue *fa, int val) {
-    element* elmt = malloc(sizeof(*elmt));
-    elmt->data = val;
-    elmt->next = NULL;
-    if (queue_is_empty(*fa)) {
-        fa->head = elmt;
-        fa->tail = elmt;
-    } else {
-        fa->tail->next = elmt;
-        fa->tail = elmt;
-    }
-}
-```
-
-# Défilage
-
-## Trois cas différents
-
-1. La file a plus d'un élément (faire un dessin):
-
-. . .
-
-![Extraction d'une file d'attente](./figs/fig_queue_extract.png){width=80%}
-
-2. La file un seul élément (faire un dessin):
-
-. . .
-
-![Extraction d'une file d'attente de longueur 1.](./figs/fig_queue_extract_one.svg){width=25%}
-
-
-3. La file est vide (problème)
-
-# Défilage
-
-## Live (implémentation)
-
-. . .
-
-```C
-int queue_dequeue(queue *fa) {
-    element* elmt = fa->head;
-    int val = elmt->data;
-    fa->head = fa->head->next;
-    free(elmt);
-    if (NULL == fa->head) {
-        fa->tail = NULL;
-    }
-    return val;
-}
-```
-
-. . .
-
-## Problème avec cette implémentation?
-
-# Destruction
-
-## Comment on faire la désallocation?
-
-. . .
-
-On défile jusqu'à ce que la file soit vide!
-
-# Complexité
-
-## Quelle sont les complexité de:
-
-* Enfiler?
-
-. . .
-
-* Défiler?
-
-. . .
-
-* Détruire?
-
-. . .
-
-* Est vide?
-
-
-# Implémentation alternative
-
-## Comment implémenter la file autrement?
-
-. . .
-
-* Données stockées dans un tableau;
-* Tableau de taille connue à la compilation ou pas (réallouable);
-* `tail` serait un indice du tableau;
-* `capacity` serait la capacité maximale;
-* On *enfile* "au bout" du tableau, au défile au début (indice `0`).
-
-. . .
-
-## Structure de données
-
-```C
-typedef struct _queue {
-    int *data;
-    int tail, capacity;
-} queue;
-```
-
-# File basée sur un tableau
-
-* Initialisation?
-
-. . .
-
-```C
-
-
-
-
-```
-
-* Est vide?
-
-. . .
-
-```C
-
-
-
-
-```
-
-
-* Enfiler?
-
-. . .
-
-```C
-
-
-
-
-```
-
-* Défiler?
-
-. . .
-
-```C
-
-
-
-
-```
-
-# Complexité
-
-## Quelle sont les complexités de:
-
-* Initialisation?
-
-. . .
-
-```C
-
-
-
-
-```
-
-* Est vide?
-
-. . .
-
-```C
-
-```
-
-
-* Enfiler?
-
-. . .
-
-```C
-
-
-
-
-```
-
-* Défiler?
-
-. . .
-
-```C
-
-
-
-
-```
-
-# Une file plus efficace
-
-## Comment faire une file plus efficace?
-
-* Où est-ce que ça coince?
-
-. . .
-
-* Défiler est particulièrement lent $\mathcal{O}(N)$.
-
-## Solution?
-
-. . .
-
-* Utiliser un indice séparé pour `head`.
-
-```C
-typedef struct _queue {
-    int *data;
-    int head, tail, capacity;
-} queue;
-```
-
-# Une file plus efficace (implémentation)
-
-## Enfilage
-
-\footnotesize
-
-```C
-void queue_enqueue(queue *fa, int val) {
-    if ((fa->head == 0 && fa->tail == fa->capacity-1) ||
-            (fa->tail == (fa->head-1) % (fa->capacity-1))) {
-        return; // queue is full
-    }
-    if (fa->head == -1) { // queue was empty
-        fa->head = fa->tail = 0;
-        fa->data[fa->tail] = val;
-    } else if (fa->tail == fa->capacity-1 && fa->head != 0) {
-        // the tail reached the end of the array
-        fa->tail = 0;
-        fa->data[fa->tail] = val;
-    } else {
-        // nothing particular
-        fa->tail += 1;
-        fa->data[fa->tail] = val;
-    }
-}
-```
-
-# Une file plus efficace (implémentation)
-
-## Défilage
-
-```C
-void queue_dequeue(queue *fa, int *val) {
-    if (queue_is_empty(*fa)) {
-        return; // queue is empty
-    }
-    *val = fa->data[fa->head];
-    if (fa->head == fa->tail) { // that was the last element
-        fa->head = fa->tail = -1;
-    } else if (fa->head == fa->capacity-1) {
-        fa->head = 0;
-    } else {
-        fa->head += 1;
-    }
-}
-```
-
-
-# Les listes triées
-
-Une liste chaînée triée est:
-
-* une liste chaînée
-* dont les éléments sont insérés dans l'ordre.
-
-![Exemple de liste triée.](./figs/sorted_list_example.svg)
-
-. . .
-
-* L'insertion est faite telle que l'ordre est maintenu.
-
-## Quelle structure de données?
-
-```C
-
-
-
-
-
-```
-
-# Les listes triées
-
-## Quel but?
-
-* Permet de retrouver rapidement un élément.
-* Utile pour la recherche de plus court chemin dans des graphes.
-* Ordonnancement de processus par degré de priorité.
-
-## Comment?
-
-* Les implémentations les plus efficaces se basent sur les tableaux.
-* Possibles aussi avec des listes chaînées.
-
-# Les listes triées
-
-\footnotesize
-
-## Quelle structure de données dans notre cas?
-
-
-Une liste chaînée bien sûr (oui c'est pour vous entraîner)!
-
-```C
-typedef struct _element { // chaque élément
-    int data;
-    struct _element *next;
-} element;
-typedef element* sorted_list; // la liste
-```
-
-## Fonctionnalités
-
-```C
-// insertion de val
-sorted_list sorted_list_push(sorted_list list, int val);
-// la liste est-elle vide?
-bool sorted_list_is_empty(sorted_list list); // list == NULL
-// extraction de val (il disparaît)
-sorted_list sorted_list_extract(sorted_list list, int val); 
- // rechercher un élément et le retourner
-element* sorted_list_search(sorted_list list, int val);
-```
-
-# L'insertion
-
-## Trois cas
-
-1. La liste est vide.
-
-. . .
-
-![Insertion dans une liste vide, `list == NULL`.](figs/sorted_list_insert_one.svg){width=30%}
-
-. . .
-
-```C
-sorted_list sorted_list_push(sorted_list list, int val) {
-    if (sorted_list_is_empty(list)) {
-        list = malloc(sizeof(*list));
-        list->data = val;
-        list->next = NULL;
-        return list;
-    }
-}
-```
-
-# L'insertion
-
-2. L'insertion se fait en première position.
-
-. . .
-
-![Insertion en tête de liste, `list->data >=
-val`.](figs/sorted_list_insert_first.svg){width=80%}
-
-. . .
-
-```C
-sorted_list sorted_list_push(sorted_list list, int val) {
-    if (list->data >= val) {
-        element *tmp = malloc(sizeof(*tmp));
-        tmp->data = val;
-        tmp->next = list;
-        list = tmp;
-        return list;
-    }
-}
-```
-
-# L'insertion
-
-3. L'insertion se fait sur une autre position que la première.
-
-. . .
-
-![Insertion sur une autre position, list->data <
-val.](figs/sorted_list_insert_any.svg){width=70%}
-
-. . .
-
-\footnotesize
-
-```C
-sorted_list sorted_list_push(sorted_list list, int val) {
-    element *tmp = malloc(sizeof(*tmp));
-    tmp->data = val;
-    element *crt = list;
-    while (NULL != crt->next && val > crt->next->data) {
-        crt = crt->next;
-    }
-    tmp->next = crt->next;
-    crt->next = tmp;
-    return list;
-}
-```
-
-
diff --git a/slides/cours_12.md b/slides/cours_12.md
new file mode 100644
index 0000000000000000000000000000000000000000..08cca9343e429715ed32c62d01ee8648aa55a244
--- /dev/null
+++ b/slides/cours_12.md
@@ -0,0 +1,792 @@
+---
+title: "File d'attente, liste triée, liste doublement chaînée"
+date: "2024-12-16"
+---
+
+# La file d'attente
+
+\Huge La file d'attente
+
+# La file d'attente (1/2)
+
+* Structure de données abstraite permettant le stockage d'éléments.
+* *FIFO*: First In First Out, ou première entrée première sortie.
+* Analogue de la vie "réelle"":
+    * File à un guichet,
+    * Serveur d'impressions,
+    * Mémoire tampon, ...
+
+## Fonctionnalités
+ 
+ . . .
+
+* Enfiler: ajouter un élément à la fin de la file.
+* Défiler: extraire un élément au devant de la file.
+* Tester si la file est vide.
+
+. . .
+
+* Lire l'élément de la fin de la file.
+* Lire l'élément du devant de la file.
+* Créer une file vide.
+* Détruire une file.
+
+# La file d'attente (2/2)
+
+\footnotesize
+
+## Implémentation possible
+
+* La structure de file, contient un pointeur vers la tête et un autre vers le début de la file.
+* Entre les deux, les éléments sont stockés dans une liste chaînée.
+
+![Illustration d'une file d'attente.](figs/fig_queue_representation.png){width=80%}
+
+## Structure de données en C?
+
+. . .
+
+```C
+typedef struct _element {  // Elément de liste
+   int data;
+   struct _element* next;
+} element;
+typedef struct _queue {    // File d'attente
+   element* head;  //    tête de file d'attente
+   element* tail;  //    queue de file d'attente
+} queue;
+```
+
+# Fonctionnalités d'une file d'attente
+
+## Création et consultations
+
+. . .
+
+```C
+void queue_init(queue *fa); // head = tail = NULL
+bool queue_is_empty(queue fa); // fa.head == fa.tail == NULL
+int queue_tail(queue fa); // return fa.tail->data
+int queue_head(queue fa); // return fa.head->data
+```
+
+## Manipulations et destruction
+
+. . .
+
+```C
+void queue_enqueue(queue *fa, int val);
+// adds an element before the tail
+int queue_dequeue(queue *fa);
+// removes the head and returns stored value
+void queue_destroy(queue *fa);
+// dequeues everything into oblivion
+```
+
+# Enfilage
+
+## Deux cas différents:
+
+1. La file est vide (faire un dessin):
+
+. . .
+
+![Insertion dans une file d'attente vide.](./figs/fig_empty_queue_insert.png){width=40%}
+
+2. La file n'est pas vide (faire un dessin):
+
+. . .
+
+![Insertion dans une file d'attente non-vide.](./figs/fig_non_empty_queue_insert.png){width=70%}
+
+# Enfilage
+
+## Live (implémentation)
+
+. . .
+
+```C
+void queue_enqueue(queue *fa, int val) {
+    element* elmt = malloc(sizeof(*elmt));
+    elmt->data = val;
+    elmt->next = NULL;
+    if (queue_is_empty(*fa)) {
+        fa->head = elmt;
+        fa->tail = elmt;
+    } else {
+        fa->tail->next = elmt;
+        fa->tail = elmt;
+    }
+}
+```
+
+# Défilage
+
+## Trois cas différents
+
+1. La file a plus d'un élément (faire un dessin):
+
+. . .
+
+![Extraction d'une file d'attente](./figs/fig_queue_extract.png){width=80%}
+
+2. La file un seul élément (faire un dessin):
+
+. . .
+
+![Extraction d'une file d'attente de longueur 1.](./figs/fig_queue_extract_one.svg){width=25%}
+
+
+3. La file est vide (problème)
+
+# Défilage
+
+## Live (implémentation)
+
+. . .
+
+```C
+int queue_dequeue(queue *fa) {
+    element* elmt = fa->head;
+    int val = elmt->data;
+    fa->head = fa->head->next;
+    free(elmt);
+    if (NULL == fa->head) {
+        fa->tail = NULL;
+    }
+    return val;
+}
+```
+
+. . .
+
+## Problème avec cette implémentation?
+
+# Destruction
+
+## Comment on faire la désallocation?
+
+. . .
+
+On défile jusqu'à ce que la file soit vide!
+
+# Complexité
+
+## Quelle sont les complexité de:
+
+* Enfiler?
+
+. . .
+
+* Défiler?
+
+. . .
+
+* Détruire?
+
+. . .
+
+* Est vide?
+
+
+# Implémentation alternative
+
+## Comment implémenter la file autrement?
+
+. . .
+
+* Données stockées dans un tableau;
+* Tableau de taille connue à la compilation ou pas (réallouable);
+* `tail` serait un indice du tableau;
+* `capacity` serait la capacité maximale;
+* On *enfile* "au bout" du tableau, au défile au début (indice `0`).
+
+. . .
+
+## Structure de données
+
+```C
+typedef struct _queue {
+    int *data;
+    int tail, capacity;
+} queue;
+```
+
+# File basée sur un tableau
+
+* Initialisation?
+
+. . .
+
+```C
+
+
+
+
+```
+
+* Est vide?
+
+. . .
+
+```C
+
+
+
+
+```
+
+
+* Enfiler?
+
+. . .
+
+```C
+
+
+
+
+```
+
+* Défiler?
+
+. . .
+
+```C
+
+
+
+
+```
+
+# Complexité
+
+## Quelle sont les complexités de:
+
+* Initialisation?
+
+. . .
+
+```C
+
+
+
+
+```
+
+* Est vide?
+
+. . .
+
+```C
+
+```
+
+
+* Enfiler?
+
+. . .
+
+```C
+
+
+
+
+```
+
+* Défiler?
+
+. . .
+
+```C
+
+
+
+
+```
+
+# Une file plus efficace
+
+## Comment faire une file plus efficace?
+
+* Où est-ce que ça coince?
+
+. . .
+
+* Défiler est particulièrement lent $\mathcal{O}(N)$.
+
+## Solution?
+
+. . .
+
+* Utiliser un indice séparé pour `head`.
+
+```C
+typedef struct _queue {
+    int *data;
+    int head, tail, capacity;
+} queue;
+```
+
+# Une file plus efficace (implémentation)
+
+## Enfilage
+
+\footnotesize
+
+```C
+void queue_enqueue(queue *fa, int val) {
+    if ((fa->head == 0 && fa->tail == fa->capacity-1) ||
+            (fa->tail == (fa->head-1) % (fa->capacity-1))) {
+        return; // queue is full
+    }
+    if (fa->head == -1) { // queue was empty
+        fa->head = fa->tail = 0;
+        fa->data[fa->tail] = val;
+    } else if (fa->tail == fa->capacity-1 && fa->head != 0) {
+        // the tail reached the end of the array
+        fa->tail = 0;
+        fa->data[fa->tail] = val;
+    } else {
+        // nothing particular
+        fa->tail += 1;
+        fa->data[fa->tail] = val;
+    }
+}
+```
+
+# Une file plus efficace (implémentation)
+
+## Défilage
+
+```C
+void queue_dequeue(queue *fa, int *val) {
+    if (queue_is_empty(*fa)) {
+        return; // queue is empty
+    }
+    *val = fa->data[fa->head];
+    if (fa->head == fa->tail) { // that was the last element
+        fa->head = fa->tail = -1;
+    } else if (fa->head == fa->capacity-1) {
+        fa->head = 0;
+    } else {
+        fa->head += 1;
+    }
+}
+```
+
+# Listes triées
+
+\Huge Les listes triées
+
+# Les listes triées
+
+Une liste chaînée triée est:
+
+* une liste chaînée
+* dont les éléments sont insérés dans l'ordre.
+
+![Exemple de liste triée.](./figs/sorted_list_example.svg)
+
+. . .
+
+* L'insertion est faite telle que l'ordre est maintenu.
+
+## Quelle structure de données?
+
+```C
+
+
+
+
+
+```
+
+# Les listes triées
+
+## Quel but?
+
+* Permet de retrouver rapidement un élément.
+* Utile pour la recherche de plus court chemin dans des graphes.
+* Ordonnancement de processus par degré de priorité.
+
+## Comment?
+
+* Les implémentations les plus efficaces se basent sur les tableaux.
+* Possibles aussi avec des listes chaînées.
+
+# Les listes triées
+
+\footnotesize
+
+## Quelle structure de données dans notre cas?
+
+
+Une liste chaînée bien sûr (oui c'est pour vous entraîner)!
+
+```C
+typedef struct _element { // chaque élément
+    int data;
+    struct _element *next;
+} element;
+typedef element* sorted_list; // la liste
+```
+
+## Fonctionnalités
+
+```C
+// insertion de val
+sorted_list sorted_list_push(sorted_list list, int val);
+// la liste est-elle vide?
+bool sorted_list_is_empty(sorted_list list); // list == NULL
+// extraction de val (il disparaît)
+sorted_list sorted_list_extract(sorted_list list, int val); 
+ // rechercher un élément et le retourner
+element* sorted_list_search(sorted_list list, int val);
+```
+
+# L'insertion (1/3)
+
+## Trois cas
+
+1. La liste est vide.
+
+. . .
+
+![Insertion dans une liste vide, `list == NULL`.](figs/sorted_list_insert_one.svg){width=30%}
+
+. . .
+
+```C
+sorted_list sorted_list_push(sorted_list list, int val) {
+    if (sorted_list_is_empty(list)) {
+        list = malloc(sizeof(*list));
+        list->data = val;
+        list->next = NULL;
+        return list;
+    }
+}
+```
+
+# L'insertion (2/3)
+
+2. L'insertion se fait en première position.
+
+. . .
+
+![Insertion en tête de liste, `list->data >=
+val`.](figs/sorted_list_insert_first.svg){width=80%}
+
+. . .
+
+```C
+sorted_list sorted_list_push(sorted_list list, int val) {
+    if (list->data >= val) {
+        element *tmp = malloc(sizeof(*tmp));
+        tmp->data = val;
+        tmp->next = list;
+        list = tmp;
+        return list;
+    }
+}
+```
+
+# L'insertion (3/3)
+
+3. L'insertion se fait sur une autre position que la première.
+
+. . .
+
+![Insertion sur une autre position, list->data < val.](figs/sorted_list_insert_any.svg){width=70%}
+
+. . .
+
+\footnotesize
+
+```C
+sorted_list sorted_list_push(sorted_list list, int val) {
+    element *tmp = malloc(sizeof(*tmp));
+    tmp->data = val;
+    element *crt = list;
+    while (NULL != crt->next && val > crt->next->data) {
+        crt = crt->next;
+    }
+    tmp->next = crt->next;
+    crt->next = tmp;
+    return list;
+}
+```
+
+# L'extraction (1/3)
+
+## Trois cas
+
+1. L'élément à extraire n'est **pas** le premier élément de la liste
+
+. . .
+
+![Extraction d'un élément qui n'est pas le premier.](figs/sorted_list_extract_any.svg){width=70%}
+
+. . .
+
+\scriptsize
+
+```C
+sorted_list sorted_list_extract(sorted_list list, int val) {
+    element *prec = *crt = list; // needed to glue elements together
+    while (NULL != crt && val > crt->data) {
+	   prec = crt;
+	   crt = crt->next;
+	}
+    if (NULL != crt && prec != crt && crt->data == val) { // glue things together
+        prec->next = crt->next;
+        free(crt);
+    }
+    return list;
+}
+```
+
+
+# L'extraction (2/3)
+
+2. L'élément à extraire est le premier élément de la liste
+
+. . .
+
+![Extraction d'un élément qui est le premier.](figs/sorted_list_extract_first.svg){width=70%}
+
+. . .
+
+\footnotesize
+
+```C
+sorted_list sorted_list_extract(sorted_list list, int val) {
+    element *prec = *crt = list; // needed to glue elements together
+    while (NULL != crt && val > crt->data) {
+	   prec = crt;
+	   crt = crt->next;
+	}
+    // glue things together
+    if (NULL != crt && crt->data == val && prec == crt) {
+        list = list->next;
+        free(crt);
+    }
+    return list;
+}
+```
+
+# L'extraction (3/3)
+
+3. L'élément à extraire n'est **pas** dans la liste.
+    * La liste est vide.
+    * La valeur est plus grande que le dernier élément de la liste.
+    * La valeur est plus petite que la valeur de `crt`.
+
+. . .
+
+On retourne la liste inchangée.
+
+. . .
+
+\footnotesize
+
+```C
+sorted_list sorted_list_extract(sorted_list list, int val) {
+    element *prec = *crt = list; // needed to glue elements together
+    while (NULL != crt && val > crt->data) {
+	   prec = crt;
+	   crt = crt->next;
+	}
+    if (NULL == crt || crt->data != val) { // val not present
+        return list;
+    }
+}
+```
+
+# La recherche
+
+
+
+```C
+element* sorted_list_search(sorted_list list, int val);
+```
+
+* Retourne `NULL` si la valeur n'est pas présente (ou la liste vide).
+* Retourne un pointeur vers l'élément si la valeur est présente.
+
+. . .
+
+```C
+element* sorted_list_search(sorted_list list, int val) {
+    // search for element smaller than val
+    element* pos = sorted_list_position(list, val); 
+    if (NULL == pos && val == list->data) {
+        return list; // first element contains val
+    } else if (NULL != pos && NULL != pos->next 
+        && val == pos->next->data) 
+    {
+        return pos->next; // non-first element contains val
+    } else {
+        return NULL; // well... val's not here
+    }
+}
+```
+
+# La recherche
+
+## La fonction `sorted_list_position`
+
+```C
+element* sorted_list_position(sorted_list list, int val);
+```
+
+![Trois exemples de retour de la fonction `sorted_list_position()`.](figs/sorted_list_position.svg)
+
+# La recherche
+
+## Exercice: implémenter
+
+```C
+element* sorted_list_position(sorted_list list, int val);
+```
+
+. . .
+
+```C
+element* sorted_list_position(sorted_list list, int val) {
+    element* pos = list;
+    if (sorted_list_is_empty(list) || val <= list->data) {
+        pos = NULL;
+    } else {
+        while (NULL != pos->next && val > pos->next->data) {
+            pos = pos->next;
+        }
+    }
+    return pos;
+}
+```
+
+# Complexité de la liste chaînée triée
+
+## L'insertion?
+
+. . .
+
+$$
+\mathcal{O}(N).
+$$
+
+## L'extraction?
+
+. . .
+
+$$
+\mathcal{O}(N).
+$$
+
+## La recherche?
+
+. . .
+
+$$
+\mathcal{O}(N).
+$$
+
+
+# Liste doublement chaînée
+
+## Application: navigateur ou éditeur de texte
+
+* Avec une liste chaînée:
+    * Comment implémenter les fonctions `back` et `forward` d'un navigateur?
+    * Comment implémenter les fonctions `undo` et `redo` d'un éditeur de texte?
+
+. . .
+
+Pas possible.
+
+## Solution?
+
+. . .
+
+* Garder un pointeur supplémentaire sur l'élément précédent et pas seulement le
+  suivant.
+
+. . .
+
+* Cette structure de donnée est la **liste doublement chaînée** ou **doubly
+  linked list**.
+
+# Liste doublement chaînée
+
+\Huge Liste doublement chaînée
+
+# Liste doublement chaînée
+
+## Exercices
+
+* Partir du dessin suivant et par **groupe de 5**
+
+![Un schéma de liste doublement chaînée d'entiers.](figs/doubly_linked_list.svg)
+
+1. Écrire les structures de données pour représenter la liste doublement
+   chaînée dont le type sera `dll` (pour
+   `doubly_linked_list`)
+
+# Liste doublement chaînée
+
+2. Écrire les fonctionnalités de création et consultation
+
+```C 
+// crée la liste doublement chaînée
+dll dll_create();
+// retourne la valeur à la position actuelle dans la liste
+int dll_value(dll list);
+// la liste est-elle vide?
+bool dll_is_empty(dll list);
+// Est-ce que pos est le 1er élément?
+bool dll_is_head(dll list);
+// Est-ce que pos est le dernier élément?
+bool dll_is_tail(dll list);
+// data est-elle dans la liste?
+bool dll_is_present(dll list, int data);
+// affiche la liste
+void dll_print(dll list);
+```
+
+# Liste doublement chaînée
+
+3. Écrire les fonctionnalités de manipulation 
+
+```C 
+// déplace pos au début de la liste
+dll dll_move_to_head(dll list);
+// déplace pos à la position suivante dans la liste
+dll dll_next(dll list);
+// déplace pos à la position précédente dans la liste
+dll dll_prev(dll list);
+```
+
+# Liste doublement chaînée
+
+4. Écrire les fonctionnalités d'insertion
+
+```C
+// insertion de data dans l'élément après pos
+dll dll_insert_after(dll list, int data);
+// insertion de data en tête de liste
+dll dll_push(dll list, int data);
+```
+
+5. Écrire les fonctionnalités d'extraction
+
+```C
+// extraction de la valeur se trouvant dans l'élément pos
+// l'élément pos est libéré
+int dll_extract(dll *list);
+// extrait la donnée en tête de liste
+int dll_pop(dll *list);
+// vide la liste
+void dll_destroy(dll *list);
+```
+