--- title: "Applications des piles, listes chaînées et files d'attente" date: "2024-12-09" --- # Rappel: les piles ## Qu'est-ce donc? . . . * tructure de données abstraite de type LIFO ## Quelles fonctionnalités? . . . 1. Empiler (push): ajouter un élément sur la pile. 2. Dépiler (pop): retirer l'élément du sommet de la pile et le retourner. 3. Pile vide? (is_empty?). # Le tri à deux piles \Huge Le tri à deux piles # Le tri à deux piles (1/3) ## Cas pratique {width=70%} # Le tri à deux piles (2/3) ## Exercice: formaliser l'algorithme . . . ## Algorithme de tri nécessitant 2 piles (G, D) Soit `tab` le tableau à trier: ```C pour i de 0 à N-1 tant que (tab[i] > que le sommet de G) dépiler G dans D tant que (tab[i] < que le sommet de D) dépiler de D dans G empiler tab[i] sur G dépiler tout D dans G tab est trié dans G ``` # Le tri à deux piles (3/3) ## Exercice: trier le tableau `[2, 10, 5, 20, 15]` ```C ``` # La Calculatrice \Huge La Calculatrice # La calculatrice (1/8) ## Vocabulaire ```C 2 + 3 = 2 3 +, ``` `2` et `3` sont les *opérandes*, `+` l'*opérateur*. . . . ## La notation infixe ```C 2 * (3 + 2) - 4 = 6. ``` ## La notation postfixe ```C 2 3 2 + * 4 - = 6. ``` ## Exercice: écrire `2 * 3 * 4 + 2` en notation `postfixe` . . . ```C 2 3 4 * * 2 + = (2 * (3 * 4)) + 2. ``` # La calculatrice (2/8) ## De infixe à post-fixe * Une *pile* est utilisée pour stocker *opérateurs* et *parenthèses*. * Les opérateurs on des *priorités* différentes. ```C ^ : priorité 3 * / : priorité 2 + - : priorité 1 ( ) : priorité 0 // pas un opérateur mais bon ``` # La calculatrice (3/8) ## De infixe à post-fixe: algorithme * On lit l'expression infixe de gauche à droite. * On examine le prochain caractère de l'expression infixe. * Si opérande, le placer dans l'expression du résultat. * Si parenthèse le mettre dans la pile (priorité 0). * Si opérateur, comparer sa priorité avec celui du sommet de la pile: * Si sa priorité est plus élevée, empiler. * Sinon dépiler l'opérateur de la pile dans l'expression du résultat et recommencer jusqu'à apparition d'un opérateur de priorité plus faible au sommet de la pile (ou pile vide). * Si parenthèse fermée, dépiler les opérateurs du sommet de la pile et les placer dans l'expression du résultat, jusqu'à ce qu'une parenthèse ouverte apparaisse au sommet, dépiler également la parenthèse. * Si il n'y a pas de caractère dans l'expression dépiler tous les opérateurs dans le résultat. # La calculatrice (4/8) ## De infixe à post-fixe: exemple ```C Infixe Postfixe Pile Priorité ((A*B)/D-F)/(G+H) Vide Vide Néant (A*B)/D-F)/(G+H) Vide ( 0 A*B)/D-F)/(G+H) Vide (( 0 *B)/D-F)/(G+H) A (( 0 B)/D-F)/(G+H) A ((* 2 )/D-F)/(G+H) AB ((* 2 /D-F)/(G+H) AB* ( 0 D-F)/(G+H) AB* (/ 2 -F)/(G+H) AB*D (/ 2 F)/(G+H) AB*D/ (- 1 )/(G+H) AB*D/F (- 1 /(G+H) AB*D/F- Vide Néant ``` # La calculatrice (5/8) ## De infixe à post-fixe: exemple ```C Infixe Postfixe Pile Priorité ((A*B)/D-F)/(G+H) Vide Vide Néant -------------------------------------------------------- /(G+H) AB*D/F- Vide Néant (G+H) AB*D/F- / 2 G+H) AB*D/F- /( 0 +H) AB*D/F-G /( 0 H) AB*D/F-G /(+ 1 ) AB*D/F-GH /(+ 1 Vide AB*D/F-GH+ / 2 Vide AB*D/F-GH+/ Vide Néant ``` # La calculatrice (6/8) \footnotesize ## Exercice: écrire le code et le poster sur matrix * Quelle est la signature de la fonction? . . . * Une sorte de corrigé: ```C char *infix_to_postfix(char* infix) { // init and alloc stack and postfix for (size_t i = 0; i < strlen(infix); ++i) { if (is_operand(infix[i])) { // we just add operands in the new postfix string } else if (infix[i] == '(') { // we push opening parenthesis into the stack } else if (infix[i] == ')') { // we pop everything into the postfix } else if (is_operator(infix[i])) { // this is an operator. We add it to the postfix based // on the priority of what is already in the stack and push it } } // pop all the operators from the s at the end of postfix // and end the postfix with `\0` return postfix; } ``` # La calculatrice (7/8) ## Évaluation d'expression postfixe: algorithme * Chaque *opérateur* porte sur les deux opérandes qui le précèdent. * Le *résultat d'une opération* est un nouvel *opérande* qui est remis au sommet de la pile. ## Exemple ```C 2 3 4 + * 5 - = ? ``` * On parcours de gauche à droite: ```C Caractère lu Pile opérandes 2 2 3 2, 3 4 2, 3, 4 + 2, (3 + 4) * 2 * 7 5 14, 5 - 14 - 5 = 9 ``` # La calculatrice (8/8) ## Évaluation d'expression postfixe: algorithme 1. La valeur d'un opérande est *toujours* empilée. 2. L'opérateur s'applique *toujours* au 2 opérandes au sommet. 3. Le résultat est remis au sommet. ## Exercice: écrire l'algorithme en C (et poster sur matrix) . . . ```C double evaluate(char *postfix) { // init stack for (size_t i = 0; i < strlen(postfix); ++i) { if (is_operand(postfix[i])) { stack_push(&s, postfix[i]); } else if (is_operator(postfix[i])) { double rhs = stack_pop(&s); double lhs = stack_pop(&s); stack_push(&s, op(postfix[i], lhs, rhs)); } } return stack_pop(&s); } ``` # Liste chaînée et pile \Huge Liste chaînée et pile # La liste chaînée et pile (1/6) ## Structure de données * Chaque élément de la liste contient: 1. une valeur, 2. un pointeur vers le prochain élément. * La pile est un pointeur vers le premier élément. {width=80%} # La liste chaînée et pile (2/6) ## Une pile-liste-chaînée ```C typedef struct _element { int data; struct _element *next; } element; typedef element* stack; ``` ## Fonctionnalités? . . . ```C void stack_create(stack *s); // *s = NULL; void stack_destroy(stack *s); void stack_push(stack *s, int val); void stack_pop(stack *s, int *val); void stack_peek(stack s, int *val); bool stack_is_empty(stack s); // reutrn NULL == stack; ``` # La liste chaînée et pile (3/6) ## Empiler? (faire un dessin) . . . ```C ``` ## Empiler? (le code ensemble) . . . ```C void stack_push(stack *s, int val) { element *elem = malloc(sizeof(*elem)); elem->data = val; elem->next = *s; *s = elem; } ``` # La liste chaînée et pile (4/6) ## Jeter un oeil? (faire un dessin) . . . ```C ``` ## Jeter un oeil? (le code ensemble) . . . ```C void stack_peek(stack s, int *val) { *val = s->data; } ``` # La liste chaînée et pile (5/6) ## Dépiler? (faire un dessin) . . . ```C ``` ## Dépiler? (le code ensemble) . . . ```C void stack_pop(stack *s, int *val) { stack_peek(*s, val); element *tmp = *s; *s = (*s)->next; free(tmp); } ``` # La liste chaînée et pile (6/6) ## Détruire? (faire un dessin) . . . ```C ``` ## Détruire? (le code ensemble) . . . ```C void stack_destroy(stack *s) { while (!stack_is_empty(*s)) { int val; stack_pop(s, &val); } } ``` # 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 liste vide. * Détruire une liste vide. # La file d'attente (2/2) \footnotesize ## Implémentation possible * La structure file, contient un pointeur vers la tête et un vers le début de la file. * Entre les deux, les éléments sont stockés dans une liste chaînée. {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 ## Creation 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): . . . {width=40%} 2. La file n'est pas vide (faire un dessin): . . . {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): . . . {width=80%} 2. La file un seul élément (faire un dessin): . . . {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` seraient les indices du tableau; * `capacity` seraient 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.  . . . * 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 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. . . . {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. . . . {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. . . . {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; } ```