-
paul.albuquer authoredpaul.albuquer authored
- Rappel: les piles
- Qu'est-ce donc?
- Quelles fonctionnalités?
- Le tri à deux piles
- Le tri à deux piles (1/3)
- Cas pratique
- Le tri à deux piles (2/3)
- Exercice: formaliser l'algorithme
- Algorithme de tri nécessitant 2 piles (G, D)
- Le tri à deux piles (3/3)
- Exercice: trier le tableau [2, 10, 5, 20, 15]
- La Calculatrice
- La calculatrice (1/8)
- Vocabulaire
- La notation infixe
- La notation postfixe
- Exercice: écrire 2 * 3 * 4 + 2 en notation postfixe
- La calculatrice (2/8)
- De infixe à post-fixe
- La calculatrice (3/8)
- De infixe à post-fixe: algorithme
- La calculatrice (4/8)
- De infixe à post-fixe: exemple
- La calculatrice (5/8)
- De infixe à post-fixe: exemple
- La calculatrice (6/8)
- Exercice: écrire le code et le poster sur matrix
- La calculatrice (7/8)
- Évaluation d'expression postfixe: algorithme
- Exemple
- La calculatrice (8/8)
- Évaluation d'expression postfixe: algorithme
- Exercice: écrire l'algorithme en C (et poster sur matrix)
- Liste chaînée et pile
- La liste chaînée et pile (1/6)
- Structure de données
- La liste chaînée et pile (2/6)
- Une pile-liste-chaînée
- Fonctionnalités?
- La liste chaînée et pile (3/6)
- Empiler? (faire un dessin)
- Empiler? (le code ensemble)
- La liste chaînée et pile (4/6)
- Jeter un oeil? (faire un dessin)
- Jeter un oeil? (le code ensemble)
- La liste chaînée et pile (5/6)
- Dépiler? (faire un dessin)
- Dépiler? (le code ensemble)
- La liste chaînée et pile (6/6)
- Détruire? (faire un dessin)
- Détruire? (le code ensemble)
- La file d'attente
- La file d'attente (1/2)
- Fonctionnalités
- La file d'attente (2/2)
- Implémentation possible
- Structure de données en C?
- Fonctionnalités d'une file d'attente
- Creation et consultations
- Manipulations et destruction
- Enfilage
- Deux cas différents:
- Enfilage
- Live (implémentation)
- Défilage
- Trois cas différents
- Défilage
- Live (implémentation)
- Problème avec cette implémentation?
- Destruction
- Comment on faire la désallocation?
- Complexité
- Quelle sont les complexité de:
- Implémentation alternative
- Comment implémenter la file autrement?
- Structure de données
- File basée sur un tableau
- Complexité
- Quelle sont les complexités de:
- Une file plus efficace
- Comment faire une file plus efficace?
- Solution?
- Une file plus efficace (implémentation)
- Enfilage
- Une file plus efficace (implémentation)
- Défilage
- Les listes triées
- Quelle structure de données?
- Les listes triées
- Quel but?
- Comment?
- Les listes triées
- Quelle structure de données dans notre cas?
- Fonctionnalités
- L'insertion
- Trois cas
- L'insertion
- L'insertion
cours_11.md 16.03 KiB
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?
. . .
- Empiler (push): ajouter un élément sur la pile.
- Dépiler (pop): retirer l'élément du sommet de la pile et le retourner.
- Pile vide? (is_empty?).
Le tri à deux piles
\Huge Le tri à deux piles
Le tri à deux piles (1/3)
Cas pratique
Le tri à deux piles (2/3)
Exercice: formaliser l'algorithme
. . .
Algorithme de tri nécessitant 2 piles (G, D)
Soit tab
le tableau à trier:
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)
[2, 10, 5, 20, 15]
Exercice: trier le tableau
La Calculatrice
\Huge La Calculatrice
La calculatrice (1/8)
Vocabulaire
2 + 3 = 2 3 +,
2
et 3
sont les opérandes, +
l'opérateur.
. . .
La notation infixe
2 * (3 + 2) - 4 = 6.
La notation postfixe
2 3 2 + * 4 - = 6.
2 * 3 * 4 + 2
en notation postfixe
Exercice: écrire . . .
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.
^ : 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
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
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é:
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
2 3 4 + * 5 - = ?
- On parcours de gauche à droite:
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
- La valeur d'un opérande est toujours empilée.
- L'opérateur s'applique toujours au 2 opérandes au sommet.
- Le résultat est remis au sommet.
Exercice: écrire l'algorithme en C (et poster sur matrix)
. . .
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:
- une valeur,
- un pointeur vers le prochain élément.
- La pile est un pointeur vers le premier élément.
La liste chaînée et pile (2/6)
Une pile-liste-chaînée
typedef struct _element {
int data;
struct _element *next;
} element;
typedef element* stack;
Fonctionnalités?
. . .
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)
. . .
Empiler? (le code ensemble)
. . .
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)
. . .
Jeter un oeil? (le code ensemble)
. . .
void stack_peek(stack s, int *val) {
*val = s->data;
}
La liste chaînée et pile (5/6)
Dépiler? (faire un dessin)
. . .
Dépiler? (le code ensemble)
. . .
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)
. . .
Détruire? (le code ensemble)
. . .
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.
Structure de données en 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
. . .
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
. . .
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:
- La file est vide (faire un dessin):
. . .
- La file n'est pas vide (faire un dessin):
. . .
Enfilage
Live (implémentation)
. . .
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
- La file a plus d'un élément (faire un dessin):
. . .
- La file un seul élément (faire un dessin):
. . .
- La file est vide (problème)
Défilage
Live (implémentation)
. . .
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
typedef struct _queue {
int *data;
int tail, capacity;
} queue;
File basée sur un tableau
- Initialisation?
. . .
- Est vide?
. . .
- Enfiler?
. . .
- Défiler?
. . .
Complexité
Quelle sont les complexités de:
- Initialisation?
. . .
- Est vide?
. . .
- Enfiler?
. . .
- Défiler?
. . .
Une file plus efficace
Comment faire une file plus efficace?
- Où est-ce que ça coince?
. . .
- Défiler est particulièrement lent .
Solution?
. . .
- Utiliser un indice séparé pour
head
.
typedef struct _queue {
int *data;
int head, tail, capacity;
} queue;
Une file plus efficace (implémentation)
Enfilage
\footnotesize
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
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?
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)!
typedef struct _element { // chaque élément
int data;
struct _element *next;
} element;
typedef element* sorted_list; // la liste
Fonctionnalités
// 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
- La liste est vide.
. . .
. . .
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
- L'insertion se fait en première position.
. . .
. . .
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
- L'insertion se fait sur une autre position que la première.
. . .
. . .
\footnotesize
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;
}