From 3cdf061bd30c422bee9f5206ec930776a11d6b19 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Sun, 17 Sep 2023 22:49:27 +0200 Subject: [PATCH] cours 1 updated --- slides/cours_10.md | 402 -------------- slides/cours_11.md | 514 ------------------ slides/cours_12.md | 409 -------------- slides/cours_13.md | 605 --------------------- slides/cours_14.md | 367 ------------- slides/cours_15.md | 929 -------------------------------- slides/cours_16.md | 1277 -------------------------------------------- slides/cours_17.md | 695 ------------------------ slides/cours_18.md | 947 -------------------------------- slides/cours_19.md | 488 ----------------- slides/cours_2.md | 317 ----------- slides/cours_20.md | 795 --------------------------- slides/cours_21.md | 731 ------------------------- slides/cours_22.md | 351 ------------ slides/cours_23.md | 1013 ----------------------------------- slides/cours_24.md | 709 ------------------------ slides/cours_25.md | 1077 ------------------------------------- slides/cours_3.md | 339 ------------ slides/cours_4.md | 851 ----------------------------- slides/cours_5.md | 834 ----------------------------- slides/cours_6.md | 488 ----------------- slides/cours_7.md | 295 ---------- slides/cours_8.md | 281 ---------- slides/cours_9.md | 304 ----------- 24 files changed, 15018 deletions(-) delete mode 100644 slides/cours_10.md delete mode 100644 slides/cours_11.md delete mode 100644 slides/cours_12.md delete mode 100644 slides/cours_13.md delete mode 100644 slides/cours_14.md delete mode 100644 slides/cours_15.md delete mode 100644 slides/cours_16.md delete mode 100644 slides/cours_17.md delete mode 100644 slides/cours_18.md delete mode 100644 slides/cours_19.md delete mode 100644 slides/cours_2.md delete mode 100644 slides/cours_20.md delete mode 100644 slides/cours_21.md delete mode 100644 slides/cours_22.md delete mode 100644 slides/cours_23.md delete mode 100644 slides/cours_24.md delete mode 100644 slides/cours_25.md delete mode 100644 slides/cours_3.md delete mode 100644 slides/cours_4.md delete mode 100644 slides/cours_5.md delete mode 100644 slides/cours_6.md delete mode 100644 slides/cours_7.md delete mode 100644 slides/cours_8.md delete mode 100644 slides/cours_9.md diff --git a/slides/cours_10.md b/slides/cours_10.md deleted file mode 100644 index 5af82c0..0000000 --- a/slides/cours_10.md +++ /dev/null @@ -1,402 +0,0 @@ ---- -title: "Piles" -date: "2022-12-14" -patat: - eval: - tai: - command: fish - fragment: false - replace: true - ccc: - command: fish - fragment: false - replace: true - images: - backend: auto ---- - -# Rappel - -## Qu'est-ce qu'une pile? - -. . . - -* Structure de données 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 retrouner. -3. Liste vide? (is_empty?). -4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler). -5. Nombre d'éléments (length). - -# Le tri à deux piles (3/3) - -## Exercice: trier le tableau `[2, 10, 5, 20, 15]` - -```C - - - - - - - - - - - - - - - - -``` - -# 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 -bool evaluate(char *postfix, double *val) { // 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); -} -``` - - - -# 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); - return val; -} -``` - -# 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); - } -} -``` - - diff --git a/slides/cours_11.md b/slides/cours_11.md deleted file mode 100644 index 13e2536..0000000 --- a/slides/cours_11.md +++ /dev/null @@ -1,514 +0,0 @@ ---- -title: "Files d'attente et listes triées" -date: "2022-12-21" ---- - -# La file d'attente (1/N) - -* 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/N) - -\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.head->data -int queue_head(queue fa); // return fa.tail->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; -} -``` - - diff --git a/slides/cours_12.md b/slides/cours_12.md deleted file mode 100644 index c9646d2..0000000 --- a/slides/cours_12.md +++ /dev/null @@ -1,409 +0,0 @@ ---- -title: "Listes triées et listes doublement chaînées" -date: "2023-01-11" ---- - -# 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 - -## 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; -} -``` - - -# L'extraction - -## Trois cas - -1. L'élément à extraire n'est **pas** le premier élément de la liste - -. . . - -{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. L'élément à extraire est le premier élément de la liste - -. . . - -{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; - } - if (NULL != crt && crt->data == val && prec == crt) { // glue things together - list = list->next; - free(crt); - } - return list; -} -``` - -# L'extraction - -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); -``` - - - -# 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 - -## Exercices - -* Partir du dessin suivant et par **groupe de 5** - - - -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) -``` - diff --git a/slides/cours_13.md b/slides/cours_13.md deleted file mode 100644 index d6d3d13..0000000 --- a/slides/cours_13.md +++ /dev/null @@ -1,605 +0,0 @@ ---- -title: "Tables de hachage" -date: "2023-01-18" ---- - -# Tableau vs Table - -## Tableau - -* Chaque élément (ou valeur) est lié à un indice (la case du tableau). - -```C -annuaire tab[2] = { - "+41 22 123 45 67", "+41 22 234 56 78", ... -}; -tab[1] == "+41 22 123 45 67"; -``` - -## Table - -* Chaque élément (ou valeur) est lié à une clé. - -```C -annuaire tab = { -// Clé , Valeur - "Paul", "+41 22 123 45 67", - "Orestis", "+41 22 234 56 78", -}; -tab["Paul"] == "+41 22 123 45 67"; -tab["Orestis"] == "+41 22 234 56 78"; -``` - -# Table - -## Définition - -Structure de données abstraite où chaque *valeur* (ou élément) est associée à une *clé* (ou -argument). - -On parle de paires *clé-valeur* (*key-value pairs*). - -## Donnez des exemples de telles paires - -. . . - -* Annuaire (nom-téléphone), -* Catalogue (objet-prix), -* Table de valeur fonctions (nombre-nombre), -* Index (nombre-page) -* ... - -# Table - -## Opérations principales sur les tables - -* Insertion d'élément (`insert(clé, valeur)`{.C}), insère la paire `clé-valeur` -* Consultation (`get(clé)`{.C}), retourne la `valeur` correspondant à `clé` -* Suppression (`remove(clé)`{.C}), supprime la paire `clé-valeur` - -## Structure de données / implémentation - -Efficacité dépend de différents paramètres: - -* taille (nombre de clé-valeurs maximal), -* fréquence d'utilisation (insertion, consultation, suppression), -* données triées/non-triées, -* ... - -# Consultation séquentielle (`sequential_get`) - -## Séquentielle - -* table représentée par un (petit) tableau ou liste chaînée, -* types: `key_t` et `value_t` quelconques, et `key_value_t` - - ```C - typedef struct { - key_t key; - value_t value; - } key_value_t; - ``` -* on recherche l'existence de la clé séquentiellement dans le tableau, on - retourne la valeur. - -# Consultation séquentielle (`sequential_get`) - -## Implémentation? Une idée? - -. . . - -```C -bool sequential_get(int n, key_value_t table[n], key_t key, - value_t *value) -{ - int pos = n - 1; - while (pos >= 0) { - if (key == table[pos].key) { - *value = table[pos].value; - return true; - } - pos--; - } - return false; -} -``` - -. . . - -## Inconvénient? - -# Consultation séquentielle (`sequential_get`) - -## Exercice: implémenter la même fonction avec une liste chaînée - -Poster le résultat sur matrix. - -# Consultation dichotomique (`binary_get`) - -## Dichotomique - -* table représentée par un (petit) tableau trié par les clés, -* types: `key_t` et `value_t` quelconques, et `key_value_t` -* on recherche l'existence de la clé par dichotomie dans le tableau, on - retourne la valeur, -* les clés possèdent la notion d'ordre (`<, >, =` sont définis). - -# Consultation dichotomique (`binary_get`) - -\footnotesize - -## Implémentation? Une idée? - -. . . - -```C -bool binary_get1(int n, value_key_t table[n], key_t key, value_t *value) { - int top = n - 1, bottom = 0; - while (top > bottom) { - int middle = (top + bottom) / 2; - if (key > table[middle].key) { - bottom = middle+1; - } else { - top = middle; - } - } - if (key == table[top].key) { - *value = table[top].value; - return true; - } else { - return false; - } -} -``` - -# Consultation dichotomique (`binary_get`) - -\footnotesize - -## Autre implémentation - -```C -bool binary_get2(int n, key_value_t table[n], key_t key, value_t *value) { - int top = n - 1, bottom = 0; - while (true) { - int middle = (top + bottom) / 2; - if (key > table[middle].key) { - bottom = middle + 1; - } else if (key < table[middle].key) { - top = middle; - } else { - *value = table[middle].value; - return true; - } - if (top < bottom) { - break; - } - } - return false; -} -``` - -## Quelle est la différence avec le code précédent? - -# Transformation de clé (hashing) - -## Problématique: Numéro AVS (13 chiffres) - -* Format: 106.3123.8492.13 - - ``` - Numéro AVS | Nom - 0000000000000 | ------- - ... | ... - 1063123849213 | Paul - ... | ... - 3066713878328 | Orestis - ... | ... - 9999999999999 | ------- - ``` - -## Quelle est la clé? Quelle est la valeur? - -. . . - -* Clé: Numéro AVS, Valeur: Nom. - -## Nombre de clés? Nombre de citoyens? Rapport? - -. . . - -* $10^{13}$ clés, $10^7$ citoyens, $10^{-5}$ ($10^{-3}\%$ de la table est - occupée) $\Rightarrow$ *inefficace*. -* Pire: $10^{13}$ entrées ne rentre pas dans la mémoire d'un - ordinateur. - -# Transformation de clé (hashing) - -## Problématique 2: Identificateurs d'un programme - -* Format: 8 caractères (simplification) - - ``` - Identificateur | Adresse - aaaaaaaa | ------- - ... | ... - resultat | 3aeff - compteur | 4fedc - ... | ... - zzzzzzzz | ------- - ``` - -## Quelle est la clé? Quelle est la valeur? - -. . . - -* Clé: Identificateur, Valeur: Adresse. - -## Nombre de clés? Nombre d'identificateur d'un programme? Rapport? - -. . . - -* $26^{8}\sim 2\cdot 10^{11}$ clés, $2000$ identificateurs, $10^{-8}$ ($10^{-6}\%$ de la table est - occupée) $\Rightarrow$ *un peu inefficace*. - -# Fonctions de transformation de clé (hash functions) - -* La table est représentée avec un tableau. -* La taille du tableau est beaucoup plus petit que le nombre de clés. -* On produit un indice du tableau à partir d'une clé: -$$ -h(key) = n,\quad n\in\mathbb{N}. -$$ -En français: on transforme `key` en nombre entier qui sera l'indice dans le -tableau correspondant à `key`. - -## La fonction de hash - -* La taille du domaine des clés est beaucoup plus grand que le domaine des - indices. -* Plusieurs indices peuvent correspondre à la **même clé**: - * Il faut traiter les **collisions**. -* L'ensemble des indices doit être plus petit ou égal à la taille de la table. - -## Une bonne fonction de hash - -* Distribue uniformément les clés sur l'ensemble des indices. - -# Fonctions de transformation de clés: exemples - -## Méthode par troncature - -\begin{align*} -&h: [0,9999]\rightarrow [0,9]\\ -&h(key)=\mbox{troisième chiffre du nombre.} -\end{align*} - -``` -Key | Index -0003 | 0 -1123 | 2 \ -1234 | 3 |-> collision. -1224 | 2 / -1264 | 6 -``` - -## Quelle est la taille de la table? - -. . . - -C'est bien dix oui. - -# Fonctions de transformation de clés: exemples - -## Méthode par découpage - -Taille de l'index: 3 chiffres. - -``` -key = 321 991 24 -> 321 - 991 - + 24 - ---- - 1336 -> index = 336 -``` - -## Devinez l'algorithme? - -. . . - -On part de la gauche: - -1. On découpe la clé en tranche de longueur égale à celle de l'index. -2. On somme les nombres obtenus. -3. On tronque à la longueur de l'index. - -# Fonctions de transformation de clés: exemples - -## Méthode multiplicative - -Taille de l'index: 2 chiffres. - -``` -key = 5486 -> key^2 = 30096196 -> index = 96 -``` - -On prend le carré de la clé et on garde les chiffres du milieu du résultat. - -# Fonctions de transformation de clés: exemples - -## Méthode par division modulo - -Taille de l'index: `N` chiffres. - -``` -h(key) = key % N. -``` - -## Quelle doit être la taille de la table? - -. . . - -Oui comme vous le pensiez au moins `N`. - -# Traitement des collisions - -## La collision - -``` -key1 != key2, h(key1) == h(key2) -``` - -## Traitement (une idée?) - -. . . - -* La première clé occupe la place prévue dans le tableau. -* La deuxième (troisième, etc.) est placée ailleurs de façon **déterministe**. - -Dans ce qui suit la taille de la table est `table_size`. - -# La méthode séquentielle - -\footnotesize - -## Comment ça marche? - -* Quand l'index est déjà occupé on regarde sur la position suivante, jusqu'à en - trouver une libre. - -```C -index = h(key); -while (table[index].state == OCCUPIED && table[index].key != key) { - index = (index + 1) % table_size; // attention à pas dépasser -} -table[index].key = key; -table[index].state = OCCUPIED; -``` - -## Problème? - -. . . - -* Regroupement d'éléments (clustering). - -# Méthode linéaire - -\footnotesize - -## Comment ça marche? - -* Comme la méthode séquentielle mais on "saute" de `k`. - -```C -index = h(key); -while (table[index].state == OCCUPIED && table[index].key != key) { - index = (index + k) % table_size; // attention à pas dépasser -} -table[index].key = key; -table[index].state = OCCUPIED; -``` - -## Quelle valeur de `k` éviter? - -. . . - -* Une valeur où `table_size` est multiple de `k`. - -Cette méthode répartit mieux les regroupements au travers de la table. - -# Méthode du double hashing - -\footnotesize - -## Comment ça marche? - -* Comme la méthode linéaire, mais `k = h2(key)` (variable). - -```C -index = h(key); -while (table[index].state == OCCUPIED && table[index].key != key) { - index = (index + h2(k)) % table_size; // attention à pas dépasser -} -table[index].key = key; -table[index].state = OCCUPIED; -``` - -## Quelle propriété doit avoir `h2`? - -## Exemple - -```C -h2(key) = (table_size - 2) - key % (table_size -2) -``` - -# Méthode pseudo-aléatoire - -\footnotesize - -## Comment ça marche? - -* Comme la méthode linéaire mais on génère `k` pseudo-aléatoirement. - - ```C - index = h(key); - while (table[index].state == OCCUPIED && table[index].key != key) { - index = (index + random_number) % table_size; - } - table[index].key = key; - table[index].state = OCCUPIED; - ``` - -## Comment s'assurer qu'on va bien retrouver la bonne clé? - -. . . - -* Le germe (seed) de la séquence pseudo-aléatoire doit être le même. -* Le germe à choisir est l'index retourné par `h(key)`. - - ```C - srand(h(key)); - while { - random_number = rand(); - } - ``` - -# Méthode quadratique - -* La fonction des indices de collision est de degré 2. -* Soit $J_0=h(key)$, les indices de collision se construisent comme: - - ```C - J_i = J_0 + i^2 % table_size, i > 0, - J_0 = 100, J_1 = 101, J_2 = 104, J_3 = 109, ... - ``` - -## Problème possible? - -. . . - -* Calculer le carré peut-être "lent". -* En fait on peut ruser un peu. - -# Méthode quadratique - -\footnotesize - -```C -J_i = J_0 + i^2 % table_size, i > 0, -J_0 = 100 - \ - d_0 = 1 - / \ -J_1 = 101 Delta = 2 - \ / - d_1 = 3 - / \ -J_2 = 104 Delta = 2 - \ / - d_2 = 5 - / \ -J_3 = 109 Delta = 2 - \ / - d_3 = 7 - / -J_4 = 116 --------------------------------------- -J_{i+1} = J_i + d_i, -d_{i+1} = d_i + Delta, d_0 = 1, i > 0. -``` - -# Méthode de chaînage - -## Comment ça marche? - -* Chaque index de la table contient un pointeur vers une liste chaînée - contenant les paires clés-valeurs. - -## Un petit dessin - -``` - - - - - - - - - - - -``` - -# Méthode de chaînage - -## Exemple - -On hash avec la fonction `h(key) = key % 11` (`key` est le numéro de la lettre -de l'alphabet) - -``` - U | N | E | X | E | M | P | L | E | D | E | T | A | B | L | E - 10 | 3 | 5 | 2 | 5 | 2 | 5 | 1 | 5 | 4 | 5 | 9 | 1 | 2 | 1 | 5 -``` - -## Comment on représente ça? (à vous) - -. . . - -{width=80%} - -# Méthode de chaînage - -Avantages: - -* Si les clés sont grandes l'économie de place est importante (les places vides - sont `NULL`). -* La gestion des collisions est conceptuellement simple. -* Pas de problème de regroupement (clustering). - -# Exercice 1 - -* Construire une table à partir de la liste de clés suivante: - ``` - R, E, C, O, U, P, A, N, T - ``` - -* On suppose que la table est initialement vide, de taille $n = 13$. -* Utiliser la fonction $h1(k)= k \mod 13$ où k est la $k$-ème lettre de l'alphabet et un traitement séquentiel des collisions. - -# Exercice 2 - -* Reprendre l'exercice 1 et utiliser la technique de double hachage pour traiter - les collisions avec - -\begin{align*} -h_1(k)&=k\mod 13,\\ -h_2(k)&=1+(k\mod 11). -\end{align*} -* La fonction de hachage est donc $h(k)=(h(k)+h_2(k)) \% 13$ en cas de - collision. - - -# Exercice 3 - -* Stocker les numéros de téléphones internes d'une entreprise suivants dans un -tableau de 10 positions. -* Les numéros sont compris entre 100 et 299. -* Soit $N$ le numéro de téléphone, la fonction de hachage est -$$ -h(N)=N\mod 10. -$$ -* La fonction de gestion des collisions est -$$ -C_1(N,i)=(h(N)+3\cdot i)\mod 10. -$$ -* Placer 145, 167, 110, 175, 210, 215 (mettre son état à occupé). -* Supprimer 175 (rechercher 175, et mettre son état à supprimé). -* Rechercher 35. -* Les cases ni supprimées, ni occupées sont vides. -* Expliquer se qui se passe si on utilise? -$$ -C_1(N,i)=(h(N)+5\cdot i)\mod 10. -$$ - diff --git a/slides/cours_14.md b/slides/cours_14.md deleted file mode 100644 index b4057a4..0000000 --- a/slides/cours_14.md +++ /dev/null @@ -1,367 +0,0 @@ ---- -title: "Tables de hachage" -date: "2023-02-24" -patat: - eval: - tai: - command: fish - fragment: false - replace: true - ccc: - command: fish - fragment: false - replace: true - images: - backend: auto ---- - -# Rappel sur les tables de hachage (1/N) - -## Définition? Qui se souvient? - -. . . - -Structure de données abstraite où chaque *valeur* (ou élément) est associée à une *clé* (ou -argument). - -On parle de paires *clé-valeur* (*key-value pairs*). - -## Donnez des exemples de telles paires - -. . . - -* Annuaire (nom-téléphone), -* Catalogue (objet-prix), -* Table de valeur fonctions (nombre-nombre), -* Index (nombre-page) -* ... - -# Rappel sur les tables de hachage (1/N) - -## Opérations principales sur les tables - -* Insertion d'élément (`insert(clé, valeur)`{.C}), insère la paire `clé-valeur` -* Consultation (`get(clé)`{.C}), retourne la `valeur` correspondant à `clé` -* Suppression (`remove(clé)`{.C}), supprime la paire `clé-valeur` - -## Transformation de clé (hashing) - -* Format: 106.3123.8492.13 - - ``` - Numéro AVS | Nom - 0000000000000 | ------- - ... | ... - 1063123849213 | Paul - ... | ... - 3066713878328 | Orestis - ... | ... - 9999999999999 | ------- - ``` -* Nombre de numéros >> nombre d'entrées. - -# Fonctions de transformation de clé (hash functions) - -* La table est représentée avec un tableau. -* La taille du tableau est beaucoup plus petit que le nombre de clés. -* On produit un indice du tableau à partir d'une clé: -$$ -h(key) = n,\quad n\in\mathbb{N}. -$$ -En français: on transforme `key` en nombre entier qui sera l'indice dans le -tableau correspondant à `key`. - -## La fonction de hash - -* La taille du domaine des clés est beaucoup plus grand que le domaine des - indices. -* Plusieurs indices peuvent correspondre à la **même clé**: - * Il faut traiter les **collisions**. -* L'ensemble des indices doit être plus petit ou égal à la taille de la table. - -## Une bonne fonction de hash - -* Distribue uniformément les clés sur l'ensemble des indices. - -# Fonctions de transformation de clés: exemple - -## Méthode par division modulo - -Taille de l'index: `N` chiffres. - -``` -h(key) = key % N. -``` - -## Quelle doit être la taille de la table? - -. . . - -Oui comme vous le pensiez au moins `N`. - -# Traitement des collisions - -## La collision - -``` -key1 != key2, h(key1) == h(key2) -``` - -## Traitement (une idée?) - -. . . - -* La première clé occupe la place prévue dans le tableau. -* La deuxième (troisième, etc.) est placée ailleurs de façon **déterministe**. - -Dans ce qui suit la taille de la table est `table_size`. - -# La méthode séquentielle - -\footnotesize - -* Quand l'index est déjà occupé on regarde sur la position suivante, jusqu'à en - trouver une libre. - -```C -index = h(key); -while (table[index].state == OCCUPIED && table[index].key != key) { - index = (index + 1) % table_size; // attention à pas dépasser -} -table[index].key = key; -table[index].state = OCCUPIED; -``` - -# Méthode de chaînage - -## Comment ça marche? - -* Chaque index de la table contient un pointeur vers une liste chaînée - contenant les paires clés-valeurs. - -## Un petit dessin - -``` - - - - - - - - - - - -``` - -# Méthode de chaînage - -## Exemple - -On hash avec la fonction `h(key) = key % 11` (`key` est le numéro de la lettre -de l'alphabet) - -``` - U | N | E | X | E | M | P | L | E | D | E | T | A | B | L | E - 10 | 3 | 5 | 2 | 5 | 2 | 5 | 1 | 5 | 4 | 5 | 9 | 1 | 2 | 1 | 5 -``` - -## Comment on représente ça? (à vous) - -. . . - -{width=80%} - -# Exercice 1 - -* Construire une table à partir de la liste de clés suivante: - ``` - R, E, C, O, U, P, A, N, T - ``` - -* On suppose que la table est initialement vide, de taille $n = 13$. -* Utiliser la fonction $h1(k)= k \mod 13$ où k est la $k$-ème lettre de l'alphabet et un traitement séquentiel des collisions. - -# Exercice 2 - -* Reprendre l'exercice 1 et utiliser la technique de double hachage pour traiter - les collisions avec - -\begin{align*} -h_1(k)&=k\mod 13,\\ -h_2(k)&=1+(k\mod 11). -\end{align*} - -* La fonction de hachage est donc $h(k)=(h(k)+h_2(k)) \% 13$ en cas de - collision. - - -# Exercice 3 - -* Stocker les numéros de téléphones internes d'une entreprise suivants dans un -tableau de 10 positions. -* Les numéros sont compris entre 100 et 299. -* Soit $N$ le numéro de téléphone, la fonction de hachage est -$$ -h(N)=N\mod 10. -$$ -* La fonction de gestion des collisions est -$$ -C_1(N,i)=(h(N)+3\cdot i)\mod 10. -$$ -* Placer 145, 167, 110, 175, 210, 215 (mettre son état à occupé). -* Supprimer 175 (rechercher 175, et mettre son état à supprimé). -* Rechercher 35. -* Les cases ni supprimées, ni occupées sont vides. -* Expliquer se qui se passe si on utilise? -$$ -C_1(N,i)=(h(N)+5\cdot i)\mod 10. -$$ - -# Préambule - -\small - -* On considère pas le cas du chaînage en cas de collisions. -* L'insertion est construite avec une forme du type - - ```C - index = h(key); - while (table[index].state == OCCUPIED - && table[index].key != key) { - index = (index + k) % table_size; // attention à pas dépasser - } - table[index].key = key; - table[index].state = OCCUPIED; - ``` -\normalsize - -* Gestion de l'état d'une case *explicite* - - ```C - typedef enum {EMPTY, OCCUPIED, DELETED} state; - ``` - -# L'insertion - -## Pseudocode? - -. . . - -```C -insert(table, key, value) { - index = hash de la clé; - index = - si "index" est déjà "occupé" - et la clé correspondante n'est pas "key" - alors gérer la collision; - - changer l'état de la case "index" à "occupé"; - changer la valeur de la case "index" à "value"; -} -``` - -# La suppression - -## Pseudocode? - -. . . - -```C -value_t remove(table, key) { - index = hash de la clé; - tant que l'état de la case n'est pas "vide" - si "index" est "occupé" et la clé est "key" - changer l'état de la case à "supprimé" - sinon - index = rehash -} -``` - -# La recherche - -## Pseudocode? - -. . . - -```C -bool search(table, key, value) { - index = hash de la clé; - tant que l'état de la case n'est pas "vide" - si "index" est "occupé" et la clé est "key" - retourner vrai - sinon - index = rehash -} -``` - -# Écrivons le code! - -* Mais avant: - * Quelles sont les structures de données dont nous avons besoin? - * Y a-t-il des fonctions auxiliaires à écrire? - * Écrire les signatures des fonctions. - -. . . - -## Structures de données - -\footnotesize - -. . . - -```C -typedef enum {empty, deleted, occupied}; -typedef ... key_t; -typedef ... value_t; -typedef struct _cell_t { - key_t key; - value_t value; - state_t state; -} cell_t; -typedef struct _hm { - cell_t *table; - int capacity; - int size; -} hm; -``` - -# Écrivons le code! - -## Fonctions auxiliaires - -. . . - -```C -static int hash(key_t key); -static int rehash(int index, key_t key); -static int find_index(hm h, key_t key); -``` - -## Signature de l'API - -. . . - -```C -void hm_init(hm *h, int capacity); -void hm_destroy(hm *h); -bool hm_set(hm *h, key_t key, value_t *value); -bool hm_get(hm h, key_t key, value_t *value); -bool hm_remove(hm *h, key_t key, value_t *value); -bool hm_search(hm h, key_t key); -void hm_print(hm h); -``` - -# Live code session! - -0. Offered to you by ProtonVPN[^1]! - -. . . - -1. Like the video. -2. Subscribe to the channel. -3. Use our one time voucher for ProtonVPN: `PAULISAWESOME`. -4. Consider donating on our patreon. - -[^1]: The fastest way to connect to BBB! diff --git a/slides/cours_15.md b/slides/cours_15.md deleted file mode 100644 index 09a8ed0..0000000 --- a/slides/cours_15.md +++ /dev/null @@ -1,929 +0,0 @@ ---- -title: "Arbres" -date: "2023-03-10" ---- - -# Les arbres: définition - -"Un arbre est un graphe acyclique orienté possédant une unique racine, et tel que tous les nœuds sauf la racine ont un unique parent." - -. . . - -**Santé!** - -## Plus sérieusement - -* Ensemble de **nœuds** et d'**arêtes** (graphe), -* Les arêtes relient les nœuds entre eux, mais pas n'importe comment: chaque - nœud a au plus un **parent**, -* Le seul nœud sans parent est la **racine**, -* Chaque nœud a un nombre fini de **fils**, -* La hiérarchie des nœuds rend les arêtes **orientées** (parent -> fils), et empêche les - **cycles** (acyclique, orienté). -* La **feuille** ou **nœud terminal** est un nœud sans enfants, -* Le **niveau** est 1 à la racine et **niveau+1** pour les fils, -* Le **degré** d'un nœud est le nombre de fils du nœud. - -. . . - -* Chaque nœud est un arbre en lui même. -* La **récursivité** sera très utile! - - -# Arbre ou pas arbre? - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - 1-->2; - 1-->3; - 3-->2; - 3-->4; - 3-->5; -``` -:::: - -. . . - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - 1-->2; - 1-->3; - 3-->4; - 3-->5; - 3-->6; -``` -:::: - -::: - -# Arbre ou pas arbre? - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - 1-->2; - 1-->3; - 3-->4; - 3-->5; - 3-->6; - 6-->7; - 7-->3; -``` -:::: - -. . . - -:::: column -```{.mermaid format=pdf width=300 loc=figs/} -graph TD; - 1; -``` -:::: - -::: - -# Arbre ou pas arbre? - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - 1---2; - 1---3; - 3---4; - 3---5; -``` -:::: - -. . . - -:::: column -```{.mermaid format=pdf width=300 loc=figs/} -graph BT; - 1-->2; - 1-->3; - 3-->4; - 3-->5; - 3-->6; -``` -:::: - -::: - -# Degré et niveau - -* Illustration du degré (nombre de fils) et du niveau (profondeur) - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - 1[degré 2]-->2[degré 0]; - 1-->3[degré 3]; - 3-->4[degré 0]; - 3-->5[degré 0]; - 3-->6[degré 0]; -``` -:::: - -. . . - -:::: column -```{.mermaid format=pdf width=300 loc=figs/} -graph TD; - 1[niveau 1]-->2[niveau 2]; - 1-->3[niveau 2]; - 3-->4[niveau 3]; - 3-->5[niveau 3]; - 3-->6[niveau 3]; -``` -:::: - -::: - -* Les nœuds de degré 0, sont des feuilles. - -# Application: recherche rapide - -## Pouvez vous construire un arbre pour résoudre le nombre secret? - - . . . - -* Le nombre secret ou la recherche dichotomique (nombre entre 0 et 10). - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 5-->|<|2; - 5-->|>|7; - 7-->|>|8; - 7-->|<|6; - 8-->|>|9; - 9-->|>|10; - 2-->|<|1; - 2-->|>|3; - 3-->|>|4; - 1-->|<|0; -``` -:::: - -:::: column - -**Question:** Quelle est la complexité pour trouver un nombre? - -:::: - -::: - -# Autres représentation - -* Botanique -* **Exercice:** Ajouter les degrés/niveaux et feuilles - -```{.mermaid width=250 format=pdf loc=figs/} -graph TD; - A-->B; - A-->C; - B-->D; - B-->E; - B-->F; - F-->I; - F-->J; - C-->G; - C-->H; - H-->K; -``` - -# Autres représentation - -* Ensembliste - -::: columns - -:::: column -```{.mermaid width=300 format=pdf loc=figs/} -graph TD; - A-->B; - A-->C; - B-->D; - B-->E; - B-->F; - F-->I; - F-->J; - C-->G; - C-->H; - H-->K; -``` -:::: - -. . . - -:::: column - -:::: - -::: - -# Autres représentation - -* Liste - -::: columns - -:::: column -```{.mermaid width=400 format=pdf loc=figs/} -graph TD; - A-->B; - A-->C; - B-->D; - B-->E; - B-->F; - F-->I; - F-->J; - C-->G; - C-->H; - H-->K; -``` -:::: - -. . . - -:::: column -``` -(A - (B - (D) - (E) - (F - (I) - (J) - ) - ) - (C - (G) - (H - (K) - ) - ) -) -``` -:::: - -::: - -# Autres représentation - -* Par niveau - -::: columns - -:::: column -```{.mermaid width=400 format=pdf loc=figs/} -graph TD; - A-->B; - A-->C; - B-->D; - B-->E; - B-->F; - F-->I; - F-->J; - C-->G; - C-->H; - H-->K; -``` -:::: - -. . . - -:::: column -``` -1 2 3 4 -------------------------- -A - B - D - E - F - I - J - C - G - H - K -``` -:::: - -::: - -# L'arbre binaire - -* Structure de données abstraite, -* Chaque nœud a au plus deux fils: gauche et droite, -* Chaque fils est un arbre. - -## Comment représenteriez vous une telle structure? - -. . . - -```C -<R, G, D> - R: racine - G: sous-arbre gauche - D: sous-arbre droite -``` - -## Comment cela s'écrirait en C? - -. . . - -```C -typedef struct _node { - contenu info; - struct _node *left, *right; -} node; -typedef node *tree; -``` - -# L'arbre binaire - -## Que se passerait-il avec - -```C -typedef struct _node { - int info; - struct _node left, right; -} node; -``` - -* On ne sait pas quelle est la taille de node, on ne peut pas l'allouer! - -## Interface minimale - -* Qu'y mettriez vous? - -. . . - -```C -NULL -> arbre (vide) -<n, arbre, arbre> -> arbre -visiter(arbre) -> nœud (la racine de l'arbre) -gauche(arbre) -> arbre (sous-arbre de gauche) -droite(arbre) -> arbre (sous-arbre de droite) -``` - -* Les autres opérations (insertion, parcours, etc) dépendent de ce qu'on stocke - dans l'arbre. - -# Exemple d'arbre binaire - -* Représentez `(c - a * b) * (d + e / f)` à l'aide d'un arbre binaire (matrix) - -. . . - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - A[*]-->B[-]; - B-->C[c]; - B-->D[*]; - D-->E[a]; - D-->F[b]; - A-->G[+]; - G-->H[d]; - G-->I["/"]; - I-->J[e]; - I-->K[f]; -``` -:::: - - -:::: column - -## Remarques - -* L'arbre est **hétérogène**: le genre d'info est pas le même sur chaque nœud - (opérateur, opérande). - * Les feuilles contiennent les opérandes. - * Les nœuds internes contiennent les opérateurs. - -:::: - -::: - -# Parcours d'arbres binaires - -* Appliquer une opération à tous les nœuds de l'arbre, -* Nécessité de **parcourir** l'arbre, -* Utiliser uniquement l'interface: visiter, gauche, - droite. - -## Une idée de comment parcourir cet arbre? - -* 3 parcours (R: Racine, G: sous-arbre gauche, D: sous-arbre droit): - - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - A[*]-->B[-]; - B-->C[c]; - B-->D[*]; - D-->E[a]; - D-->F[b]; - A-->G[+]; - G-->H[d]; - G-->I["/"]; - I-->J[e]; - I-->K[f]; -``` -:::: - -:::: column - -1. Parcours **préfixe** (R, G D), -2. Parcours **infixe** (G, R, D), -3. Parcours **postfixe** (G, D, R). - -:::: - -::: - -# Le parcours infixe (G, R, D) - -* Gauche, Racine, Droite: - 1. On descend dans l'arbre de gauche tant qu'il est pas vide, - 2. On visite la racine du sous arbre, - 3. On descend dans le sous-arbre de droite (s'il est pas vide), - 4. On recommence. - -. . . - -## Incompréhensible? - -* La récursivité c'est la vie. - -``` -parcours_infixe(arbre a) - si est_pas_vide(gauche(a)) - parcours_infixe(gauche(a)) - visiter(A) - si est_pas_vide(droite(A)) - parcours_infixe(droite(A)) -``` - -# Graphiquement (dessinons) - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - A[*]-->B[-]; - B-->C[c]; - B-->D[*]; - D-->E[a]; - D-->F[b]; - A-->G[+]; - G-->H[d]; - G-->I["/"]; - I-->J[e]; - I-->K[f]; -``` -:::: - -:::: column - -``` -parcours_infixe(arbre a) - si est_pas_vide(gauche(a)) - parcours_infixe(gauche(a)) - visiter(A) - si est_pas_vide(droite(A)) - parcours_infixe(droite(A)) -``` - -:::: - -::: - - -# Graphiquement (`mermaid` c'est super) - -::: columns - -:::: column -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - A[*]-->B[-]; - A[*]-.->|1|B[-]; - B-->C[c]; - B-.->|2|C[c]; - C-.->|3|B; - B-->D[*]; - B-.->|4|D; - D-->E[a]; - D-.->|5|E; - E-.->|6|D; - D-->F[b]; - D-.->|7|F; - F-.->|8|A; - A-->G[+]; - A-.->|9|G; - G-->H[d]; - G-.->|10|H; - H-.->|11|G; - G-->I["/"]; - G-.->|12|I; - I-->J[e]; - I-.->|13|J; - J-.->|14|I; - I-->K[f]; - I-.->|15|K; -``` -:::: - -:::: column - -``` -parcours_infixe(arbre a) - si est_pas_vide(gauche(a)) - parcours_infixe(gauche(a)) - visiter(A) - si est_pas_vide(droite(A)) - parcours_infixe(droite(A)) -``` - -## Remarque - -Le nœud est visité à la **remontée**. - -## Résultat - -``` -c - a * b * d + e / f -``` - -:::: - -::: - -# Et en C? - -## Live code - -\footnotesize - -. . . - -```C -typedef int data; -typedef struct _node { - data info; - struct _node* left; - struct _node* right; -} node; -typedef node* tree_t; -void tree_print(tree_t tree, int n) { - if (NULL != tree) { - tree_print(tree->left, n+1); - for (int i = 0; i < n; i++) { - printf(" "); - } - printf("%d\n", tree->info); - tree_print(tree->right, n+1); - } -} -``` - -# Question - -## Avez-vous compris le fonctionnement? - -. . . - -## Vous en êtes sûr·e·s? - -. . . - -## OK, alors deux exercices: - -1. Écrire le pseudo-code pour le parcours R, G, D (matrix). -2. Écrire le pseudo-code pour la parcours G, D, R (matrix), - -## Rappel - -``` -parcours_infixe(arbre a) - si est_pas_vide(gauche(a)) - parcours_infixe(gauche(a)) - visiter(A) - si est_pas_vide(droite(A)) - parcours_infixe(droite(A)) -``` - -# Correction - -\footnotesize - -* Les deux parcours sont des modifications **triviales**[^1] de l'algorithme - infixe. - -## Le parcours postfixe - -```python -parcours_postfixe(arbre a) - si est_pas_vide(gauche(a)) - parcours_postfixe(gauche(a)) - si est_pas_vide(droite(a)) - parcours_postfixe(droite(a)) - visiter(a) -``` - -## Le parcours préfixe - -```python -parcours_préfixe(arbre a) - visiter(a) - si est_pas_vide(gauche(a)) - parcours_préfixe(gauche(a)) - si est_pas_vide(droite(a)) - parcours_préfixe(droite(a)) -``` - -. . . - -**Attention:** L'implémentation de ces fonctions en C sont **à faire** en -exercice (inspirez vous de ce qu'on a fait avant)! - -# Exercice: parcours - -## Comment imprimer l'arbre ci-dessous? - -``` - f - / - e - + - d -* - c - - - b - * - a -``` - -. . . - -## Bravo vous avez trouvé! - -* Il s'agissait du parcours D, R, G. - -# Implémentation - -## Vous avez 5 min pour implémenter cette fonction et la poster sur matrix! - -. . . - -```C -void pretty_print(tree_t tree, int n) { - if (NULL != tree) { - pretty_print(tree->right, n+1); - for (int i = 0; i < n; ++i) { - printf(" "); - } - printf("%d\n", tree->info); - pretty_print(tree->left, n+1); - } -} -``` - -# Exercice supplémentaire (sans corrigé) - -Écrire le code de la fonction - -```C -int depth(tree_t t); -``` - -qui retourne la profondeur maximale d'un arbre. - -Indice: la profondeur à chaque niveau peut-être calculée à partir du niveau des -sous-arbres de gauche et de droite. - -# La recherche dans un arbre binaire - -* Les arbres binaires peuvent retrouver une information très rapidement. -* À quelle complexité? À quelle condition? - -. . . - -## Condition - -* Le contenu de l'arbre est **ordonné** (il y a une relation d'ordre (`<`, `>` - entre les éléments). - -## Complexité - -* La profondeur de l'arbre (ou le $\mathcal{O}(\log_2(N))$) - -. . . - -## Exemple: les arbres lexicographiques - -* Chaque nœud contient une information de type ordonné, la **clé**, -* Par construction, pour chaque nœud $N$: - * Toutes clé du sous-arbre à gauche de $N$ sont inférieurs à la clé de $N$. - * Toutes clé du sous-arbre à droite de $N$ sont inférieurs à la clé de $N$. - -# Algorithme de recherche - -* Retourner le nœud si la clé est trouvée dans l'arbre. - -```python -arbre recherche(clé, arbre) - tante_que est_non_vide(arbre) - si clé < clé(arbre) - arbre = gauche(arbre) - sinon si clé > clé(arbre) - arbre = droite(arbre) - sinon - retourne arbre - retourne NULL -``` - -# Algorithme de recherche, implémentation (live) - -\footnotesize - -. . . - -```C -typedef int key_t; -typedef struct _node { - key_t key; - struct _node* left; - struct _node* right; -} node; -typedef node* tree_t; -tree_t search(key_t key, tree_t tree) { - tree_t current = tree; - while (NULL != current) { - if (current->key > X) { - current = current->gauche; - } else if (current->key < X){ - current = current->droite; - } else { - return current; - } - } - return NULL; -} -``` - -# Exercice (5-10min) - -Écrire le code de la fonction - -```C -int tree_size(tree_t tree); -``` - -qui retourne le nombre total de nœuds d'un arbre et poster le résultat sur -matrix. - -Indication: la taille, est 1 + le nombre de nœuds du sous-arbre de gauche -additionné au nombre de nœuds dans le sous-arbre de droite. - -. . . - -```C -int arbre_size(tree_t tree) { - if (NULL == tree) { - return 0; - } else { - return 1 + tree_size(tree->left) - + tree_size(tree->right); - } -} -``` - -# L'insertion dans un arbre binaire - -* C'est bien joli de pouvoir faire des parcours, recherches, mais si on peut - pas construire l'arbre.... - -## Pour un arbre lexicographique - -* Rechercher la position dans l'arbre où insérer. -* Créer un nœud avec la clé et le rattacher à l'arbre. - -# Exemple d'insertions - -* Clés uniques pour simplifier. -* Insertion de 5, 15, 10, 25, 2, -5, 12, 14, 11. -* Rappel: - * Plus petit que la clé courante => gauche, - * Plus grand que la clé courante => droite. -* Faisons le dessins ensemble - -``` - - - - - - - - - -``` - -## Exercice (3min, puis matrix) - -* Dessiner l'arbre en insérant 20, 30, 60, 40, 10, 15, 25, -5 - - -# Pseudo-code d'insertion (1/2) - -* Deux parties: - * Recherche le parent où se passe l'insertion. - * Ajout du fils dans l'arbre. - -## Recherche du parent - -``` -arbre position(arbre, clé) - si est_non_vide(arbre) - si clé < clé(arbre) - suivant = gauche(arbre) - sinon - suivant = droite(arbre) - tant que clé(arbre) != clé && est_non_vide(suivant) - arbre = suivant - si clé < clé(arbre) - suivant = gauche(arbre) - sinon - suivant = droite(arbre) - - retourne arbre -``` - -# Pseudo-code d'insertion (2/2) - -* Deux parties: - * Recherche de la position. - * Ajout dans l'arbre. - -## Ajout du fils - -``` -ajout(arbre, clé) - si est_vide(arbre) - arbre = nœud(clé) - sinon - si clé < clé(arbre) - gauche(arbre) = nœud(clé) - sinon si clé > clé(arbre) - droite(arbre) = nœud(clé) - sinon - retourne -``` - -# Code d'insertion en C (1/2) - -## Recherche du parent (ensemble) - -. . . - -```C -tree_t position(tree_t tree, key_t key) { - tree_t current = tree; - if (NULL != current) { - tree_t subtree = key > current->key ? current->right : - current->left; - while (key != current->key && NULL != subtree) { - current = subtree; - subtree = key > current->key ? current->right : - current->left; - } - } - return current; -} -``` - -[^1]: Copyright cours de mathématiques pendant trop d'années. diff --git a/slides/cours_16.md b/slides/cours_16.md deleted file mode 100644 index 6e284d6..0000000 --- a/slides/cours_16.md +++ /dev/null @@ -1,1277 +0,0 @@ ---- -title: "Arbres et tri par tas" -date: "2023-03-17" ---- - -# Un joli site - -## Visualisation d'algorithmes - -* <https://visualgo.net/> -* Allons nous rafraîchir la mémoire sur l'insertion / recherche dans un arbre - binaire. - - - -# L'insertion (1/4) - -* Deux parties: - * Recherche le parent où se passe l'insertion. - * Ajout du fils dans l'arbre. - -## Recherche du parent (pseudo-code) - -``` -arbre position(arbre, clé) - si est_non_vide(arbre) - si clé < clé(arbre) - suivant = gauche(arbre) - sinon - suivant = droite(arbre) - tant que clé(arbre) != clé && est_non_vide(suivant) - arbre = suivant - si clé < clé(arbre) - suivant = gauche(arbre) - sinon - suivant = droite(arbre) - - retourne arbre -``` - -# L'insertion (2/4) - -## Recherche du parent (code) - -. . . - -```C -tree_t position(tree_t tree, key_t key) { - tree_t curr = tree; - if (NULL != curr) { - tree_t subtree = - key > curr->key ? curr->right : curr->left; - while (key != curr->key && NULL != subtree) { - curr = subtree; - subtree = key > curr->key ? curr->right : - curr->left; - } - } - return curr; -} -``` - -# L'insertion (3/4) - -* Deux parties: - * Recherche de la position. - * Ajout dans l'arbre. - -## Ajout du fils (pseudo-code) - -``` -rien ajout(arbre, clé) - si est_vide(arbre) - arbre = nœud(clé) - sinon - arbre = position(arbre, clé) - si clé < clé(arbre) - gauche(arbre) = nœud(clé) - sinon si clé > clé(arbre) - droite(arbre) = nœud(clé) - sinon - retourne -``` - - - -# L'insertion (4/4) - -## Ajout du fils (code) - -\scriptsize - -* 2 cas: arbre vide ou pas. -* on retourne un pointeur vers le nœud ajouté (ou `NULL`) - -. . . - -```C -tree_t add_key(tree_t *tree, key_t key) { - node_t *new_node = calloc(1, sizeof(*new_node)); - new_node->key = key; - if (NULL == *tree) { - *tree = new_node; - } else { - tree_t subtree = position(*tree, key); - if (key == subtree->key) { - return NULL; - } else { - if (key > subtree->key) { - subtree->right = new_node; - } else { - subtree->left = new_node; - } - } - } - return new_node; -} -``` - -# La version PK (1/5) - -```C -typedef struct _node { - int info; - struct _node *left, *right; -} node; -typedef node *tree; -void parcours_infixe(tree arbre, int n){ - if(arbre!=NULL){ - parcours_infixe(arbre->left, n+1); - for(int i=0; i<n; i++){ - printf(" "); - } - printf("%d\n", arbre->info); - parcours_infixe(arbre->right, n+1); - } -} -``` - -# La version PK (2/5) - -```C -tree recherche(int cle, tree arbre){ - while(arbre != NULL){ - if(arbre->info == cle) return arbre; - if(arbre->info > cle){ - arbre = arbre->left; - }else if(arbre->info < cle){ - arbre = arbre->right; - } - } - return NULL; -} - -``` - -# La version PK (3/5) - -\footnotesize - -```C -node* parent_insertion(int donnee, tree arbre){ - if(arbre != NULL){ - node* suivant = NULL; - if(arbre->info > donnee){ - suivant = arbre->left; - } else { - suivant = arbre->right; - } - while(suivant != NULL && arbre->info != donnee){ - arbre = suivant; - if(arbre->info > donnee){ - suivant = arbre->left; - } else { - suivant = arbre->right; - } - } - } - return arbre; -} - -``` - -# La version PK (4/5) - -\footnotesize - -```C -node* nouveau_noeud(int donnee){ - node* new_node = malloc(sizeof(node)); - new_node->info = donnee; - new_node->left = NULL; - new_node->right = NULL; - return new_node; -} -tree insertion(int donnee, tree arbre){ - if(arbre == NULL){ - arbre = nouveau_noeud(donnee); - } else { - node* parent = parent_insertion(donnee, arbre); - if(donnee > parent->info){ - parent->right = nouveau_noeud(donnee); - } else if(donnee < parent->info) { - parent->left = nouveau_noeud(donnee); - } - } - return arbre; -} -``` - -# La version PK (5/5) - -```C -int main(){ - tree arbre = NULL; - - arbre = insertion(2, arbre); - arbre = insertion(1, arbre); - arbre = insertion(8, arbre); - arbre = insertion(10, arbre); - arbre = insertion(5, arbre); - - parcours_infixe(arbre, 0); -} -``` - -# La suppression de clé - - -::: columns - -:::: column - -## Cas simples: - -* le nœud est absent, -* le nœud est une feuille -* le nœuds a un seul fils. - -## Une feuille (le 19 p.ex.). - -```{.mermaid format=pdf width=150 loc=figs/} -flowchart TB; - 10-->20; - 10-->5 - 20-->21 - 20-->19 -``` - -:::: - -:::: column - -## Un seul fils (le 20 p.ex.). - -```{.mermaid format=pdf width=400 loc=figs/} -flowchart TB; - 10-->20; - 10-->5 - 20-->25 - 20-->18 - 25-->24 - 25-->30 - 5-->4; - 5-->8; - style 18 fill:#fff,stroke:#fff,color:#fff -``` - -## Dans tous les cas - -* Chercher le nœud à supprimer: utiliser `position()`. - -:::: - -::: - -# La suppression de clé - - -::: columns - -:::: column - -## Cas compliqué - -* Le nœud à supprimer à (au moins) deux descendants (10). - -```{.mermaid format=pdf width=400 loc=figs/} -flowchart TB; - 10-->20; - 10-->5 - 20-->25 - 20-->18 - 25-->24 - 25-->30 - 5-->4; - 5-->8; -``` - -:::: - -:::: column - -* Si on enlève 10 il se passe quoi? - -. . . - -* On peut pas juste enlever `10` et recoller... -* Proposez une solution bon sang! - -. . . - -## Solution - -* Échange de la valeur à droite dans le sous-arbre de gauche ou - ... -* de la valeur de gauche dans le sous-arbre de droite! -* Puis, on retire le nœud. - -:::: - -::: - -# Le pseudo-code de la suppression - -## Pour une feuille ou absent (ensemble) - -``` -arbre suppression(arbre, clé) - sous_arbre = position(arbre, clé) - si est_vide(sous_arbre) ou clé(sous_arbre) != clé - retourne vide - sinon - si est_feuille(sous_arbre) et clé(sous_arbre) == clé - nouvelle_feuille = parent(arbre, sous_arbre) - si est_vide(nouvelle_feuille) - arbre = vide - sinon - si gauche(nouvelle_feuille) == sous_arbre - gauche(nouvelle_feuille) = vide - sinon - droite(nouvelle_feuille) = vide - retourne sous_arbre -``` - -# Il nous manque le code pour le `parent` -## Pseudo-code pour trouver le parent (5min -> matrix) - -. . . - -``` -arbre parent(arbre, sous_arbre) - si est_non_vide(arbre) - actuel = arbre - parent = actuel - clé = clé(sous_arbre) - faire - si (clé != clé(actuel)) - parent = actuel - si clé < clé(actuel) - actuel = gauche(actuel) - sinon - actuel = droite(actuel) - sinon - retour parent - tant_que (actuel != sous_arbre) - retourne vide -``` - -# Le pseudo-code de la suppression - -\footnotesize - -## Pour un seul enfant (5min -> matrix) - -. . . - -``` -arbre suppression(arbre, clé) - sous_arbre = position(arbre, clé) - si est_vide(gauche(sous_arbre)) ou est_vide(droite(sous_arbre)) - parent = parent(arbre, sous_arbre) - si est_vide(gauche(sous_arbre)) - si droite(parent) == sous_arbre - droite(parent) = droite(sous_arbre) - sinon - gauche(parent) = droite(sous_arbre) - sinon - si droite(parent) == sous_arbreou est_ - droite(parent) = gauche(sous_arbre) - sinon - gauche(parent) = gauche(sous_arbre) - retourne sous_arbre -``` - - -# Le pseudo-code de la suppression - -\footnotesize - -## Pour au moins deux enfants (ensemble) - -``` -arbre suppression(arbre, clé) - sous_arbre = position(arbre, clé) # on revérifie pas que c'est bien la clé - si est_non_vide(gauche(sous_arbre)) et est_non_vide(droite(sous_arbre)) - max_gauche = position(gauche(sous_arbre), clé) - échange(clé(max_gauche), clé(sous_arbre)) - suppression(gauche(sous_arbre), clé) -``` - -# Exercices (poster sur matrix) - -1. Écrire le pseudo-code de l'insertion purement en récursif. - -. . . - -``` -arbre insertion(arbre, clé) - si est_vide(arbre) - retourne nœud(clé) - - si (clé < arbre->clé) - gauche(arbre) = insert(gauche(arbre), clé) - sinon - droite(arbre) = insert(droite(arbre), clé) - retourne arbre -``` - -# Exercices (poster sur matrix) - -2. Écrire le pseudo-code de la recherche purement en récursif. - -. . . - -``` -bool recherche(arbre, clé) - si est_vide(arbre) - retourne faux // pas trouvée - si clé(arbre) == clé - retourne vrai // trouvée - si clé < clé(arbre) - retourne recherche(gauche(arbre), clé) - sinon - retourne recherche(droite(arbre), clé) -``` - -# Exercices (à la maison) - -3. Écrire une fonction qui insère des mots dans un arbre et ensuite affiche - l'arbre. - -# Trier un tableau à l'aide d'un arbre binaire - -* Tableau représenté comme un arbre binaire. -* Aide à comprendre "comment" trier, mais on ne construit jamais l'arbre. -* Complexité $O(N\log_2 N)$ en moyenne et grande stabilité (pas de cas - dégénérés). - -# Lien entre arbre et tableau - -* La racine de l'arbre set le premier élément du tableau. -* Les deux fils d'un nœud d'indice $i$, ont pour indices $2i+1$ et $2i+2$: - * Les fils du nœud $i=0$, sont à $2\cdot 0+1=1$ et $2\cdot 0+2=2$. - * Les fils du nœud $i=1$, sont à $2\cdot 1+1=3$ et $2\cdot 1+2=4$. - * Les fils du nœud $i=2$, sont à $2\cdot 2+2=5$ et $2\cdot 1+2=6$. - * Les fils du nœud $i=3$, sont à $2\cdot 3+1=7$ et $2\cdot 3+2=8$. -* Un élément d'indice $i$ a pour parent l'élément $(i-1)/2$ (division entière): - * Le parent du nœud $i=8$ est $(8-1)/2=3$. - * Le parent du nœud $i=7$ est $(7-1)/2=3$. - -# Visuellement - -::: columns - -:::: column - -* Où vont les indices correspondant du tableau? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0(( ))-->id1(( )); - id0-->id2(( )); - id1-->id3(( )); - id1-->id4(( )); - id2-->id5(( )); - id2-->id6(( )); - id3-->id7(( )); - id3-->id8(( )); - id4-->id9(( )); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` -:::: - -:::: column - -* Les flèche de gauche à droite, parent -> enfants. -* Les flèche de droite à gauche, enfants -> parent. - - - -:::: - -::: - -**Propriétés:** - -1. les feuilles sont toutes sur l'avant dernier ou dernier niveau. -2. les feuilles de profondeur maximale sont "tassée" à gauche. - -# Le tas (ou heap) - -## Définition - -* Un arbre est un tas, si la valeur de chacun de ses descendants est inférieure - ou égale à sa propre valeur. - -## Exemples (ou pas) - -``` -16 8 14 6 2 10 12 4 5 # Tas -16 14 8 6 2 10 12 4 5 # Non-tas, 10 > 8 et 12 > 8 -``` - -## Exercices (ou pas) - -``` -19 18 12 12 17 1 13 4 5 # Tas ou pas tas? -19 18 16 12 17 1 12 4 5 # Tas ou pas tas? -``` - -. . . - -``` -19 18 12 12 17 1 13 4 5 # Pas tas! 13 > 12 -19 18 16 12 17 1 12 4 5 # Tas! -``` - -# Exemple de tri par tas (1/N) - -``` - | 1 | 16 | 5 | 12 | 4 | 2 | 8 | 10 | 6 | 7 | -``` - -::: columns - -:::: column - -* Quel est l'arbre que cela représente? - -. . . - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((5)); - id1-->id3((12)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6((8)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((7)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Transformer l'arbre en tas. - -* On commence à l'indice $N/2 = 5$: `4`. -* `7 > 4` (enfant `>` parent). -* intervertir `4` et `7`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((5)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((8)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -. . . - -``` - * * - | 1 | 16 | 5 | 12 | 7 | 2 | 8 | 10 | 6 | 4 | -``` - -# Exemple de tri par tas (2/N) - -``` - | 1 | 16 | 5 | 12 | 7 | 2 | 8 | 10 | 6 | 4 | -``` - -::: columns - -:::: column - -**But:** Transformer l'arbre en tas. - -* On continue à l'indice $N/2-1 = 4$: `12`. -* Déjà un tas, rien à faire. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((5)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((8)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Transformer l'arbre en tas. - -* On continue à l'indice $N/2-2 = 3$: `5`. -* `5 < 8`, échanger `8` et `5` (aka `max(2, 5, 8)`) - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((8)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -. . . - -``` - | 1 | 16 | 8 | 12 | 7 | 2 | 5 | 10 | 6 | 4 | -``` - -# Exemple de tri par tas (3/N) - -``` - | 1 | 16 | 5 | 12 | 7 | 2 | 8 | 10 | 6 | 4 | -``` - -::: columns - -:::: column - -**But:** Transformer l'arbre en tas. - -* Indice $N/2-1 = 4$: `12`. -* Déjà un tas, rien à faire. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((5)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((8)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Transformer l'arbre en tas. - -* Indice $N/2-2 = 3$: `5`. -* `5 < 8`, `5 <=> max(2, 5, 8)` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((8)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - * * - | 1 | 16 | 8 | 12 | 7 | 2 | 5 | 10 | 6 | 4 | -``` - -# Exemple de tri par tas (4/N) - -``` - | 1 | 16 | 8 | 12 | 7 | 2 | 5 | 10 | 6 | 4 | -``` - -::: columns - -:::: column - -**But:** Transformer l'arbre en tas. - -* Indice $N/2-3 = 1$: `16`. -* Déjà un tas, rien à faire. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((8)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Transformer l'arbre en tas. - -* Indice $N/2-4 = 1$: `1`. -* `1 < 16 && 1 < 8`, `1 <=> max(1, 16, 8)` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((16))-->id1((1)); - id0-->id2((8)); - id1-->id3((12)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - * * - | 16 | 1 | 8 | 12 | 7 | 2 | 5 | 10 | 6 | 4 | -``` - - -# Exemple de tri par tas (5/N) - -``` - | 16 | 1 | 8 | 12 | 7 | 2 | 5 | 10 | 6 | 4 | -``` - -::: columns - -:::: column - -**But:** Transformer l'arbre en tas. - -* Recommencer avec `1`. -* `1 <=> max(1, 12, 7)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((16))-->id1((12)); - id0-->id2((8)); - id1-->id3((1)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Transformer l'arbre en tas. - -* Recommencer avec `1`. -* `1 <=> max(1, 10, 6)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((16))-->id1((12)); - id0-->id2((8)); - id1-->id3((10)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((1)); - id3-->id8((6)); - id4-->id9((4)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - * * * - | 16 | 12 | 8 | 10 | 7 | 2 | 5 | 1 | 6 | 4 | -``` - -* L'arbre est un tas. - -# Exemple de tri par tas (6/N) - -``` - | 16 | 12 | 8 | 10 | 7 | 2 | 5 | 1 | 6 | 4 | -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `16` (`max` de l'arbre) avec `4`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((4))-->id1((12)); - id0-->id2((8)); - id1-->id3((10)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((1)); - id3-->id8((6)); -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `4 <=> max(4, 12, 8)`. -* `4 <=> max(4, 10, 7)`. -* `4 <=> max(4, 1, 6)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((10)); - id0-->id2((8)); - id1-->id3((6)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((1)); - id3-->id8((4)); -``` - -:::: - -::: - -``` - | 12 | 10 | 8 | 6 | 7 | 2 | 5 | 1 | 4 || 16 -``` - - -# Exemple de tri par tas (7/N) - -``` - | 12 | 10 | 8 | 6 | 7 | 2 | 5 | 1 | 4 || 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `12` (`max` de l'arbre) avec `4`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((4))-->id1((10)); - id0-->id2((8)); - id1-->id3((6)); - id1-->id4((7)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((1)); - id3-->id8(( )); - style id8 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `4 <=> max(4, 10, 8)`. -* `4 <=> max(4, 6, 7)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((10))-->id1((7)); - id0-->id2((8)); - id1-->id3((6)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6((5)); - id3-->id7((1)); - id3-->id8(( )); - style id8 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - | 10 | 7 | 8 | 6 | 4 | 2 | 5 | 1 || 12 | 16 -``` - -# Exemple de tri par tas (8/N) - -``` - | 10 | 7 | 8 | 6 | 4 | 2 | 5 | 1 || 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `10` (`max` de l'arbre) avec `1`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((7)); - id0-->id2((8)); - id1-->id3((6)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6((5)); -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `1 <=> max(1, 7, 8)`. -* `5 <=> max(1, 2, 5)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((8))-->id1((7)); - id0-->id2((5)); - id1-->id3((6)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6((1)); -``` - -:::: - -::: - -``` - | 8 | 7 | 5 | 6 | 4 | 2 | 1 || 10 | 12 | 16 -``` - -# Exemple de tri par tas (9/N) - -``` - | 8 | 7 | 5 | 6 | 4 | 2 | 1 || 10 | 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `8` (`max` de l'arbre) avec `1`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((7)); - id0-->id2((5)); - id1-->id3((6)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6(( )); - style id6 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `1 <=> max(1, 7, 5)`. -* `1 <=> max(1, 6, 4)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((7))-->id1((6)); - id0-->id2((5)); - id1-->id3((1)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6(( )); - style id6 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - | 7 | 6 | 5 | 1 | 4 | 2 || 8 | 10 | 12 | 16 -``` - -# Exemple de tri par tas (10/N) - -``` - | 7 | 6 | 5 | 1 | 4 | 2 || 8 | 10 | 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `7` (`max` de l'arbre) avec `2`. -* Traiter la racine. - -```{.mermaid format=pdf width=150 loc=figs/} -graph TD; - id0((2))-->id1((6)); - id0-->id2((5)); - id1-->id3((1)); - id1-->id4((4)); -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `2 <=> max(2, 6, 5)`. -* `2 <=> max(2, 1, 4)`. - -```{.mermaid format=pdf width=150 loc=figs/} -graph TD; - id0((6))-->id1((4)); - id0-->id2((5)); - id1-->id3((1)); - id1-->id4((2)); -``` - -:::: - -::: - -``` - | 6 | 4 | 5 | 1 | 2 || 8 | 10 | 12 | 16 -``` - -# Exemple de tri par tas (11/N) - -``` - | 6 | 4 | 5 | 1 | 2 || 8 | 10 | 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `6` (`max` de l'arbre) avec `2`. -* Traiter la racine. - -```{.mermaid format=pdf width=150 loc=figs/} -graph TD; - id0((2))-->id1((4)); - id0-->id2((5)); - id1-->id3((1)); - id1-->id4(( )); - style id4 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `2 <=> max(2, 4, 5)`. -* `2 <=> max(2, 1, 4)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((5))-->id1((4)); - id0-->id2((2)); - id1-->id3((1)); - id1-->id4(( )); - style id4 fill:#fff,stroke:#fff -``` - -:::: - -::: - -``` - | 5 | 4 | 2 | 1 || 6 | 8 | 10 | 12 | 16 -``` - -# Exemple de tri par tas (12/N) - -``` - | 5 | 4 | 2 | 1 || 6 | 8 | 10 | 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `5` (`max` de l'arbre) avec `1`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((4)); - id0-->id2((2)); -``` - -:::: - -:::: column - -**But:** Trier les tas. - -* `1 <=> max(1, 4, 2)`. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((4))-->id1((1)); - id0-->id2((2)); -``` - -:::: - -::: - -``` - | 4 | 1 | 2 || 5 | 6 | 8 | 10 | 12 | 16 -``` - -# Exemple de tri par tas (13/N) - -``` - | 4 | 1 | 2 || 5 | 6 | 8 | 10 | 12 | 16 -``` - -::: columns - -:::: column - -**But:** Trier les tas. - -* Échanger la racine, `4` (`max` de l'arbre) avec `2`. -* Traiter la racine. - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((2))-->id1((1)); - id0-->id2(( )); - style id2 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -**But:** Trier les tas. Plus rien à trier - -* On fait les 2 dernières étapes en vitesse. -* Échange `2` avec `1`. -* Il reste que `1`. GGWP! - - -:::: - -::: - -``` - | 1 | 2 | 4 | 5 | 6 | 8 | 10 | 12 | 16 -``` - -# Exercice (10min) - -* Trier par tas le tableau - -``` - | 1 | 2 | 4 | 5 | 6 | 8 | 10 | 12 | 16 -``` - -* Mettez autant de détails que possible. -* Que constatez-vous? -* Postez le résultat sur matrix. - - diff --git a/slides/cours_17.md b/slides/cours_17.md deleted file mode 100644 index 014b123..0000000 --- a/slides/cours_17.md +++ /dev/null @@ -1,695 +0,0 @@ ---- -title: "Tri par tas et arbres AVL" -date: "2023-03-24" ---- - -# Questions sur les notions du dernier cours (1/2) - -* Comment représenter un tableau sous forme d'arbre binaire? - -. . . - -* Qu'est-ce qu'un tas? - -. . . - -``` - | 1 | 16 | 5 | 12 | 4 | 2 | 8 | 10 | 6 | 7 | -``` - -* Quel est l'arbre que cela représente? - - -# Questions sur les notions du dernier cours (2/2) - - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((1))-->id1((16)); - id0-->id2((5)); - id1-->id3((12)); - id1-->id4((4)); - id2-->id5((2)); - id2-->id6((8)); - id3-->id7((10)); - id3-->id8((6)); - id4-->id9((7)); - id4-->id10(( )); - style id10 fill:#fff,stroke:#fff -``` - -# Exercice - -* Trier par tas le tableau - -``` - | 1 | 2 | 4 | 5 | 6 | 8 | 10 | 12 | 16 -``` - -* Mettez autant de détails que possible. -* Que constatez-vous? -* Postez le résultat sur matrix. - -# L'algorithme du tri par tas (1/2) - -\footnotesize - -## Deux étapes - -1. Entassement: transformer l'arbre en tas. -2. Échanger la racine avec le dernier élément et entasser la racine. - -## Pseudo-code d'entassement de l'arbre (15 min, matrix) - -. . . - -```python -rien tri_par_tas(tab) - entassement(tab) - échanger(tab[0], tab[size(tab)-1]) - pour i de size(tab)-1 à 2 - tamisage(tab, 0) - échanger(tab[0], tab[i-1]) - -rien entassement(tab) - pour i de size(tab)/2-1 à 0 - tamisage(tab, i) - -rien tamisage(tab, i) - ind_max = ind_max(tab, i, gauche(i), droite(i)) - si i != ind_max - échanger(tab[i], tab[ind_max]) - tamisage(tab, ind_max) -``` - -# L'algorithme du tri par tas (2/2) - -* Fonctions utilitaires - - ```python - entier ind_max(tab, i, g, d) - ind_max = i - si tab[ind_max] < tab[g] && size(tab) > g - ind_max = g - si tab[ind_mx] < tab[d] && size(tab) > d - ind_max = d - retourne ind_max - - entier gauche(i) - retourne 2 * i + 1 - - entier droite(i) - retourne 2 * i + 2 - ``` - - -<!-- # L'algorithme du tri par tas (3/4) - -\footnotesize - -## Implémenter en C l'algorithme du tri par tas (matrix, 20min) - -. . . - -```C -void heapsort(int size, int tab[size]) { - heapify(size, tab); - swap(tab, tab + size - 1); - for (int s = size - 1; s > 1; s--) { - sift_up(s, tab, 0); - swap(tab, tab + s - 1); - } -} -void heapify(int size, int tab[size]) { - for (int i = size / 2 - 1; i >= 0; i--) { - sift_up(size, tab, i); - } -} -void sift_up(int size, int tab[size], int i) { - int ind_max = ind_max3(size, tab, i, left(i), right(i)); - if (i != ind_max) { - swap(tab + i, tab + ind_max); - sift_up(size, tab, ind_max); - } -} -``` - -# L'algorithme du tri par tas (4/4) - -\footnotesize - -## Fonctions utilitaires - -. . . - -```C -int ind_max3(int size, int tab[size], int i, int l, int r) { - int ind_max = i; - if (l < size && tab[ind_max] < tab[l]) { - ind_max = l; - } - if (r < size && tab[ind_max] < tab[r]) { - ind_max = r; - } - return ind_max; -} -void swap(int *a, int *b) { - int tmp = *a; - *a = *b; - *b = tmp; -} -int left(int i) { - return 2 * i + 1; -} -int right(int i) { - return 2 * i + 2; -} -``` --> - - -# Complexités - -::: columns - -:::: column - -## Complexité de la recherche - -1. En moyenne? -2. Dans le pire des cas? - -. . . - -1. $O(\log_2(N))$ -2. $O(N)$ - -:::: - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((10))-->id1((9)); - id0-->id8(( )); - id1-->id2((7)); - id1-->id9(( )); - id2-->id3((6)); - id2-->id10(( )); - id3-->id4((5)); - id3-->id11(( )); - style id8 fill:#fff,stroke:#fff - style id9 fill:#fff,stroke:#fff - style id10 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Un meilleur arbre - -* Quelle propriété doit satisfaire un arbre pour être $O(\log_2(N))$? - -. . . - -* Si on a environ le même nombre de nœuds dans les sous-arbres. - -## Définition - -Un arbre binaire est parfaitement équilibré si, pour chaque -nœud, la différence entre les nombres de nœuds des sous- -arbres gauche et droit vaut au plus 1. - -* Si l'arbre est **parfaitement équilibré**, alors tout ira bien. -* Quelle est la hauteur (profondeur) d'un arbre parfaitement équilibré? - -. . . - -* $O(\log_2(N))$. - - -# Équilibre parfait ou pas? - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((W))-->id1((B)); - id0-->id8((Y)); - id1-->id2((A)); - id1-->id9(( )); - id8-->id10((X)); - id8-->id11(( )); - style id9 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -``` -É -Q -U -I -L -I -B -R -É -``` - -:::: - -::: - -# Équilibre parfait ou pas? - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((16))-->id1((10)); - id0-->id2((19)); - id1-->id3((8)); - id1-->id4((12)); - id4-->id5((11)); - id4-->id6(( )); - id2-->id7((17)); - id2-->id8(( )); - id7-->id9(( )); - id7-->id10((18)); - style id6 fill:#fff,stroke:#fff - style id8 fill:#fff,stroke:#fff - style id9 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -``` -P -A -S - -É -Q -U -I -L -I -B -R -É -``` - -:::: - -::: - -# Arbres AVL - -\footnotesize - -* Quand est-ce qu'on équilibre un arbre? - -. . . - -* A l'insertion/suppression. -* Maintenir un arbre en état d'équilibre parfait: cher (insertion, suppression). -* On peut "relaxer" la condition d'équilibre: profondeur (hauteur) au lieu du - nombre de nœuds: - * La hauteur $\sim\log_2(N)$. - -## Définition - -Un arbre AVL (Adelson-Velskii et Landis) est un arbre binaire de recherche dans -lequel, pour chaque nœud, la différence de hauteur entre le sous-arbre gauche et droit vaut au plus 1. - -* Relation entre nœuds et hauteur: -$$ -\log_2(1+N)\leq 1+H\leq 1.44\cdot\log_2(2+N),\quad N=10^5,\ 17\leq H \leq 25. -$$ -* Permet l'équilibrage en temps constant: insertion/suppression $O(\log_2(N))$. - -# Notation - -* `hg`: hauteur du sous-arbre gauche. -* `hd`: hauteur du sous-arbre droit. -* `facteur d'équilibre = fe = hd - hg` -* Que vaut `fe` si l'arbre est AVL? - -. . . - -* `fe = {-1, 0, 1}` - - -# AVL ou pas? - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((10)); - id0-->id2((19)); - id2-->id3((17)); - id2-->id4((97)); -``` - -:::: - -:::: column - -. . . - -``` -A -V -L -``` - -:::: - -::: - -# AVL ou pas? - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id2-->id3((17)); - id2-->id4((97)); - id4-->id5((37)); - id4-->id6(( )); - style id6 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -``` -P -A -S - -A -V -L -``` - -:::: - -::: - -# Insertion dans un arbre AVL (1/N) - -1. On part d'un arbre AVL. -2. On insère un nouvel élément. - -::: columns - -:::: column - -* `hd ? hg`. -* Insertion de `4`? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); -``` - -:::: - -:::: column - -. . . - -* `hd = hg` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id1-->id4(( )); - id1-->id5((4)); - style id4 fill:#fff,stroke:#fff -``` - -* `fe = -1` - -:::: - -::: - -# Insertion dans un arbre AVL (2/N) - -1. On part d'un arbre AVL. -2. On insère un nouvel élément. - -::: columns - -:::: column - -* `hd ? hg`. -* Insertion de `4`? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id2-->id3((18)); - id2-->id4(( )); - style id4 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -* `hd < hg` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id2-->id3((18)); - id2-->id4(( )); - id1-->id5(( )); - id1-->id6((4)); - style id4 fill:#fff,stroke:#fff - style id5 fill:#fff,stroke:#fff -``` - -* `fe = 0` - -:::: - -::: - -# Insertion dans un arbre AVL (3/N) - -\footnotesize - -1. On part d'un arbre AVL. -2. On insère un nouvel élément. - -::: columns - -:::: column - -* `hd ? hg`. -* Insertion de `4`? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id1-->id3(( )); - id1-->id4((6)); - id2-->id5(( )); - id2-->id6(( )); - style id3 fill:#fff,stroke:#fff - style id5 fill:#fff,stroke:#fff - style id6 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -* `hd < hg` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id1-->id3(( )); - id1-->id4((6)); - id4-->id5((4)); - id4-->id6(( )); - id2-->id7(( )); - id2-->id8(( )); - style id3 fill:#fff,stroke:#fff - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff - style id8 fill:#fff,stroke:#fff -``` - -:::: - -::: - -**Déséquilibre!** Que vaut `fe`? - -. . . - -* `fe = 2` - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 1a - -* `u`, `v`, `w` même hauteur. -* déséquilibre en `B` après insertion dans `u` - - - -:::: - -:::: column - -## Cas 1a - -* Comment rééquilibrer? - -. . . - -* ramène `u`, `v` `w` à la même hauteur. -* `v` à droite de `A` (gauche de `B`) - - - -:::: - -::: - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 1b (symétrique 1a) - - - -:::: - -:::: column - -## Cas 1b (symétrique 1a) - -* Comment rééquilibrer? - -. . . - - - -:::: - -::: - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 2a - -* `h(v1)=h(v2), h(u)=h(w)`. -* déséquilibre en `C` après insertion dans `v2` - - - -:::: - -:::: column - -## Cas 2a - -* Comment rééquilibrer? - -. . . - -* ramène `u`, `v2`, `w` à la même hauteur (`v1` pas tout à fait). -* `v2` à droite de `B` (gauche de `C`) -* `B` à droite de `A` (gauche de `C`) -* `v1` à droite de `A` (gauche de `B`) - - - -:::: - -::: - - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 2b (symétrique 2a) - - - -:::: - -:::: column - -## Cas 2b (symétrique 2a) - -* Comment rééquilibrer? - -. . . - - - -:::: - -::: diff --git a/slides/cours_18.md b/slides/cours_18.md deleted file mode 100644 index 15f609f..0000000 --- a/slides/cours_18.md +++ /dev/null @@ -1,947 +0,0 @@ ---- -title: "Arbres AVL" -date: "2023-03-31" ---- - -# Questions sur les notions du dernier cours - -* Qu'est-ce qu'un arbre AVL? - -. . . - -* Un arbre binaire qui a la propriété suivante: - * La différence de hauteur de chaque noeud est d'au plus 1. - * Tous les noeuds ont `fe = hd - hg = {-1, 0, 1}`. - -* Pourquoi utiliser un arbre AVL plutôt qu'un arbre binaire de recherche? - -. . . - -* Insertion/recherche/... toujours en $O(\log_2(N))$. - -# AVL ou pas? - -\footnotesize - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((21))-->id1((9)); - id0-->id2((40)); - id1-->id3((5)); - id1-->id4((10)); - id3-->id5((3)); - id3-->id6((7)) - id6-->id7((6)) - id6-->id8(( )) - id2-->id9((33)) - id2-->id10((61)) - id9-->id11((22)) - id9-->id12((39)) - id10-->id13(( )) - id10-->id14((81)) - style id8 fill:#fff,stroke:#fff - style id13 fill:#fff,stroke:#fff -``` - -. . . - -* Ajouter un noeud pour qu'il le soit plus. - -# Insertion dans un arbre AVL - -\footnotesize - -1. On part d'un arbre AVL. -2. On insère un nouvel élément. - -::: columns - -:::: column - -* `hd ? hg`. -* Insertion de `4`? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id1-->id3(( )); - id1-->id4((6)); - id2-->id5(( )); - id2-->id6(( )); - style id3 fill:#fff,stroke:#fff - style id5 fill:#fff,stroke:#fff - style id6 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -* `hd > hg` - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((12))-->id1((1)); - id0-->id2((19)); - id1-->id3(( )); - id1-->id4((6)); - id4-->id5((4)); - id4-->id6(( )); - id2-->id7(( )); - id2-->id8(( )); - style id3 fill:#fff,stroke:#fff - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff - style id8 fill:#fff,stroke:#fff -``` - -:::: - -::: - -**Déséquilibre!** Que vaut `fe`? - -. . . - -* `fe = 2` - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 1a - -* `u`, `v`, `w` même hauteur. -* déséquilibre en `B` après insertion dans `u` - - - -:::: - -:::: column - -## Cas 1a - -* Comment rééquilibrer? - -. . . - -* ramène `u`, `v` `w` à la même hauteur. -* `v` à droite de `A` (gauche de `B`) - - - -:::: - -::: - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 1b (symétrique 1a) - - - -:::: - -:::: column - -## Cas 1b (symétrique 1a) - -* Comment rééquilibrer? - -. . . - - - -:::: - -::: - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 2a - -* `h(v1)=h(v2), h(u)=h(w)`. -* déséquilibre en `C` après insertion dans `v2` - - - -:::: - -:::: column - -## Cas 2a - -* Comment rééquilibrer? - -. . . - -* ramène `u`, `v2`, `w` à la même hauteur (`v1` pas tout à fait). -* `v2` à droite de `B` (gauche de `C`) -* `B` à droite de `A` (gauche de `C`) -* `v1` à droite de `A` (gauche de `B`) - - - -:::: - -::: - - -# Les cas de déséquilibre - - -::: columns - -:::: column - -## Cas 2b (symétrique 2a) - - - -:::: - -:::: column - -## Cas 2b (symétrique 2a) - -* Comment rééquilibrer? - -. . . - - - -:::: - -::: - -# Le facteur d'équilibre (balance factor) - -## Définition - -``` -fe(arbre) = hauteur(droite(arbre)) - hauteur(gauche(arbre)) -``` - -## Valeurs possibles? - -. . . - -``` -fe = {-1, 0, 1} // arbre AVL -fe = {-2, 2} // arbre déséquilibré -``` - -{width=40%} - -# Algorithme d'insertion - -* Insérer le noeud comme d'habitude. -* Mettre à jour les facteurs d'équilibre jusqu'à la racine (ou au premier - noeud déséquilibré). -* Rééquilibrer le noeud si nécessaire. - -## Cas possibles - -::: columns - -:::: column - -## Sous-arbre gauche (avant) - -``` -fe(P) = 1 -fe(P) = 0 -fe(P) = -1 -``` - -:::: - -:::: column - -## Sous-arbre gauche (après) - -. . . - -``` -=> fe(P) = 0 -=> fe(P) = -1 -=> fe(P) = -2 // Rééquilibrer P -``` - -:::: - -::: - -# Algorithme d'insertion - -* Insérer le noeud comme d'habitude. -* Mettre à jour les facteurs d'équilibre jusqu'à la racine (ou au premier - noeud déséquilibré). -* Rééquilibrer le noeud si nécessaire. - -## Cas possibles - -::: columns - -:::: column - -## Sous-arbre droit (avant) - -``` -fe(P) = 1 -fe(P) = 0 -fe(P) = -1 -``` - -:::: - -:::: column - -## Sous-arbre droit (après) - -. . . - -``` -=> fe(P) = 0 -=> fe(P) = +1 -=> fe(P) = +2 // Rééquilibrer P -``` - -:::: - -::: - -# Rééquilibrage - -## Lien avec les cas vus plus tôt - -``` -fe(P) = -2 && fe(gauche(P)) = -1 => cas 1a -fe(P) = -2 && fe(gauche(P)) = +1 => cas 2a - -fe(P) = +2 && fe(droite(P)) = -1 => cas 2b -fe(P) = +2 && fe(droite(P)) = +1 => cas 1b -``` - -## Dessiner les différents cas, sur le dessin ci-dessous - - - -# La rotation - -## La rotation gauche (5min, matrix) - - - -. . . - -\footnotesize -``` -arbre rotation_gauche(arbre P) - si est_non_vide(P) - Q = droite(P) - droite(P) = gauche(Q) - gauche(Q) = P - retourne Q - retourne P -``` - -# La rotation en C (1/2) - -## La rotation gauche - -``` -arbre rotation_gauche(arbre P) - si est_non_vide(P) - Q = droite(P) - droite(P) = gauche(Q) - gauche(Q) = P - retourne Q - retourne P -``` - -## Écrire le code C correspondant (5min, matrix) - -1. Structure de données -2. Fonction `tree_t rotation_left(tree_t tree)` - -. . . - -\footnotesize -```C -typedef struct _node { - int key; - struct _node *left, *right; - int bf; // balance factor -} node; -typedef node *tree_t; -``` - -# La rotation en C (2/2) - -\footnotesize - -```C -tree_t rotation_left(tree_t tree) { - tree_t subtree = NULL; - if (NULL != tree) { - subtree = tree->right; - tree->right = subtree->left; - subtree->left = tree; - } - return subtree; -} -``` - -. . . - -* Et la rotation à droite, pseudo-code (5min)? - -. . . - -``` -arbre rotation_droite(arbre P) - si est_non_vide(P) - Q = gauche(P) - gauche(P) = droite(Q) - droite(Q) = P - retourne Q - retourne P -``` - -<!-- ```C -tree_t rotation_right(tree_t tree) { - tree_t subtree = NULL; - if (NULL != tree) { - subtree = tree->left; - tree->left = subtree->right; - subtree->right = tree; - } - return subtree; -} -``` --> - -# Exemple de rotation (1/2) - -## Insertion de 9? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((5))-->id1((1)); - id0-->id2((6)); - id2-->id3(( )); - id2-->id4((8)); - style id3 fill:#fff,stroke:#fff -``` - -# Exemple de rotation (2/2) - -::: columns - -:::: column - -## Quelle rotation et sur quel noeud (5 ou 6)? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((5))-->id1((1)); - id0-->id2((6)); - id2-->id3(( )); - id2-->id4((8)); - id4-->id5(( )); - id4-->id6((9)); - style id3 fill:#fff,stroke:#fff - style id5 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Sur le plus jeune évidemment! - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((5))-->id1((1)); - id0-->id2((8)); - id2-->id3((6)); - id2-->id4((9)); -``` - -:::: - -::: - -* Cas `1a/b` *check*! - - -# La rotation gauche-droite - -## Là c'est plus difficile (cas 2a/b) - - - -# Exercices - -## Faire l'implémentation de la double rotation (pas corrigé, 5min) - -# Exercices - -::: columns - -:::: column - -## Insérer 50, ex 10min (matrix) - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((89))-->id1((71)); - id0-->id2((90)); - id1-->id3((44)); - id3-->id4((37)); - id3-->id5((61)); - id1-->id6((81)) - id2-->id7(( )) - id2-->id8((100)) - style id7 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Où se fait la rotation? - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((89))-->id1((71)); - id0-->id2((90)); - id1-->id3((44)); - id3-->id4((37)); - id3-->id5((61)); - id1-->id6((81)) - id2-->id7(( )) - id2-->id8((100)) - id5-->id9((50)) - id5-->id10(( )) - style id7 fill:#fff,stroke:#fff - style id10 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Exercices - -::: columns - -:::: column - -## Rotation gauche en 44 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((89))-->id1((71)); - id0-->id2((90)); - id1-->id3((61)); - id1-->id10((81)); - id3-->id4((44)); - id3-->id5(( )); - id4-->id6((37)) - id4-->id7((50)) - id2-->id8(( )) - id2-->id9((100)) - style id5 fill:#fff,stroke:#fff - style id8 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Rotation à droite en 71 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((89))-->id1((61)); - id0-->id2((90)); - id1-->id3((44)); - id1-->id10((71)); - id3-->id4((37)); - id3-->id5((50)); - id2-->id8(( )); - id2-->id9((100)); - id10-->id11(( )) - id10-->id12((81)) - style id8 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Exercice de la mort - -Soit l’arbre AVL suivant: - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((60))-->id1((40)); - id0-->id2((120)); - id1-->id3((20)); - id1-->id4((50)); - id3-->id5((10)); - id3-->id6((30)); - id2-->id7((100)); - id2-->id8((140)); - id7-->id9((80)) - id7-->id10((110)) - id9-->id11((70)) - id9-->id12((90)) - id8-->id13((130)) - id8-->id14((160)) - id14-->id15((150)) - id14-->id16((170)) -``` - -:::: - -:::: column - -1. Montrer les positions des insertions de feuille qui conduiront à un arbre - désequilibré. -2. Donner les facteurs d’equilgaucheibre. -3. Dessiner et expliquer les modifications de l’arbre lors de l’insertion de la - valeur `65`. On mentionnera les modifications des facteurs - d’équilibre. - -:::: - -::: - -# Encore un petit exercice - -* Insérer les nœuds suivants dans un arbre AVL - -``` -25 | 60 | 35 | 10 | 5 | 20 | 65 | 45 | 70 | 40 - | 50 | 55 | 30 | 15 -``` - -## Un à un et le/la premier/ère qui poste la bonne réponse sur matrix a un point - -# Suppression dans un arbre AVL - - -::: columns - -:::: column - -## Algorithme par problème: supprimer 10 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((8))-->id1((4)); - id0-->id2((10)); - id1-->id3((2)); - id1-->id4((6)); - id3-->id5((1)); - id3-->id6(( )) - id4-->id7(( )) - id4-->id8((7)) - id2-->id9((9)) - id2-->id10((14)) - id10-->id11((12)) - id10-->id12((16)) - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Algorithme par problème: supprimer 10 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((8))-->id1((4)); - id0-->id2((12)); - id1-->id3((2)); - id1-->id4((6)); - id3-->id5((1)); - id3-->id6(( )) - id4-->id7(( )) - id4-->id8((7)) - id2-->id9((9)) - id2-->id10((14)) - id10-->id11(( )) - id10-->id12((16)) - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Suppression dans un arbre AVL - - -::: columns - -:::: column - -## Algorithme par problème: supprimer 8 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((8))-->id1((4)); - id0-->id2((12)); - id1-->id3((2)); - id1-->id4((6)); - id3-->id5((1)); - id3-->id6(( )) - id4-->id7(( )) - id4-->id8((7)) - id2-->id9((9)) - id2-->id10((14)) - id10-->id11(( )) - id10-->id12((16)) - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Algorithme par problème: rotation de 12 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((9))-->id1((4)); - id0-->id2((12)); - id1-->id3((2)); - id1-->id4((6)); - id3-->id5((1)); - id3-->id6(( )) - id4-->id7(( )) - id4-->id8((7)) - id2-->id9(( )) - id2-->id10((14)) - id10-->id11(( )) - id10-->id12((16)) - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff - style id9 fill:#fff,stroke:#fff - style id11 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Suppression dans un arbre AVL - -::: columns - -:::: column - -## Algorithme par problème: rotation de 12 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((9))-->id1((4)); - id0-->id2((14)); - id1-->id3((2)); - id1-->id4((6)); - id3-->id5((1)); - id3-->id6(( )) - id4-->id7(( )) - id4-->id8((7)) - id2-->id9((12)) - id2-->id10((16)) - style id6 fill:#fff,stroke:#fff - style id7 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -1. On supprime comme d'habitude. -2. On rééquilibre si besoin à l'endroit de la suppression. - -* Facile non? - -. . . - -* Plus dur.... - -:::: - -::: - -# Suppression dans un arbre AVL 2.0 - -::: columns - -:::: column - -## Algorithme par problème: suppression de 30 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((50))-->id1((30)); - id0-->id2((100)); - id1-->id3((10)); - id1-->id4((40)); - id3-->id5(( )); - id3-->id6((20)) - id2-->id7((80)) - id2-->id8((200)) - id7-->id9((70)) - id7-->id10((90)) - id9-->id11((60)) - id9-->id12(( )) - id8-->id13(( )) - id8-->id14((300)) - style id5 fill:#fff,stroke:#fff - style id12 fill:#fff,stroke:#fff - style id13 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Algorithme par problème: rotation GD autour de 40 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((50))-->id1((40)); - id0-->id2((100)); - id1-->id3((10)); - id1-->id4(( )); - id3-->id5(( )); - id3-->id6((20)) - id2-->id7((80)) - id2-->id8((200)) - id7-->id9((70)) - id7-->id10((90)) - id9-->id11((60)) - id9-->id12(( )) - id8-->id13(( )) - id8-->id14((300)) - style id4 fill:#fff,stroke:#fff - style id5 fill:#fff,stroke:#fff - style id12 fill:#fff,stroke:#fff - style id13 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Suppression dans un arbre AVL 2.0 - -::: columns - -:::: column - -## Argl! 50 est déséquilibré! - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((50))-->id1((20)); - id0-->id2((100)); - id1-->id3((10)); - id1-->id4((40)); - id2-->id7((80)) - id2-->id8((200)) - id7-->id9((70)) - id7-->id10((90)) - id9-->id11((60)) - id9-->id12(( )) - id8-->id13(( )) - id8-->id14((300)) - style id12 fill:#fff,stroke:#fff - style id13 fill:#fff,stroke:#fff -``` - -:::: - -:::: column - -. . . - -## Algorithme par problème: rotation DG autour de 50 - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((80))-->id1((50)); - id0-->id2((100)); - id1-->id3((20)); - id1-->id4((70)); - id3-->id5((10)); - id3-->id6((40)); - id4-->id9((60)) - id4-->id10(( )) - id2-->id7((90)) - id2-->id8((200)) - id8-->id13(( )) - id8-->id14((300)) - style id10 fill:#fff,stroke:#fff - style id13 fill:#fff,stroke:#fff -``` - -:::: - -::: - -# Résumé de la suppression - -1. On supprime comme pour un arbre binaire de recherche. -2. Si un nœud est déséquilibré, on le rééquilibre. - * Cette opération peut déséquilibrer un autre nœud. -3. On continue à rééquilibrer tant qu'il y a des nœuds à équilibrer. diff --git a/slides/cours_19.md b/slides/cours_19.md deleted file mode 100644 index 4394b42..0000000 --- a/slides/cours_19.md +++ /dev/null @@ -1,488 +0,0 @@ ---- -title: "Arbres quaternaires" -date: "2023-04-28" ---- - - -# Les arbres quaternaires - -## Définition - -Arbre dont chaque nœud a 4 enfants ou aucun. - - - -# Les arbres quaternaires - -## Cas d'utilisation - -Typiquement utilisés pour représenter des données bidimensionnelles. - -Son équivalent tri-dimensionnel est l'octree (chaque nœud a 8 enfants ou aucun). - -## Cas d'utilisation: images - -* Stockage: compression. -* Transformations: symétries, rotations, etc. - -## Cas d'utilisation: simulation - -* Indexation spatiale. -* Détection de collisions. -* Simulation de galaxies, Barnes-Hut. - -# Exemple de compression - -::: columns - -:::: {.column width=30%} - -## Comment représenter l'image - - - -:::: - -:::: {.column width=70%} - -## Sous la forme d'un arbre quaternaire? - -. . . - - - -**Économie?** - -. . . - -Image 64 pixels, arbre 25 nœuds. - -:::: - -::: - - -# Structure de données - -::: columns - -:::: {.column width=50%} - -## Pseudo-code? - -. . . - -```python -struct node - info - node sup_gauche, sup_droit, - inf_gauche, inf_droit -``` - - - -:::: - -:::: {.column width=50%} - -## En C? - -. . . - -```C -struct _node { - int info; - struct _node *sup_left; - struct _node *sup_right; - struct _node *inf_left; - struct _node *inf_right; -}; -``` - -* Pourquoi le `*` est important? - -. . . - -* Type récursif => taille inconnue à la compilation. - -:::: - -::: - -# Une fonctionnalité simple - -\footnotesize - -## La fonction `est_feuille(noeud)` - -* Problème avec cette implémentation? - -```python -bool est_feuille(noeud) - retourne - est_vide(sup_gauche(noeud)) && - est_vide(sup_droit(noeud)) && - est_vide(inf_gauche(noeud)) && - est_vide(inf_droit(noeud)) -``` - -. . . - -* Inutile d'avoir 4 conditions (soit 4 enfants soit aucun!) -* Facile d'en oublier un! -* Comment changer la structure pour que ça soit moins terrible? - -. . . - -```python -struct node - info - node enfant[4] -``` - -# Structure de données - -## En C? - -. . . - -```C -typedef struct _node { - int info; - struct _node *child[4]; -} node; -``` - -## Fonction `is_leaf(node *tree)`? - -. . . - -```C -bool is_leaf(node *tree) { - return (NULL == tree->child[0]); // only first matters -} -``` - -# Problème à résoudre - -* Construire un arbre quaternaire à partir d'une image: - * Créer l'arbre (allouer la mémoire pour tous les nœuds), - * Le remplir avec les valeurs des pixels. -* Compression de l'image: - * Si les pixels sont les mêmes dans le quadrant on supprime le sous-arbre (sans perte) - * Si les pixels dévient pas trop on supprime le quadrant (avec perte) - -# Création de l'arbre - -## Comment créer un arbre de profondeur `prof` (3min)? - -. . . - -```python -arbre creer_arbre(prof) - n = nouveau_noeud() # alloue la mémoire - si prof > 0 - pour i = 0 à 3 - n.enfant[i] = creer_arbre(prof-1) - retourne n -``` - -## En `C` (3 min, matrix)? - -. . . - -```C -node *qt_create(int depth) { - node *n = calloc(1, sizeof(node)); - if (depth > 0) { - for (int i = 0; i < 4; ++i) { - n->child[i] = qt_create(depth-1); - } - } - return n; -} -``` - -# Le nombre de nœuds? - -## Comment implémenter la fonction (pseudo-code, 5min, matrix)? - -. . . - -```C -entier nombre_nœuds(arbre) - si est_feuille(arbre) - retourne 1 - sinon - somme = 1 - pour i de 0 à 3 - somme += nombre_nœuds(arbre.enfant[i]) - retourne somme -``` - -# Le nombre de nœuds? - -## Comment implémenter la fonction en C (3min, matrix)? - -. . . - -```C -int size(node *qt) { - if (is_leaf(qt)) { - return 1; - } else { - int sum = 1; - for (int i = 0; i < 4; ++i) { - sum += size(qt->child[i]); - } - return sum; - } -} -``` - -# La profondeur en C? - -## Implémentation (5min, matrix) - -. . . - -\footnotesize - -```C -int max(int x, int y) { - return (x >= y ? x : y); -} -int max_depth(int depths[4]) { - int m = depths[0]; - for (int i = 1; i < 4; ++i) { - m = max(m, depths[i]); - } - return m; -} -int depth(node *qt) { - int depths[] = {0, 0, 0, 0}; - if (is_leaf(qt)) { - return 0; - } else { - for (int i = 0; i < 4; ++i) { - depths[i] = depth(qt->child[i]); - } - return 1 + max_depth(depths); - } -} -``` - -# Fonctions utiles (1/4) - -## Comment remplir un arbre depuis une matrice? - -``` - SG=0 | SD=1 - 21 | 12 | 4 | 4 - 9 | 7 | 4 | 4 ------------------ - 1 | 1 | 0 | 31 - 1 | 1 | 3 | 27 - IG=2 | ID=3 -``` - -## Quel arbre cela représente? - -. . . - - - -# Fonctions utiles (2/4) - -* On veut transformer une ligne/colonne en feuille. -* Comment? - -::: columns - -:::: {.column width=40%} - -## Soit `ligne=2`, `colonne=3` - -``` - SG=0 | SD=1 - 21 | 12 | 4 | 4 - 9 | 7 | 4 | 4 ------------------ - 1 | 1 | 0 | 31 - 1 | 1 | 3 | 27 - IG=2 | ID=3 -``` - -:::: - -:::: {.column width=70%} - -## Trouver un algorithme - - - -* Quelle feuille pour 31 (`li=2`, `co=3`)? -* Plus important: quel chemin? - -. . . - -* `co -> G/D`, `li -> S/I`, -* `2 * (li / 2) + co / 2 -> 2 * 1 + 1 = 3` -* `2 * ((li % 2) / 1) + (co % 2) / 1 -> 2 * 0 + 1 = 1` -* Comment généraliser? - -:::: - -::: - -# Fonctions utiles (3/4) - -::: columns - -:::: {.column width=40%} - -## Soit `ligne=2`, `colonne=3` - -``` - SG=0 | SD=1 - 21 | 12 | 4 | 4 - 9 | 7 | 4 | 4 ------------------ - 1 | 1 | 0 | 31 - 1 | 1 | 3 | 27 - IG=2 | ID=3 -``` - -:::: - -:::: {.column width=70%} - -## Trouver un algorithme (prendre plusieurs exemples, 15min, matrix) - - - -* Comment généraliser? - -. . . - -```C -noeud position(li, co, arbre) - d = profondeur(arbre); - tant_que (d >= 1) - index = 2 * ((li % 2^d) / 2^(d-1)) + - (col % 2^d) / 2^(d-1) - arbre = arbre.enfant[index] - d -= 1 - retourne arbre -``` - - -:::: - -::: - -# Fonctions utiles (4/4) - -\footnotesize - -## Pseudo-code - -```C -noeud position(li, co, arbre) - d = profondeur(arbre); - tant_que (d >= 1) - index = 2 * ((li % 2^d) / 2^(d-1)) + - (col % 2^d) / 2^(d-1) - arbre = arbre.enfant[index] - d -= 1 - retourne arbre -``` - -## Écrire le code `C` correspondant (5min, matrix) - -```C - - - - - - - - - - - -``` - -# Remplir l'arbre - -## A partir d'une matrice (pseudo-code, 5min, matrix)? - -. . . - -```C -arbre matrice_à _arbre(matrice) - arbre = creer_arbre(profondeur) - pour li de 0 à nb_lignes(matrice) - pour co de 0 à nb_colonnes(matrice) - noeud = position(li, co, arbre) - noeud.info = matrice[co][li] - retourne arbre -``` - -. . . - -## A partir d'une matrice (C, 5min, matrix)? - -. . . - -\footnotesize - -```C -node *matrix_to_qt(int nb_li, int nb_co, int matrix[nb_li][nb_co], int depth) -{ - node *qt = qt_create(depth); - for (int li = 0; li < nd_li; ++li) { - for (int co = 0; co < nd_co; ++co) { - node *current = position(li, co, qt); - current->info = matrix[li][co]; - } - } - return qt; -} -``` - - -# Remplir la matrice - -## A partir de l'arbre (pseudo-code, 3min, matrix)? - -. . . - -```C -matrice arbre_à _matrice(arbre) - matrice = creer_matrice(nb_lignes(arbre), nb_colonnes(arbre)) - pour li de 0 à nb_lignes(matrice) - pour co de 0 à nb_colonnes(matrice) - noeud = position(li, co, arbre) - matrice[co][li] = noeud.info - retourne matrice -``` - -. . . - -## A partir de l'arbre (C, 3min, matrix)? - -. . . - -\footnotesize - -```C -void qt_to_matrix(node *qt, int nb_li, int nb_co, int matrix[nb_li][nb_co]) - for (int li = 0; li < nd_li; ++li) { - for (int co = 0; co < nd_co; ++co) { - node *current = position(li, co, qt); - matrix[li][co] = current->info; - } - } -``` diff --git a/slides/cours_2.md b/slides/cours_2.md deleted file mode 100644 index 660bf81..0000000 --- a/slides/cours_2.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title: "Introduction aux algorithmes" -date: "2022-09-28" ---- - -# Rappel (1/2) - -* Quel est l'algorithme pour le calcul des nombres 1ers? - -. . . - -```C -bool est_premier(int nombre) { - int i = 2; // bonne pratique! - while (i < nombre) { // penser à bien indenter! - if (0 == nombre % i) { // ça rend le code LISIBLE! - return false; - } - i += 1; - } - return true; -} -``` - -# Rappel (2/2) - -* Quelles structures de contrôles avons nous vues? - -. . . - -* La boucle `while`, -* La boucle `do ... while`, -* La boucle `for`, -* La condition `if ... else if ... else`, - -# Exercice: la factorielle - -Écrire un programme qui calcule la factorielle d'un nombre -$$ -N! = 1\cdot 2\cdot ... \cdot (N-1)\cdot N. -$$ - -## Par groupe de 3: écrire un pseudo-code - -. . . - -```python -entier factorielle(entier n) - i = 1 - fact = 1 - tant que i <= n - fact *= i - i += 1 - retourne fact -``` - -# Exercice: la factorielle - -\footnotesize - -Écrire un programme qui calcule la factorielle d'un nombre -$$ -N! = 1\cdot 2\cdot ... \cdot (N-1)\cdot N. -$$ - -## Par groupe de 3: écrire un code en C - -Quand vous avez fini postez le code sur le salon matrix. - -. . . - -```C -#include <stdio.h> -int main() { - int nb = 10; - int fact = 1; - int iter = 2; - while (iter <= nb) { - fact *= iter; - iter++; - } - printf("La factorielle de %d est %d\n", nb, fact); -} -``` - -. . . - -## Comment améliorer ce code? (notez ça sur une feuille) - - -# Exercice: la factorielle en mieux - -## Individuellement - -1. Écrivez l'algorithme de calcul de deux façon différentes. -2. Que se passe-t-il si $n>=15$? -3. Pour celles et ceux qui ont fini pendant que les autres essaient: faites-le - en récursif (sans aide). - -. . . - -## Postez vos solutions sur **matrix**! - -# Exercice: test si un nombre est premier - -## Avec tout ce que vous avez appris jusqu'ici: - -* Écrivez le code testant si un nombre est premier. -* Quels sont les ajouts possibles par rapport au code de la semaine passée? -* Rencontrez-vous des problèmes éventuels de compilation? -* Voyez-vous une façon de générer des nombres premiers avec votre programme? - -. . . - -## Implémentez-la et postez votre code sur le salon matrix (10 min). - -# Corrigé: enfin pas vraiment mais juste un possible - -\footnotesize - -```C -#include <stdio.h> -#include <stdlib.h> -#include <math.h> -#include <stdbool.h> -int main() { - int nb = 1; - printf("Entrez un nombre: "); - scanf("%d", &nb); - bool premier = true; - for (int div = 2; div <= sqrt(nb); div++) { - if (nb % div == 0) { - premier = false; - break; - } - } - if (premier) { - printf("Le nombre %d est premier\n", nb); - } else { - printf("Le nombre %d n'est pas premier\n", nb); - } - return 0; -} -``` - -# Quelques algorithmes simples - -## Voyons quelques algorithmes supplémentaires - -- Plus petit commun multiple (PPCM) de deux nombres -- Autre algorithme de calcul du PPCM de deux nombres -- Plus grand commun diviseur (PGCD) de deux nombres - -# Le calcul du PPCM (1/5) - -## Définition - -Le plus petit commun multiple (PPCM), `p`, de deux entiers non nuls, `a` et `b`, -est le plus petit entier strictement positif qui soit multiple de ces deux -nombres. - -Exemples: - -```C -PPCM(3, 4) = 12, -PPCM(4, 6) = 12, -PPCM(5, 15) = 15. -``` - -. . . - -## Mathématiquement - -Décomposition en nombres premiers: - -$$ -36 = 2^2\cdot 3^2,\quad 90=2\cdot 5\cdot 3^2, -$$ -On garde tous les premiers à la puissance la plus élevée -$$ -PPCM(36, 90)=2^2\cdot 3^2\cdot 5=180. -$$ - -# Le calcul du PPCM (2/5) - -## Exemple d'algorithme - -```C -PPCM(36, 90): -36 < 90 // 36 + 36 -72 < 90 // 72 + 36 -108 > 90 // 90 + 90 -108 < 180 // 108 + 36 -144 < 180 // 144 + 36 -180 = 180 // The End! -``` - -* 5 additions, 5 assignations, et 6 comparaisons. - -. . . - -## Transcrivez cet exemple en algorithme (groupe de 3), 5min - -. . . - -## et codez-le! - - -# Le calcul du PPCM (3/5) - -## Tentative de correction - -```C -int main() { - int m = 15, n = 12; - int mult_m = m, mult_n = n; - while (mult_m != mult_n) { - if (mult_m > mult_n) { - mult_n += n; - } else { - mult_m += m; - } - } - printf("Le ppcm de %d et %d est %d\n", n, m, mult_m); -} -``` - -. . . - -* Combien d'additions / comparaisons au pire? - -# Le calcul du PPCM (4/5) - -## Réusinage: Comment décrire une fonction qui ferait ce calcul (arguments, sorties)? - -. . . - -En `C` on pourrait la décrire comme - -```C -int ppcm(int a, int b); // La **signature** de cette fonction -``` - -. . . - -## Algorithme - -Par groupe de 3 (5-10min): - -* réfléchissez à un algorithme alternatif donnant le PPCM de deux nombres; -* écrivez l'algorithme en pseudo-code. - -# Le calcul du PPCM (5/5) - -## Indication - -Si un nombre, `p`, est multiple de `a` et de `b` alors il peut s'écrire `p = a -* i = b * j` ou encore `p / a = i` et `p / b = j`. - -. . . - -## Pseudo-code - -```C -int ppcm(int a, int b) { - for (i in [1, b]) { - if a * i est divisible par b { - return a * i - } - } -} -``` - -# Le code du PPCM de 2 nombres (1/2) - -## Implémentez le pseudo-code et postez le code sur matrix (5min). - -. . . - -## Un corrigé possible - - -```C -#include <stdio.h> -#include <stdlib.h> -int main() { - int n = 15, m = 12; - int i = 1; - while (n * i % m != 0) { - i++; - } - printf("Le ppcm de %d et %d est %d\n", n, m, n*i); -} -``` - -# Le code du PPCM de 2 nombres (2/2) - -## Corrigé alternatif - -```C -#include <stdio.h> -#include <stdlib.h> -int main() { - int res = n*m; - for (int i = 2; i <= m; i++) { - if (n * i % m == 0) { - res = n * i; - break; - } - } - printf("Le ppcm de %d et %d est %d\n", n, m, res); -} -``` - - - - diff --git a/slides/cours_20.md b/slides/cours_20.md deleted file mode 100644 index 5fbbc84..0000000 --- a/slides/cours_20.md +++ /dev/null @@ -1,795 +0,0 @@ ---- -title: "Arbres quarternaires, compression et Barnes-Hut" -date: "2023-05-05" ---- - -# Le cours précédent (1/2) - -## Questions - -* Qu'est-ce qu'un arbre quaternaire? - -. . . - -* Un arbre où chaque nœud a soit **4 enfants** soit **aucun**. -* A quoi peut servir un arbre quaternaire? - -. . . - -* Compression - -# Le cours précédent (2/2) - -## Questions - - -* Structure de données d'un arbre quaternaire? - -. . . - -```C -typedef struct _node { - int info; - struct _node *child[4]; -} node; -``` - -. . . - -* Dessin d'un nœud d'arbre quaternaire (avec correspondance `node`)? - -. . . - -```{.mermaid format=pdf width=400 loc=figs/} -graph TD; - id0((" "))-->|"child[0]"| id1(("info")); - id0-->|"child[1]"| id2(("info")); - id0-->|"child[2]"| id3(("info")); - id0-->|"child[3]"| id4(("info")); -``` - -# Transformations avec un arbre quaternaire - -## A faire - -* Symétrie axiale (horizontale/verticale). -* Rotation quart de cercle (gauche/droite). -* Compression. - -# La symétrie verticale - -## Que donne la symétrie verticale de - -``` - SG=0 | SD=1 - 21 | 12 | 4 | 4 - 9 | 7 | 4 | 4 ------------------ - 1 | 1 | 0 | 31 - 1 | 1 | 3 | 27 - IG=2 | ID=3 -``` - -. . . - -``` - SG=0 | SD=1 - 4 | 4 | 12 | 21 - 4 | 4 | 7 | 9 ------------------- - 31 | 0 | 1 | 1 - 27 | 3 | 1 | 1 - IG=2 | ID=3 -``` - -# La symétrie d'axe vertical - -## Comment faire sur une matrice (3min, matrix)? - -. . . - -\footnotesize - -```C -matrice symétrie(matrice) - pour i de 0 à nb_colonnes(matrice) / 2 - pour j de 0 à nb_lignes(matrice) - échanger(matrice[i][j], matrice[nb_colonnes(matrice)-1-i][j]) - retourne matrice -``` - -# La symétrie d'axe vertical - -## Comment faire sur un arbre? - -* Faire un dessin de l'arbre avant/après (5min, matrix) - - ``` - SG=0 | SD=1 SG=0 | SD=1 - 21 | 12 | 4 | 4 4 | 4 | 12 | 21 - 9 | 7 | 4 | 4 4 | 4 | 7 | 9 - ----------------- => ---------------- - 1 | 1 | 0 | 31 31 | 0 | 1 | 1 - 1 | 1 | 3 | 27 27 | 3 | 1 | 1 - IG=2 | ID=3 IG=2 | ID=3 - ``` - -* Écrire le pseudo-code (3min, matrix) - -. . . - -\footnotesize - -```C -arbre symétrie(arbre) - si !est_feuille(arbre) - échanger(arbre.enfant[0], arbre.enfant[1]) - échanger(arbre.enfant[2], arbre.enfant[3]) - pour i de 0 à 3 - symétrie(arbre.enfant[i]) - retourne arbre -``` - -# La symétrie d'axe horizontal - -* Trivial de faire l'axe horizontal (exercice à la maison) - -# Rotation d'un quart de cercle - -## Comment faire sur un arbre? - -* Faire un dessin de l'arbre avant/après (5min, matrix) - - ``` - SG=0 | SD=1 SG=0 | SD=1 - 21 | 12 | 4 | 4 4 | 4 | 31 | 27 - 9 | 7 | 4 | 4 4 | 4 | 0 | 3 - ----------------- => ----------------- - 1 | 1 | 0 | 31 12 | 7 | 1 | 1 - 1 | 1 | 3 | 27 21 | 9 | 1 | 1 - IG=2 | ID=3 IG=2 | ID=3 - - ``` - -* Écrire le pseudo-code (3min, matrix) - -. . . - -```C -rien rotation_gauche(arbre) - si !est_feuille(arbre) - échange_cyclique_gauche(arbre.enfant) - pour i de 0 à 3 - rotation_gauche(arbre.enfant[i]) -``` - -# Rotation d'un quart de cercle - -\footnotesize - -## Comment faire sur un arbre? - -``` - SG=0 | SD=1 SG=0 | SD=1 - 21 | 12 | 4 | 4 4 | 4 | 31 | 27 - 9 | 7 | 4 | 4 4 | 4 | 0 | 3 ------------------ => ----------------- - 1 | 1 | 0 | 31 12 | 7 | 1 | 1 - 1 | 1 | 3 | 27 21 | 9 | 1 | 1 - IG=2 | ID=3 IG=2 | ID=3 -``` - -* Écrire le vrai (5min, matrix) - -. . . - -```C -void rotate(node *qt) { - if (!is_leaf(qt)) { - node *tmp = qt->child[2]; - qt->child[2] = qt->child[0]; - qt->child[0] = qt->child[1]; - qt->child[1] = qt->child[3]; - qt->child[3] = tmp; - for (int i=0;i < 4; i++) { - rotate(qt->child[i]); - } - } -} -``` - -# Compression sans perte (1/5) - -## Idée générale - -* Regrouper les pixels par valeur - -``` - SG=0 | SD=1 SG=0 | SD=1 - 21 | 12 | 4 | 4 21 | 12 | 4 - 9 | 7 | 4 | 4 9 | 7 | ------------------ => ----------------- - 1 | 1 | 0 | 31 1 | 0 | 31 - 1 | 1 | 3 | 27 | 3 | 27 - IG=2 | ID=3 IG=2 | ID=3 -``` - -* Comment faire? - -# Compression sans perte (2/5) - -## Que devient l'arbre suivant? - - - -. . . - -## Arbre compressé - - - -# Compression sans perte (3/5) - -* Si un nœud a tous ses enfants égaux: - * Donner la valeur au nœud, - * Supprimer les enfants. -* Remonter jusqu'à la racine. - -## Écrire le pseudo-code (5min, matrix) - -. . . - -```C -rien compression_sans_pertes(arbre) - si !est_feuille(arbre) - pour i de 0 à 3 - compression_sans_pertes(arbre.enfant[i]) - si derniere_branche(arbre) - valeur, toutes_égales = valeur_enfants(arbre) - si toutes_egales - arbre.info = valeur - detruire_enfants(arbre) -``` - -# Compression sans perte (4/5) - -\footnotesize - -## Écrire le code C (5min, matrix) - -. . . - -```C -void lossless_compression(node *qt) { - if (!is_leaf(qt)) { - for (int i = 0; i < CHILDREN; i++) { - lossless_compression(qt->child[i]); - } - if (is_last_branch(qt)) { - int val = -1; - if (last_value(qt, &val)) { - qt->info = val; - for (int i = 0; i < 4; ++i) { - free(qt->child[i]); - qt->child[i] = NULL; - } - } - } - } -} -``` - -# Compression sans perte (5/5) - -\footnotesize - -```C -bool is_last_branch(node *qt) { - for (int i = 0; i < 4; ++i) { - if (!is_leaf(qt)) { - return false; - } - } - return true; -} -bool last_value(node *qt, int *val) { - int info = qt->child[0]; - for (int i = 1; i < 4; ++i) { - if (info != qt->child[i]) { - return false; - } - } - *val = info; - return true; -} -``` - - -# Compression avec perte (1/5) - -## Idée générale - -* Regrouper les pixels par valeur sous certaines conditions - -``` - SG=0 | SD=1 SG=0 | SD=1 - 21 | 12 | 4 | 3 21 | 12 | 4 - 9 | 7 | 4 | 4 9 | 7 | ------------------ => ------------------ - 1 | 1 | 0 | 31 1 | 0 | 31 - 2 | 1 | 3 | 27 | 3 | 27 - IG=2 | ID=3 IG=2 | ID=3 -``` - -* On enlève si l'écart à la moyenne est "petit"? - -# Compression avec perte (2/5) - -## Que devient l'arbre suivant si l'écart est petit? - - - -. . . - -## Arbre compressé - - - -# Compression avec perte (3/5) - -## Comment mesurer l'écart à la moyenne? - -. . . - -* Avec l'écart-type - -\begin{equation*} -\mu = \frac{1}{4}\sum_{i=0}^{3} p[i],\quad \sigma = \sqrt{\frac{1}{4}\sum_{i=0}^3 (\mu-p[i]) -^2} = \sqrt{\frac{1}{4}\left(\sum_{i=0}^3p[i]^2\right)-\mu^2} -\end{equation*} - -## Que devient l'algorithme? - -. . . - -* Si $\sigma<\theta$, $\theta$ est la **tolérance**: - * Remplacer la valeur du pixel par la moyenne des enfants. - * Remonter les valeurs dans l'arbre. - -## Quelle influence de la valeur de $\theta$ sur la compression? - -. . . - -* Plus $\theta$ est grand, plus l'image sera compressée. - -# Compression avec perte (4/5) - -## Que devient l'arbre avec $\theta=0.5$? - - - -. . . - - - -# Compression avec perte (5/5) - -## Modifications sur la structure de données? - -. . . - -* On stocke la moyenne, et la moyenne des carrés. - -```C -struct noeud - flottant moyenne, moyenne_carre - node enfants[4] -``` - -* Comment on calcule `moyenne` et `moyenne_carre` sur chaque nœud (pseudo-code)? - -# Calcul de la moyenne - -## Pseudo-code (5min, matrix) - -. . . - -```C -rien moyenne(arbre) { - si !est_feuille(arbre) - pour enfant dans arbre.enfants - moyenne(enfant) - pour enfant dans arbre.enfants - arbre.moyenne += enfant.moyenne - arbre.moyenne_carre += enfant.moyenne_carre - arbre.moyenne /= 4 - arbre.moyenne_carre /= 4 -``` - -# La compression avec pertes - -\footnotesize - -## Pseudo-code (5min, matrix) - -. . . - -```C -rien compression_avec_pertes(arbre, theta) - si !est_feuille(arbre) - pour i de 0 à 3 - compression_avec_pertes(arbre.enfant[i]) - si derniere_branche(arbre) - si racine(arbre.moyenne_carre - arbre.moyenne^2) < theta - detruire_enfants(arbre) -``` - -## Le code en entier - -```C -arbre = matrice_à _arbre(matrice) -moyenne(arbre) -compression_sans_pertes(arbre) -``` - -# La dynamique des corps célestes - -## Slides très fortement inspirés du cours de J. Latt, Unige - -## Simulation du problème à $N$-corps - -* Prédiction du mouvement d'un grand nombre de corps célestes. -* Modélisation: - * On se limite aux étoiles; - * Chaque étoile est caractérisée par un point (coordonnées) et une masse; - * On simule en deux dimensions. - * Interactions uniquement par les lois de la gravitation Newtonienne (oui-oui c'est de la **physique**!). - - -# Les équations du mouvement - -## Mouvement de la $i$-ème étoile - -* Algorithme de Verlet ($t_{n+1}=t_n+\delta t$) - - $$ - \vec x_i(t_{n+1})= 2\vec x_i(t_n)-\vec x_i(t_{n-1})+\vec a_i(t_n)\delta t^2. - $$ - -## Force de gravitation - -* $\vec a_i(t_n)=\vec F_i/m_i$. -* Sur l'étoile $i$, la force résultante est donnée par - - $$ - \vec F_i=\sum_{j=1,j\neq i}^N \vec F_{ij}. - $$ - avec - $$ - \vec F_{ij}=\frac{G m_i m_j(\vec x_j-\vec x_i)}{||\vec x_j-\vec x_i||^3}. - $$ - -# Algorithme du problème à $n$-corps - -## Pseudo-code: structure de données - -```C -struct étoile - flottant m - vec x, x_precedent, f -``` - -## Pseudo-code: itération temporelle - -```C -rien iteration_temporelle(étoiles, dt) - pour étoile_une dans étoiles - étoile_une.f = 0 - pour étoile_deux dans étoiles - si (étoile_un != étoile_deux) - étoile_une.f += - force(étoile_une, étoile_deux) - pour étoile dans étoiles - étoile.x, étoile.x_precedent = - verlet(étoile.x, étoile.x_precedent, - étoile.f / étoile.m, dt) -``` - -# Algorithme du problème à $n$-corps - -## Complexité - -* Complexité de chacune des parties? - -. . . - -* $\mathcal{O}(N^2)$, $\mathcal{O}(N)$. - -## En temps CPU pour **une itération** - -\footnotesize - -* Si temps pour $N=1$ on calcule en $1\mu s$: - -+--------+-------+-------+-----------+ -| N | N^2 | t [s] | t [réel] | -+--------+-------+-------+-----------+ -| 10 | 10^2 | 1e-4 | | -+--------+-------+-------+-----------+ -| 10^4 | 10^8 | 1e+2 | ~1min | -+--------+-------+-------+-----------+ -| 10^6 | 10^12 | 1e+6 | ~11j | -+--------+-------+-------+-----------+ -| 10^9 | 10^18 | 1e+12 | ~30k ans | -+--------+-------+-------+-----------+ -| 10^11 | 10^22 | 1e+16 | ~300M ans | -+--------+-------+-------+-----------+ - -* Typiquement il y a des milliers-millions d'itérations. -* Il y a $10^{11}$ étoiles dans la galaxie. -* Houston we have a problem. - -# Question - -## Comment faire mieux, des idées? - -. . . - -* Si un groupe d'étoiles est suffisamment loin, on le modélise comme un corps unique situé en son centre de masse. -* Exemple: Si on simule plusieurs galaxies, on considère chaque galaxie comme un corps unique! -* Un arbre quaternaire est une structure parfaite pour regrouper les étoiles. - -# Le cas à 10 corps - -::: columns - -:::: {.column width=50%} - -## Illustration: le cas à 10 corps - -{width=60%} - -:::: - -:::: {.column width=50%} - -## Problématique - -* On veut calculer la force sur $1$. - -:::: - -::: - -. . . - - -::: columns - -:::: {.column width=50%} - -## Illustration: le cas à 10 corps - -{width=60%} - - -:::: - -:::: {.column width=50%} - -## Résultat - -* Calcul et somme des forces venant des $9$ autre corps. - -:::: - -::: - -# Le cas à 10 corps - -::: columns - -:::: {.column width=50%} - -## Réduction d'un groupe à un seul corps - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Idée - -* On accélère le calcul en traitant un groupe comme un seul corps. -* Fonctionne uniquement si le groupe est assez loin. -* Autrement l'approximation est trop grossière. - -:::: - -::: - -# Solution: l'arbre quaternaire - -## Corps célestes - arbre - - - -* On omet les nœuds vides pour éviter la surcharge. -* La numérotation est: - * 0: ID - * 1: SD - * 2: IG - * 3: SG - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 1 - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant ID. -* La feuille est vide, on insère. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 2 - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant SD. -* La feuille est vide, on insère. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (1/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant SD. -* La feuille est prise par 2. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (2/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 2 - -{width=100%} - -* On crée un nouveau nœud. -* Deux corps dans le nœud ID. -* On crée un nouveau nœud. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (3/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 3 - -{width=100%} - -* 2 va dans ID. -* 3 va dans SG. -* C'est des feuilles vides, tout va bien. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Que fait-on avec les nœuds intérieurs? - -* On les utilise pour: - * stocker la masse totale; - * stocker le centre de masse. - -\begin{align} -m&=m_2+m_3,\\ -\vec x &= \frac{m_2\vec x_2+m_3\vec x_3}{m}. -\end{align} - -## Chaque feuille contient **une étoile** - -:::: - -:::: {.column width=50%} - -## Arbre - -{width=100%} - -:::: - -::: - -# Résumé - -* Insertion du corps `c` dans le nœud `n` en partant de la racine. -* Si le nœud `n` - * ne contient pas de corps, on y dépose `c`, - * est interne, on met à jour masse et centre de masse. `c` est inséré récursivement dans le bon quadrant. - * est externe, on subdivise `n`, on met à jour la masse et centre de masse, on insère récursivement les deux nœuds dans les quadrants appropriés. - -## Remarque - -* Il faut stocker les coordonnées des quadrants. -* Un nœud a un comportement différent s'il est interne ou externe. - diff --git a/slides/cours_21.md b/slides/cours_21.md deleted file mode 100644 index 0b06c90..0000000 --- a/slides/cours_21.md +++ /dev/null @@ -1,731 +0,0 @@ ---- -title: "Barnes-Hut et B-arbres" -date: "2023-05-12" ---- - -# Le cours précédent - -## A quoi sert l'algorithme de Barnes-Hut? - -. . . - -* A accélérer la résolution du problème à $n$-corps avec la gravitation, -* si on peut vivre avec une réduction de précision. - -## Sur quelle structure de données se base l'algorithme? - -* L'arbre quaternaire. - -. . . - -## Quelle est l'idée générale? - -. . . - -* Si un groupe d'étoiles est suffisamment loin, on le modélise comme un corps unique situé en son centre de masse. -* Exemple: Si on simule plusieurs galaxies, on considère chaque galaxie comme un corps unique! -* Un arbre quaternaire est une structure parfaite pour regrouper les étoiles. - - -# Le cas à 10 corps - -::: columns - -:::: {.column width=50%} - -## Illustration: le cas à 10 corps - -{width=60%} - -:::: - -:::: {.column width=50%} - -## Problématique - -* On veut calculer la force sur $1$. - -:::: - -::: - -. . . - - -::: columns - -:::: {.column width=50%} - -## Illustration: le cas à 10 corps - -{width=60%} - - -:::: - -:::: {.column width=50%} - -## Résultat - -* Calcul et somme des forces venant des $9$ autre corps. - -:::: - -::: - -# Le cas à 10 corps - -::: columns - -:::: {.column width=50%} - -## Réduction d'un groupe à un seul corps - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Idée - -* On accélère le calcul en traitant un groupe comme un seul corps. -* Fonctionne uniquement si le groupe est assez loin. -* Autrement l'approximation est trop grossière. - -:::: - -::: - -# Solution: l'arbre quaternaire - -## Corps célestes - arbre - - - -* On omet les nœuds vides pour éviter la surcharge. -* La numérotation est: - * 0: ID - * 1: SD - * 2: IG - * 3: SG - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 1 - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant ID. -* La feuille est vide, on insère. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 2 - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant SD. -* La feuille est vide, on insère. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (1/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 1 - -{width=100%} - -* Quadrant SD. -* La feuille est prise par 2. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (2/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 2 - -{width=100%} - -* On crée un nouveau nœud. -* Deux corps dans le nœud ID. -* On crée un nouveau nœud. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Insertion corps 3 (3/N) - -{width=100%} - -:::: - -:::: {.column width=50%} - -## Arbre, niveau 3 - -{width=100%} - -* 2 va dans ID. -* 3 va dans SG. -* C'est des feuilles vides, tout va bien. - -:::: - -::: - -# Exemple d'insertion - -::: columns - -:::: {.column width=50%} - -## Que fait-on avec les nœuds intérieurs? - -* On les utilise pour: - * stocker la masse totale; - * stocker le centre de masse. - -\begin{align} -m&=m_2+m_3,\\ -\vec x &= \frac{m_2\vec x_2+m_3\vec x_3}{m}. -\end{align} - -## Chaque feuille contient **une étoile** - -:::: - -:::: {.column width=50%} - -## Arbre - -{width=100%} - -:::: - -::: - -# Résumé - -* Insertion du corps `c` dans le nœud `n` en partant de la racine. -* Si le nœud `n` - * ne contient pas de corps, on y dépose `c`, - * est interne, on met à jour masse et centre de masse. `c` est inséré récursivement dans le bon quadrant. - * est externe, on subdivise `n`, on met à jour la masse et centre de masse, on insère récursivement les deux nœuds dans les quadrants appropriés. - -## Remarque - -* Il faut stocker les coordonnées des quadrants. -* Un nœud a un comportement différent s'il est interne ou externe. - -# Algorithme d'insertion - -## Structure de données - -```C -struct node - etoile e // externe: pour stocker - etoile sup_etoile // interne: pour stocker m, x - quadrant q // coordonnées du quadrant - node enfants[4] -``` - -## Remarque: - -* On fait une simplification "moche": `sup_etoile` pourrait juste avoir une masse et une position. - -# Algorithme d'insertion - -\footnotesize - -## Algorithme d'insertion, pseudo-code (15min, matrix) - -. . . - -```C -rien insertion_etoile(arbre, e) - si (!est_vide(arbre) && dans_le_quadrant(arbre.q, e.x)) { - si (est_feuille(arbre)) - si (!contient_etoile(arbre)) - arbre.e = e - sinon - // on crée enfants et arbre.sup_etoile est initialisée - subdivision_arbre(arbre, e) - pour enfant dans arbre.enfants - insertion_etoile(enfant, arbre.e) - pour enfant dans arbre.enfants - insertion_etoile(enfant, e) - destruction(arbre.e) - sinon - maj_masse_cdm(arbre.sup_etoile, e) - pour enfant dans arbre.enfants - insertion_etoile(enfant, e) -``` - -# Utilisation de l'arbre - -* L'arbre est rempli: comment on calcule la force sur le corps 1? -* Parcours de l'arbre: - * si la distance entre 1 et le centre de masse est suffisante, on utilise la masse totale et centre de masse pour calculer la force. - * sinon, on continue le parcours - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* Le cadrant ID ne contient que `1`, rien à faire. - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* Le cadrant SG ne contient `5` corps. - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* La distance entre `1` et le centre de masse de SG est `d`. - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* La distance entre `1` et le centre de masse de SG est `d`. -* Est-ce que `d` est assez grand? -* On va comparer avec la distance `d` avec la taille du quadrant `s`. - -# Critère $\theta$ - -* On compare $d=||\vec x_1-\vec x_{cm}||$ avec $s$ la taille du quadrant. -* Le domain est assez éloigné si - - $$ - \frac{s}{d}<\theta, - $$ -* $\theta$ est la valeur de seuil. -* Une valeur typique est $\theta=0.5$, donc la condition devient - - $$ - d>2s. - $$ - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* Ici $d<2s$, domaine rejeté. -* ON descend dans l'arbre. - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* `s` est plus petit, mais.... -* Cela ne suffit pas $d<2s$, domaine rejeté. - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* Les nœuds sont des feuilles, on calcule la force. -* On ajoute la force qu'ils exercent sur `1`. - -# Algorithme pour le calcul de la force - -Pour calculer la force sur un corps `c`, on parcourt l'arbre en commençant par la racine: - -* Si le nœud `n` est une feuille et n'est pas `c`, on ajoute la force dûe à `n` sur `c`; -* Sinon si $s/d<\theta$, on traite `n` comme une feuille et on ajoute la force dûe à `n` sur `c`; -* Sinon on continue sur les enfants récursivement. - - -## Continuons notre exemple précédent! - -# Calcul de la force - -## Calcul de la force sur `1` - - - -* Il y a deux corps dans le quadrant vert. -* Quel est le critère pour remplacer les étoiles par leur centre de masse? - -. . . - -* Et oui! $d>2s$ on peut remplacer les étoiles par leur centre de masse! - -# Algorithme du calcul de force - -## Écrire le pseudo-code-code du calcul de la force - -\footnotesize - -```C -rien maj_force_sur_etoile(arbre, e, theta) - si est_vide(arbre) - retourne - - si est_feuille(arbre) && contient_etoile(arbre) && dans_le_quadrant(arbre.q, e.x) - maj_force(e, arbre.e) - sinon si noeud_assez_loin(arbre, e, theta) - maj_force(e, arbre.sup_etoile) - sinon - pour enfant dans enfants - maj_force_sur_etoile(enfant, e, theta) -``` - -# Les B-arbres - -## Problématique - -* Grands jeux de données (en 1970). -* Stockage dans un arbre, mais l'arbre tiens pas en mémoire. -* Regrouper les sous-arbres en **pages** qui tiennent en mémoire. - -## Exemple - -* 100 nœuds par page et l'arbre comporte $10^6$ nœuds: - * Recherche B-arbre: $\log_{100}(10^6)=3$; - * Recherche ABR: $\log_2(10^6)=20$. -* Si on doit lire depuis le disque: $10\mathrm{ms}$ par recherche+lecture: - * $30\mathrm{ms}$ (lecture beaucoup plus rapide que recherche) vs $200\mathrm{ms}=0.2\mathrm{s}$. - -## Remarques - -* On sait pas ce que veut dire `B`: Bayer, Boeing, Balanced? -* Variante plus récente B+-arbres. - -# Les B-arbres - -## Illustration, arbre divisé en pages de 3 nœuds - - - -. . . - -## Utilisation - -* Bases de données (souvent très grandes donc sur le disque); -* Système de fichier. - -# Les B-arbres - -## Avantages - -* Arbres moins profonds; -* Diminue les opération de rééquilibrage; -* Complexité toujours en $\log(N)$; - -. . . - -## Définition: B-arbre d'ordre $n$ - -* Chaque page d'un arbre contient au plus $2\cdot n$ *clés*; -* Chaque page (excepté la racine) contient au moins $n$ clés; -* Chaque page qui contient $m$ clés contient soit: - * $0$ descendants; - * $m+1$ descendants. -* Toutes les pages terminales apparaissent au même niveau. - -# Les B-arbres - -## Est-ce un B-arbre? - - - -. . . - -## Oui! - -* Dans chaque nœud les clés sont **triées**. -* Chaque page contient au plus $n$ nœuds: check; -* Chaque nœud avec $m$ clés a $m+1$ descendants; -* Toutes les feuilles apparaissent au même niveau. - -# Les B-arbres - -## Exemple de recherche: trouver `32` - - - -. . . - -* Si `n` plus petit que la 1e clé ou plus grand que la dernière descendre. -* Sinon parcourir (par bissection ou séquentiellement) jusqu'à trouver ou descendre entre 2 éléments. - -# Les B-arbres - -## La recherche de la clé `C` algorithme - -0. En partant de la racine. -1. Si on est dans une feuille: - * Si la `C` est dans une page, retourner la page; - * Sinon c'est perdu. -2. Sinon: - * Tant que `C > page` passer à la page suivante - * Descendre - -# Les B-arbres - -## Disclaimer - -* Inspiration de <https://en.wikipedia.org/wiki/B-tree> - -## Exemples d'insertion: `1` - - - -. . . - -* L'arbre est vide, on insère juste dans la première page. - -# Les B-arbres - -## Exemples d'insertion: `2` - - - -. . . - -* La première page est pas pleine, on insère dans l'ordre (après 1). - -# Les B-arbres - -## Exemples d'insertion: `3` - -{width=50%} - -* Comment on insère (1min de réflexion avant de donner une réponse!)? - -# Les B-arbres - -## Exemples d'insertion: `3` - -{width=50%} - -. . . - -* La page est pleine, on crée deux enfants. -* On choisit, `2`, la médiane de `1, 2, 3` et il est inséré à la racine. -* `1` descend à gauche, `3` descend à droite. - -# Les B-arbres - -## Exemples d'insertion: `4` - -{width=50%} - -* Comment on insère (1min de réflexion avant de donner une réponse!)? - -# Les B-arbres - -## Exemples d'insertion: `4` - -{width=50%} - -. . . - -* On pourrait insérer à droite de `2`, mais... ça ferait 2 parents pour 2 enfants (mais `m` parents => `m+1` enfants ou `0`); -* On descend à droite (`4 > 2`); -* On insère à droite de `3`. - -# Les B-arbres - -## Exemples d'insertion: `5` - -{width=50%} - -* Comment on insère (1min de réflexion avant de donner une réponse!)? - -# Les B-arbres - -## Exemples d'insertion: `5` - - - -. . . - -* On descend à droite (on peut pas insérer à la racine comme pour `4`); -* On dépasse la capacité de l'enfant droite; -* `4`, médiane de `3, 4, 5`, remonte à la racine; -* On crée un nouveau nœud à droite de `4`; -* La règle `m => m+1` est ok. - -# Les B-arbres - -## Exemples d'insertion: `6` - -{width=50%} - -* Comment on insère (1min de réflexion avant de donner une réponse!)? - -# Les B-arbres - -## Exemples d'insertion: `6` - - - -. . . - -* `6 > 4` on descend à droite; -* `6 > 5` et on a à la place à droite, on insère. - -# Les B-arbres - -## Exemples d'insertion: `7` - -{width=50%} - -* Comment on insère (1min de réflexion avant de donner une réponse!)? - -# Les B-arbres - -## Exemples d'insertion: `7` - -{width=50%} - -. . . - -* `7 > 4` on descend à droite; -* `7 > 6` mais on a dépassé la capacité; -* `6` est la médiane de `5, 6, 7`, remonte à la racine; -* `5` reste à gauche, `7` à droite, mais `6` fait dépasser la capacité de la racine; -* `4` est la médiane de `2, 4, 6`, `4` remonte, `2` reste à gauche, `6` à droite. - -# Les B-arbres - -## L'algorithme d'insertion - -0. Rechercher la feuille (la page a aucun enfant) où insérer; -1. Si la page n'est pas pleine insérer dans l'ordre croissant. -2. Si la page est pleine, on sépare la page en son milieu : - 1. On trouve la médiane, `M`, de la page; - 2. On met les éléments `< M` dans la page de gauche de `M` et les `> M` dans la page de droite de `M`; - 3. `M` est insérée récursivement dans la page parent. - -# Les B-arbres - -## Exercice: insérer `22, 45, 50` dans l'arbre d'ordre 2 (3min matrix) - - - -. . . - - - - -# Les B-arbres - -## Exercice: insérer `5` dans l'arbre d'ordre 2 (3min matrix) - - - -. . . - - - -# Les B-arbres - -## Exercice: insérer `32, 55, 60` dans l'arbre d'ordre 2 (3min matrix) - - - -. . . - - - -# Les B-arbres - -## Exercice: insérer `41` dans l'arbre d'ordre 2 (3min matrix) - - - -. . . - - - -# Les B-arbres - -## Exercice (matrix, 15min) - -* Insérer 20, 40, 10, 30, 15, 35, 7, 26, 18, 22, 5, 42, 13, 46, 27, 8, 32, 38, 24, 45, 25, 2, 14, 28, 32, 41, -* Dans un B-arbre d'ordre 2. - diff --git a/slides/cours_22.md b/slides/cours_22.md deleted file mode 100644 index 3940c6d..0000000 --- a/slides/cours_22.md +++ /dev/null @@ -1,351 +0,0 @@ ---- -title: "B-arbres" -date: "2023-05-19" ---- - -# Rappel: Les B-arbres - -## Pourquoi utiliser un B-arbre? - -. . . - -## À quoi ressemble un B-arbre? - -. . . - -## Qu'est-ce qu'un B-arbre d'ordre $n$ - -* Chaque page d'un arbre contient au plus $2\cdot n$ *clés*; -* Chaque page (excepté la racine) contient au moins $n$ clés; -* Chaque page qui contient $m$ clés contient soit: - * $0$ descendants; - * $m+1$ descendants. -* Toutes les pages terminales apparaissent au même niveau. - - -# Rappel: Les B-arbres - -## Quelques propriétés - -* Dans chaque nœud les clés sont **triées**. -* Chaque page contient au plus $n$ nœuds; -* Chaque nœud avec $m$ clés a $m+1$ descendants; -* Toutes les feuilles apparaissent au même niveau. - -# Les B-arbres - -\footnotesize - -## Structure de données - -* Chaque page a une contrainte de remplissage, par rapport à l'ordre de l'arbre; -* Un nœud (page) est composé d'un tableau de clés/pointeurs vers les enfants; - -``` -P_0 | K_1 | P_1 | K_2 | .. | P_i | K_{i+1} | .. | P_{m-1} | K_m | P_m -``` - -* `P_0`, ..., `P_m` pointeurs vers enfants; -* `K_1`, ..., `K_m` les clés. -* Il y a `m+1` pointeurs mais `m` clés. -* Comment faire pour gérer l'insertion? - -# Les B-arbres - -## Faire un dessin de la structure de données (3min matrix)? - -. . . - - - -1. On veut un tableau de `P_i, K_i => struct`; -2. `K_0` va être en "trop"; -3. Pour simplifier l'insertion dans une page, on ajoute un élément de plus. - -# Les B-arbres - -## L'insertion cas nœud pas plein, insertion `4`? - -{width=50%} - -. . . - -## Solution - -{width=50%} - -# Les B-arbres - -## L'insertion cas nœud pas plein, insertion `N` - -* On décale les éléments plus grand que `N`; -* On insère `N` dans la place "vide"; -* Si la page n'est pas pleine, on a terminé. - -# Les B-arbres - -## L'insertion cas nœud plein, insertion `2`? - -{width=50%} - -. . . - -## Solution - -{width=50%} - -# Les B-arbres - -## L'insertion cas nœud plein, promotion `3`? - -{width=50%} - -. . . - -## Solution - - - -# Les B-arbres - -## L'insertion cas nœud plein, insertion `N` - -* On décale les éléments plus grand que `N`; -* On insère `N` dans la place "vide"; -* Si la page est pleine: - * On trouve la valeur médiane `M` de la page (quel indice?); - * On crée une nouvelle page de droite; - * On copie les valeur à droite de `M` dans la nouvelle page; - * On promeut `M` dans la page du dessus; - * On connecte le pointeur de gauche de `M` et de droite de `M` avec l'ancienne et la nouvelle page respectivement. - -# Les B-arbres - -## Pseudo-code structure de données (3min, matrix)? - -. . . - -```C -struct page - entier ordre, nb - element tab[2*ordre + 2] -``` - -```C -struct element - entier clé - page pg -``` - -# Les B-arbres - -\footnotesize - -## Les fonctions utilitaires (5min matrix) - -```C -booléen est_feuille(page) // la page est elle une feuille? -entier position(page, valeur) // à quelle indice on insère? -booléen est_dans_page(page, valeur) // la valeur est dans la page -``` - -. . . - -```C -booléen est_feuille(page) - retourne (page.tab[0].pg == vide) - -entier position(page, valeur) - i = 0 - tant que i < page.nb && val >= page.tab[i+1].clef - i += 1 - retourne i - -booléen est_dans_page(page, valeur) - i = position(page, valeur) - retourne (page.nb > 0 && page.tab[i].val == valeur) -``` - -# Les B-arbres - -\footnotesize - -## Les fonctions utilitaires (5min matrix) - -```C -page nouvelle_page(ordre) // créer une page -rien liberer_memoire(page) // libérer tout un arbre! -``` -. . . - -```C -page nouvelle_page(ordre) - page = allouer(page) - page.ordre = ordre - page.nb = 0 - page.tab = allouer(2*ordre+2) - retourner page - -rien liberer_memoire(page) - si est_feuille(page) - liberer(page.tab) - liberer(page) - sinon - pour fille dans page.tab - liberer_memoire(fille) - liberer(page.tab) - liberer(page) -``` - -# Les B-arbres - -## Les fonctions (5min matrix) - -```C -page recherche(page, valeur) // retourner la page contenant - // la valeur ou vide -``` - -. . . - -```C -page recherche(page, valeur) - si est_dans_page(page, valeur) - retourne page - sinon si est_feuille(page) - retourne vide - sinon - recherche(page.tab[position(page, valeur) - 1], valeur) -``` - -# Les B-arbres - -## Les fonctions - -```C -page inserer_valeur(page, valeur) // insérer une valeur -``` - -. . . - -```C -page inserer_valeur(page, valeur) - element = nouvel_element(valeur) - // ici élément est modifié pour savoir - // s'il faut le remonter - inserer_element(page, element) - si element.page != vide && page.nb > 2*page.ordre - // si on atteint le sommet! - page = ajouter_niveau(page, element) - retourne page -``` - -# Les B-arbres - -## Les fonctions - -```C -rien inserer_element(page, element) // insérer un element - // et voir s'il remonte -``` - -. . . - -```C -rien inserer_element(page, element) - si est_feuille(page) - placer(page, element) - sinon - sous_page = page.tab[position(page, element.clé) - 1].page - inserer_element(sous_page, element) - // un element a été promu - si element.page != vide - placer(page, element) -``` - -# Les B-arbres - -## Les fonctions (5min matrix) - -```C -rien placer(page, element) // inserer un élément -``` - -. . . - -```C -rien placer(page, element) - pos = position(page, element.clé) - pour i de 2*page.ordre à pos+1 - page.tab[i+1] = page.tab[i] - page.tab[pos+1] = element - page.nb += 1 - si page.nb > 2*page.ordre - scinder(page, element) -``` - -# Les B-arbres - -## Les fonctions (5min matrix) - -```C -rien scinder(page, element) // casser une page et remonter -``` - -. . . - -```C -rien scinder(page, element) - nouvelle_page = nouvelle_page(page.ordre) - nouvelle_page.nb = page.ordre - pour i de 0 à ordre inclu - nouvelle_page.tab[i] = page.tab[i+ordre+1] - element.clé = page.tab[ordre+1].clé - element.page = nouvelle_page -``` - -# Les B-arbres - -## Les fonctions (5min matrix) - -```C -page ajouter_niveau(page, element) // si on remonte à la - // racine, on doit créer - // une nouvelle racine -``` - -. . . - -```C -page ajouter_niveau(page, element) - tmp = nouvelle_page(page.ordre) - tmp.tab[0].page = page - tmp.tab[1].clé = element.clé - tmp.tab[1].page = element.page - retourne tmp -``` - - - - - -<!-- # Les B-arbres --> - -<!-- ## Structure de données en C (3min, matrix) --> - -<!-- . . . --> - -<!-- ```C --> -<!-- typedef struct _page { --> -<!-- int order, nb; --> -<!-- struct _element *tab; --> -<!-- } page; --> -<!-- ``` --> - -<!-- ```C --> -<!-- typedef struct element { --> -<!-- int key; --> -<!-- struct _page *pg; --> -<!-- } element; --> -<!-- ``` --> - diff --git a/slides/cours_23.md b/slides/cours_23.md deleted file mode 100644 index 60f01e4..0000000 --- a/slides/cours_23.md +++ /dev/null @@ -1,1013 +0,0 @@ ---- -title: "B-arbres et Graphes" -date: "2023-05-24" -patat: - eval: - tai: - command: fish - fragment: false - replace: true - ccc: - command: fish - fragment: false - replace: true - images: - backend: auto ---- - - -# Les B-arbres: suppression - -## Cas simplissime - -{width=80%} - -. . . - -{width=80%} - -# Les B-arbres: suppression - -\footnotesize - -## Cas simple - - -{width=60%} - -. . . - -* On retire 27, mais.... - * Chaque page doit avoir au moins 2 éléments. - * On doit déplacer des éléments dans une autre feuille! Mais comment? - -. . . - -{width=60%} - -# Les B-arbres: suppression - -## Cas moins simple - -{width=60%} - -. . . - -* Un élément à droite, comment on fait? - * Remonter `7`, serait ok si racine, mais... c'est pas forcément. - * On redistribue les feuilles. - -. . . - -{width=60%} - -# Les B-arbres: suppression - -\footnotesize - -## Cas ultra moins simple - -{width=60%} - -. . . - -* `7` seul: - * Fusionner les feuilles et redistribuer, comment? - -. . . - -{width=60%} - -# Les B-arbres: suppression - -## Cas ultra moins simple - -{width=60%} - -. . . - -* `8` est seul, c'est plus un B-arbre : - * Fusionner le niveau 2 et redistribuer, comment? - -. . . - -{width=40%} - -. . . - -* La profondeur a diminué de 1. - -# Les B-arbres: suppression - -## Algorithme pour les feuilles! - -* Si la clé est supprimée d'une feuille: - * Si on a toujours `n` (ordre de l'arbre) clés dans la feuille on décale simplement les clés. - * Sinon on combine (récursivement) avec le nœud voisin et on descend la clé médiane. - -# Les B-arbres: suppression - -## Cas non-feuille! - -{width=60%} - -. . . - -* On sait comment effacer une valeur d'une feuille, donc? - -. . . - -{width=60%} - -* Ensuite? - -# Les B-arbres: suppression - -## Cas non-feuille! - -{width=60%} - -. . . - -* On sait comment effacer une valeur d'une feuille! - -. . . - -{width=60%} - -# Les B-arbres: suppression - -## Algorithme pour les non-feuilles! - -* Si la clé est supprimée d'une page qui n'est pas une feuille: - * On échange la valeur avec la valeur de droite de la page de gauche - * On supprime comme pour une feuille! - -## Et maintenant des exercices par millions! - -# Les graphes! Historique - -**Un mini-peu d'histoire...** - -## L. Euler et les 7 ponts de Koenigsberg: - -* Existe-t-il une promenade sympa, passant **une seule fois** par les 7 ponts et revenant au point de départ? - -{width=50%} - -. . . - -* Réponse: ben non! - -# Utilisation quotidienne - -## Réseau social - -{width=40%} - -* Chaque sommet est un individu. -* Chaque trait une relation d'amitié. -* Miam, Miam, Facebook. - -# Utilisation quotidienne - -## Moteurs de recherche - -{width=40%} - -* Sommet est un site. -* Liens sortants; -* Liens entrants; -* Notion d'importance d'un site: combien de liens entrants, pondérés par l'importance du site. -* Miam, Miam, Google (PageRank). - -# Introduction - -## Définition, plus ou moins - -* Un graphe est un ensemble de sommets, reliés par des lignes ou des flèches. - - - -* Des sommets (numérotés 1 à 6); -* Connectés ou pas par des traits ou des flèches! - -# Généralités - -## Définitions - -* Un **graphe** $G(V, E)$ est constitué - * $V$: un ensemble de sommets; - * $E$: un ensemble d'arêtes. -* Une **arête** relie une **paire** de sommets de $V$. - -## Remarques - -* Il y a **au plus** une arête $E$ par paire de sommets de $V$. -* La **complexité** d'un algorithme dans un graphe se mesure en terme de $|E|$ et $|V|$, le nombre d'éléments de $E$ et $V$ respectivement. - -# Généralités - -## Notations - -* Une arête d'un graphe **non-orienté** est représentée par une paire **non-ordonnée** $(u,v)=(v,u)$, avec $u,v\in V$. -* Les arêtes ne sont pas orientées dans un graphe non-orienté (elles sont bi-directionnelles, peuvent être parcourues dans n'importe quel ordre). - -## Exemple - - -::: columns - -:::: column - - - - -:::: - -:::: column - -## Que valent $V$, $|V|$, $E$, et $|E|$? - -. . . - -\begin{align*} -V&=\{1, 2, 3, 4\},\\ -|V|&=4,\\ -E&=\{(1,2),(2,3),(2,4),(4,1)\},\\ -|E|&=4. -\end{align*} - -:::: - -::: - -# Généralités - -## Notations - -* Une arête d'un graphe **orienté** est représentée par une paire **ordonnée** $(u,v)\neq(v,u)$, avec $u,v\in V$. -* Les arêtes sont orientées dans un graphe orienté (étonnant non?). - -## Exemple - - -::: columns - -:::: column - - - - -:::: - -:::: column - -## Que valent $V$, $|V|$, $E$, et $|E|$? - -. . . - -\begin{align*} -V&=\{1, 2, 3, 4\},\\ -|V|&=4,\\ -E&=\{(1,2),(2,3),(2,4),(4,1),(4,2)\},\\ -|E|&=5. -\end{align*} - -:::: - -::: - -# Généralités - -## Définition - -* Le somme $v$ est **adjacent** au sommet $u$, si et seulement si $(u,v)\in E$; -* Si un graphe non-orienté contient une arête $(u,v)$, $v$ est adjacent à $u$ et $u$ et adjacent à $v$. - -## Exemple - -::: columns - -:::: column - -{width=80%} - -:::: - -:::: column - -{width=80%} - -:::: - -::: - -# Généralités - -## Définition - -* Un **graphe pondéré** ou **valué** est un graphe dont chaque arête a un - poids associé, habituellement donné par une fonction de pondération $w:E\rightarrow\mathbb{R}$. - -## Exemples - -{width=80%} - - -# Généralités - -## Définition - -* Dans un graphe $G(V,E)$, une **chaîne** reliant un sommet $u$ à un sommet $v$ est une suite d'arêtes entre les sommets, $w_0$, $w_1$, ..., $w_k$, telles que -$$ -(w_i, w_{i+1})\in E,\quad u=w_0,\quad v=w_k,\quad \mbox{pour }0\leq i< k, -$$ -avec $k$ la longueur de la chaîne (le nombre d'arêtes du chemin). - -## Exemples - -{width=80%} - -# Généralités - -## Définition - -* Une **chaîne élémentaire** est une chaîne dont tous les sommets sont distincts, sauf les extrémités qui peuvent être égales - -## Exemples - -{width=80%} - -# Généralités - -## Définition - -* Une **boucle** est une arête $(v,v)$ d'un sommet vers lui-même. - -## Exemples - -{width=40%} - -# Généralités - -## Définition - -* Un graphe non-orienté est dit **connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts. - -## Exemples - -\ - -::: columns - -:::: column - -{width=80%} - -:::: - -:::: column -{width=60%} - -:::: - -::: - -# Généralités - -## Définition - -* Un graphe orienté est dit **fortement connexe**, s'il existe un chemin reliant n'importe quelle paire de sommets distincts. - -## Exemples - -\ - -::: columns - -:::: column - -{width=60%} - -:::: - -:::: column - -{width=100%} - -:::: - -::: - -# Généralités - -## Définition - -* Un **cycle** dans un graphe *non-orienté* est une chaîne de longueur $\geq 3$ telle que le 1er sommet de la chaîne est le même que le dernier, et dont les arêtes sont distinctes. -* Pour un graphe *orienté* on parle de **circuit**. -* Un graphe sans cycles est dit **acyclique**. - -## Exemples - -{width=100%} - -# Question de la mort - -* Qu'est-ce qu'un graphe connexe acyclique? - -. . . - -* Un arbre! - -# Représentations - -* La complexité des algorithmes sur les graphes s'expriment en fonction du nombre de sommets $V$, et du nombre d'arêtes $E$: - * Si $|E|\sim |V|^2$, on dit que le graphe est **dense**. - * Si $|E|\sim |V|$, on dit que le graphe est **peu dense**. -* Selon qu'on considère des graphes denses ou peu denses, différentes structure de données peuvent être envisagées. - -## Question - -* Comment peut-on représenter un graphe informatiquement? Des idées (réflexion de quelques minutes)? - -. . . - -* Matrice/liste d'adjacence. - -# Matrice d'adjacence - -* Soit le graphe $G(V,E)$, avec $V=\{1, 2, 3, ..., n\}$; -* On peut représenter un graphe par une **matrice d'adjacence**, $A$, de dimension $n\times n$ définie par -$$ -A_{ij}=\left\{ \begin{array}{ll} - 1 & \mbox{si } i,j\in E,\\ - 0 & \mbox{sinon}. - \end{array} \right. -$$ - - -::: columns - -:::: column - -## Exemple - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---4; - 2---5; - 4---5; - 5---3; -``` - -:::: - -:::: column - -\footnotesize - -## Quelle matrice d'adjacence? - -. . . - -``` - || 1 | 2 | 3 | 4 | 5 -===||===|===|===|===|=== - 1 || 0 | 1 | 0 | 1 | 0 ----||---|---|---|---|--- - 2 || 1 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 3 || 0 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 4 || 1 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 5 || 0 | 1 | 1 | 1 | 0 -``` - -:::: - -::: - -# Matrice d'adjacence - -## Remarques - -* Zéro sur la diagonale. -* La matrice d'un graphe non-orienté est symétrique - -$$ -A_{ij}=A_{ji}, \forall i,j\in[1,n] -$$. - -::: columns - -:::: column - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---4; - 2---5; - 4---5; - 5---3; -``` - -:::: - -:::: column - -\footnotesize - -``` - || 1 | 2 | 3 | 4 | 5 -===||===|===|===|===|=== - 1 || 0 | 1 | 0 | 1 | 0 ----||---|---|---|---|--- - 2 || 1 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 3 || 0 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 4 || 1 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 5 || 0 | 1 | 1 | 1 | 0 -``` - -:::: - -::: - -# Matrice d'adjacence - -* Pour un graphe orienté (digraphe) - -::: columns - -:::: column - -## Exemple - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 2-->1; - 1-->4; - 2-->5; - 5-->2; - 4-->5; - 5-->3; -``` - -:::: - -:::: column - -\footnotesize - -## Quelle matrice d'adjacence? - -. . . - -``` - || 1 | 2 | 3 | 4 | 5 -===||===|===|===|===|=== - 1 || 0 | 0 | 0 | 1 | 0 ----||---|---|---|---|--- - 2 || 1 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 3 || 0 | 0 | 0 | 0 | 0 ----||---|---|---|---|--- - 4 || 0 | 0 | 0 | 0 | 1 ----||---|---|---|---|--- - 5 || 0 | 1 | 1 | 0 | 0 -``` - -:::: - -::: - -* La matrice d'adjacence n'est plus forcément symétrique -$$ -A_{ij}\neq A_{ji}. -$$ - -# Stockage - -* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe orienté? - -. . . - -* $\mathcal{O}(|V|^2)$. -* Quel est l'espace nécessaire pour stocker une matrice d'adjacence pour un graphe non-orienté? - -. . . - -* $\mathcal{O}(|V|-1)|V|/2$. - -# Considérations d'efficacité - -* Dans quel type de graphes la matrice d'adjacence est utile? - -. . . - -* Dans les graphes denses. -* Pourquoi? - -. . . - -* Dans les graphes peu denses, la matrice d'adjacence est essentiellement composée de `0`. - -## Remarque - -* Dans la majorité des cas, les grands graphes sont peu denses. -* Comment représenter un graphe autrement? - -# La liste d'adjacence (non-orienté) - -* Pour chaque sommet $v\in V$, stocker les sommets adjacents à $v$- -* Quelle structure de données pour la liste d'adjacence? - -. . . - -* Tableau de liste chaînée, vecteur (tableau dynamique), etc. - - -::: columns - -:::: column - -## Exemple - -{width=80%} - -:::: - -:::: column - - -## Quelle liste d'adjacence? - -. . . - - - - -:::: - -::: - -# La liste d'adjacence (orienté) - - -::: columns - -:::: column - -## Quelle liste d'adjacence pour... - -* Matrix (2min) - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 0-->1; - 0-->2; - 1-->2; - 3-->0; - 3-->1; - 3-->2; -``` - -:::: - -:::: column - -``` - - - - - - - - - - - -``` - - -:::: - -::: - -# Complexité - -## Stockage - -* Quelle espace est nécessaire pour stocker une liste d'adjacence (en fonction de $|E|$ et $|V|$)? - -. . . - -$$ -\mathcal{O}(|E|) -$$ - -* Pour les graphes *non-orientés*: $\mathcal{O}(2|E|)$. -* Pour les graphes *orientés*: $\mathcal{O}(|E|)$. - -## Définition - -* Le **degré** d'un sommet $v$, est le nombre d'arêtes incidentes du sommet (pour les graphes orientés on a un degré entrant ou sortant). -* Comment on retrouve le degré de chaque sommet avec la liste d'adjacence? - -. . . - -* C'est la longueur de la liste chaînée. - - -# Parcours - -* Beaucoup d'applications nécessitent de parcourir des graphes: - * Trouver un chemin d'un sommet à un autre; - * Trouver si le graphe est connexe; -* Il existe *deux* parcours principaux: - * en largeur (Breadth-First Search); - * en profondeur (Depth-First Search). -* Ces parcours créent *un arbre* au fil de l'exploration (si le graphe est non-connexe cela crée une *forêt*, un ensemble d'arbres). - -# Illustration: parcours en largeur - -{width=80%} - -# Exemple - -## Étape par étape (blanc non-visité) - -{width=50%} - -## Étape par étape (gris visité) - -{width=50%} - -# Exemple - -## Étape par étape - -{width=50%} - -## Étape par étape (vert à visiter) - -{width=50%} - -# Exemple - -## Étape par étape - -{width=50%} - -## Étape par étape - -{width=50%} - -# Exemple - -## Étape par étape - -{width=50%} - -## Étape par étape - -{width=50%} - -# Exemple - -## Étape par étape - -{width=50%} - -## Étape par étape - -{width=50%} - -# Exemple - -## Étape par étape - -{width=50%} - -## Étape par étape - -{width=50%} - -# En faisant ce parcours... - - -::: columns - -:::: column - -## Du parcours de l'arbre - -{width=100%} - -:::: - -:::: column - -## Quel arbre est créé par le parcours (2min)? - -. . . - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 0[x]-->1[w]; - 0-->2[t]; - 0-->3[y]; - 2-->9[u]; - 1-->4[s]; - 4-->5[r]; - 5-->6[v]; -``` - -:::: - -::: - -## Remarques - -* Le parcours dépend du point de départ dans le graphe. -* L'arbre sera différent en fonction du noeud de départ, et de l'ordre de parcours des voisins d'un noeud. - -# Le parcours en largeur - -## L'algorithme, idée générale (3min, matrix)? - -. . . - -```C -v = un sommet du graphe -i = 1 -pour sommet dans graphe et sommet non-visité - visiter(v, sommet, i) // marquer sommet à distance i visité - i += 1 -``` - -## Remarque - -* `i` est la distance de plus cours chemin entre `v` et les sommets en cours de visite. - - -# Le parcours en largeur - -## L'algorithme, pseudo-code (3min, matrix)? - -* Comment garder la trace de la distance? - -. . . - -* Utilisation d'une **file** - -. . . - -```C -initialiser(graphe) // tous sommets sont non-visités -file = visiter(sommet, vide) // sommet est un sommet du graphe au hasard -tant que !est_vide(file) - v = défiler(file) - file = visiter(v, file) -``` - -## Que fait visiter? - -``` -file visiter(sommet, file) - sommet = visité - pour w = chaque arête de sommet - si w != visité - file = enfiler(file, w) - retourne file -``` - -# Exercice (5min) - -## Appliquer l'algorithme sur le graphe - -{width=50%} - -* En partant de `v`, `s`, ou `u` (par colonne de classe). -* Bien mettre à chaque étape l'état de la file. - -# Complexité du parcours en largeur - -## Étape 1 - -* Extraire un sommet de la file; - -## Étape 2 - -* Traîter tous les sommets adjacents. - -## Quelle est la complexité? - -. . . - -* Étape 1: $\mathcal{O}(|V|)$, -* Étape 2: $\mathcal{O}(2|E|)$, -* Total: $\mathcal{O}(|V| + |2|E|)$. - -# Exercice - -* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---3; - 1---4; - 2---3; - 2---6; - 3---6; - 3---4; - 3---5; - 4---5; -``` - - -# Illustration: parcours en profondeur - -{width=80%} - -# Parcours en profondeur - -## Idée générale - -* Initialiser les sommets comme non-lus -* Visiter un sommet -* Pour chaque sommet visité, on visite un sommet adjacent s'il est pas encore visité récursivement. - -## Remarque - -* La récursivité est équivalent à l'utilisation d'une **pile**. - -# Parcours en profondeur - -## Pseudo-code (5min) - -. . . - -```C -initialiser(graphe) // tous sommets sont non-visités -pile = visiter(sommet, vide) // sommet est un sommet du graphe au hasard -tant que !est_vide(pile) - v = dépiler(pile) - pile = visiter(v, pile) -``` - -## Que fait visiter? - -. . . - -```C -pile visiter(sommet, pile) - sommet = visité - pour w = chaque arête de sommet - si w != visité - pile = empiler(pile, w) - retourne pile -``` - - -# Exercice - -* Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---3; - 1---4; - 2---3; - 2---6; - 3---6; - 3---4; - 3---5; - 4---5; -``` - -# Interprétation des parcours - -* Un graphe vu comme espace d'états (sommet: état, arête: action); - * Labyrinthe; - * Arbre des coups d'un jeu. - -. . . - -* BFS (Breadth-First) ou DFS (Depth-First) parcourent l'espace des états à la recherche du meilleur mouvement. - * Les deux parcourent *tout* l'espace; - * Mais si l'arbre est grand, l'espace est gigantesque! - -. . . - -* Quand on a un temps limité - * BFS explore beaucoup de coups dans un futur proche; - * DFS explore peu de coups dans un futur lointain. diff --git a/slides/cours_24.md b/slides/cours_24.md deleted file mode 100644 index 7fa2fa9..0000000 --- a/slides/cours_24.md +++ /dev/null @@ -1,709 +0,0 @@ ---- -title: "Graphes - Plus court chemin" -date: "2023-06-02" ---- - -# Rappel du dernier cours - -* Qu'est-ce qu'un graphe? Un graphe orienté? Un graphe pondéré? - -. . . - -* Ensemble de sommets et arêtes, avec une direction, possédant une pondération. -* Comment représenter un graphe informatiquement? - -. . . - -* Liste ou matrice d'adjacence. -* Quel est le parcours que nous avons vu? - -. . . - -* Le parcours en largeur. - -# Le parcours en largeur - -## Le pseudo-code - -* Utilisation d'une **file** - -```C -initialiser(graphe) // tous sommets sont non-visités -file = visiter(sommet, vide) // sommet est un sommet - // du graphe -tant que !est_vide(file) - v = defiler(file) - file = visiter(v, file) - -file visiter(sommet, file) - sommet = visité - pour w = chaque arête de sommet - si w != visité - file = enfiler(file, w) - retourne file -``` - -# Complexité du parcours en largeur - -## Étape 1 - -* Extraire un sommet de la file; - -## Étape 2 - -* Traîter tous les sommets adjacents. - -## Quelle est la complexité? - -. . . - -* Étape 1: $\mathcal{O}(|V|)$, -* Étape 2: $\mathcal{O}(2|E|)$, -* Total: $\mathcal{O}(|V| + 2|E|)$. - -# Exercice - -* Établir la liste d'adjacence et appliquer l'algorithme de parcours en largeur au graphe - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---3; - 1---4; - 2---3; - 2---6; - 3---6; - 3---4; - 3---5; - 4---5; -``` - - -# Illustration: parcours en profondeur - -{width=80%} - -# Parcours en profondeur - -## Idée générale - -* Initialiser les sommets comme non-lus -* Visiter un sommet -* Pour chaque sommet visité, on visite un sommet adjacent s'il est pas encore visité récursivement. - -## Remarque - -* La récursivité est équivalent à l'utilisation d'une **pile**. - -# Parcours en profondeur - -## Pseudo-code (5min) - -. . . - -```C -initialiser(graphe) // tous sommets sont non-visités -pile = visiter(sommet, vide) // sommet est un - // sommet du graphe -tant que !est_vide(pile) - v = dépiler(pile) - pile = visiter(v, pile) -``` - -## Que fait visiter? - -. . . - -```C -pile visiter(sommet, pile) - sommet = visité - pour w = chaque arête de sommet - si w != visité - pile = empiler(pile, w) - retourne pile -``` - - -# Exercice - -* Établir la liste d'adjacence et appliquer l'algorithme de parcours en profondeur au graphe - -```{.mermaid format=pdf width=400 loc=figs/} -graph LR; - 1---2; - 1---3; - 1---4; - 2---3; - 2---6; - 3---6; - 3---4; - 3---5; - 4---5; -``` - -# Interprétation des parcours - -* Un graphe vu comme espace d'états (sommet: état, arête: action); - * Labyrinthe; - * Arbre des coups d'un jeu. - -. . . - -* BFS (Breadth-First) ou DFS (Depth-First) parcourent l'espace des états à la recherche du meilleur mouvement. - * Les deux parcourent *tout* l'espace; - * Mais si l'arbre est grand, l'espace est gigantesque! - -. . . - -* Quand on a un temps limité - * BFS explore beaucoup de coups dans un futur proche; - * DFS explore peu de coups dans un futur lointain. - - -## Contexte: les réseaux (informatique, transport, etc.) - -* Graphe orienté; -* Source: sommet `s`; -* Destination: sommet `t`; -* Les arêtes ont des poids (coût d'utilisation, distance, etc.); -* Le coût d'un chemin est la somme des poids des arêtes d'un chemin. - -## Problème à résoudre - -* Quel est le plus court chemin entre `s` et `t`. - -# Exemples d'application de plus court chemin - -## Devenir riches! - -* On part d'un tableau de taux de change entre devises. -* Quelle est la meilleure façon de convertir l'or en dollar? - -{width=80%} - -. . . - -* 1kg d'or => 327.25 dollars -* 1kg d'or => 208.1 livres => 327 dollars -* 1kg d'or => 455.2 francs => 304.39 euros => 327.28 dollars - -# Exemples d'application de plus court chemin - -## Formulation sous forme d'un graphe: Comment (3min)? - -{width=80%} - - -# Exemples d'application de plus court chemin - -## Formulation sous forme d'un graphe: Comment (3min)? - -{width=60%} - -* Un sommet par devise; -* Une arête orientée par transaction possible avec le poids égal au taux de change; -* Trouver le chemin qui maximise le produit des poids. - -. . . - -## Problème - -* On aimerait plutôt avoir une somme... - - -# Exemples d'application de plus court chemin - -## Conversion du problème en plus court chemin - -* Soit `taux(u, v)` le taux de change entre la devise `u` et `v`. -* On pose `w(u,w)=-log(taux(u,v))` -* Trouver le chemin poids minimal pour les poids `w`. - -{width=60%} - -* Cette conversion se base sur l'idée que - -$$ -\log(u\cdot v)=\log(u)+\log(v). -$$ - -# Applications de plus courts chemins - -## Quelles applications voyez-vous? - -. . . - -* Déplacement d'un robot; -* Planificaiton de trajet / trafic urbain; -* Routage de télécommunications; -* Réseau électrique optimal; -* ... - -# Plus courts chemins à source unique - -* Soit un graphe, $G=(V, E)$, une fonction de pondération $w:E\rightarrow\mathbb{R}$, et un sommet $s\in V$ - * Trouver pour tout sommet $v\in V$, le chemin de poids minimal reliant $s$ à $v$. -* Algorithmes standards: - * Dijkstra (arêtes de poids positif seulement); - * Bellman-Ford (arêtes de poids positifs ou négatifs, mais sans cycles). -* Comment résoudre le problèmes si tous les poids sont les mêmes? - -. . . - -* Un parcours en largeur! - -# Algorithme de Dijkstra - -## Comment chercher pour un plus court chemin? - -. . . - -``` -si distance(u,v) > distance(u,w) + distance(w,v) - on passe par w plutôt qu'aller directement -``` - -# Algorithme de Dijkstra (1 à 5) - -* $D$ est le tableau des distances au sommet $1$: $D[7]$ est la distance de 1 à 7. -* Le chemin est pas forcément direct. -* $S$ est le tableau des sommets visités. - -::: columns - -:::: column - - - -:::: - -:::: column - -. . . - -![1 visité, `D[2]=1`, `D[4]=3`.](figs/dijkstra_1.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - -::: columns - -:::: column - - - - -:::: - -:::: column - -. . . - -![2 visité, `D[3]=2`, `D[7]=3`.](figs/dijkstra_2.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - -::: columns - -:::: column - - - - -:::: - -:::: column - -. . . - -![3 visité, `D[7]=3` inchangé, `D[6]=6`.](figs/dijkstra_3.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - - -::: columns - -:::: column - - - - -:::: - -:::: column - -. . . - -![4 visité, `D[7]=3` inchangé, `D[5]=9`.](figs/dijkstra_4.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - -::: columns - -:::: column - - - - -:::: - -:::: column - -. . . - -![7 visité, `D[5]=7`, `D[6]=6` inchangé.](figs/dijkstra_5.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - -::: columns - -:::: column - - - - -:::: - -:::: column - -. . . - -![`6` visité, `D[5]=7` inchangé.](figs/dijkstra_6.png) - -:::: - -::: - -# Algorithme de Dijkstra (1 à 5) - -::: columns - -:::: column - - - -:::: - -:::: column - -. . . - - - -:::: - -::: - -# Algorithme de Dijkstra - -## Idée générale - -* On assigne à chaque noeud une distance $0$ pour $s$, $\infty$ pour les autres. -* Tous les noeuds sont marqués non-visités. -* Depuis du noeud courant, on suit chaque arête du noeud vers un sommet non visité et on calcule le poids du chemin à chaque voisin et on met à jour sa distance si elle est plus petite que la distance du noeud. -* Quand tous les voisins du noeud courant ont été visités, le noeud est mis à visité (il ne sera plus jamais visité). -* Continuer avec le noeud à la distance la plus faible. -* L'algorithme est terminé losrque le noeud de destination est marqué comme visité, ou qu'on a plus de noeuds qu'on peut visiter et que leur distance est infinie. - -# Algorithme de Dijkstra - -## Pseudo-code (5min, matrix) - -\footnotesize - -. . . - -```C -tab dijkstra(graph, s, t) - pour chaque v dans graphe - distance[v] = infini - q = ajouter(q, v) - distance[s] = 0 - tant que non_vide(q) - // sélection de u t.q. la distance dans q est min - u = min(q, distance) - si u == t // on a atteint la cible - retourne distance - q = remove(q, u) - // voisin de u encore dans q - pour chaque v dans voisinage(u, q) - // on met à jour la distance du voisin en passant par u - n_distance = distance[u] + w(u, v) - si n_distance < distance[v] - distance[v] = n_distance - retourne distance -``` - -# Algorithme de Dijkstra - -* Cet algorithme, nous donne le plus court chemin mais... -* ne nous donne pas le chemin! - -## Comment modifier l'algorithme pour avoir le chemin? - -. . . - -* Pour chaque nouveau noeud à visiter, il suffit d'enregistrer d'où on est venu! -* On a besoin d'un tableau `precedent`. - -## Modifier le pseudo-code ci-dessus pour ce faire (3min matrix) - -# Algorithme de Dijkstra - -\footnotesize - -```C -tab, tab dijkstra(graph, s, t) - pour chaque v dans graphe - distance[v] = infini - precedent[v] = indéfini - q = ajouter(q, v) - distance[s] = 0 - tant que non_vide(q) - // sélection de u t.q. la distance dans q est min - u = min(q, distance) - si u == t - retourne distance - q = remove(q, u) - // voisin de u encore dans q - pour chaque v dans voisinage(u, q) - n_distance = distance[u] + w(u, v) - si n_distance < distance[v] - distance[v] = n_distance - precedent[v] = u - retourne distance, precedent -``` - -# Algorithme de Dijkstra - -## Comment reconstruire un chemin ? - -. . . - -```C -pile parcours(precedent, s, t) - sommets = vide - u = t - // on a atteint t ou on ne connait pas de chemin - si u != s && precedent[u] != indéfini - tant que vrai - sommets = empiler(sommets, u) - u = precedent[u] - si u == s // la source est atteinte - retourne sommets - retourne sommets -``` - -# Algorithme de Dijkstra amélioré - -## On peut améliorer l'algorithme - -* Avec une file de priorité! - -## Une file de priorité est - -* Une file dont chaque élément possède une priorité, -* Elle existe en deux saveurs: `min` ou `max`: - * File `min`: les éléments les plus petits sont retirés en premier. - * File `max`: les éléments les plus grands sont retirés en premier. -* On regarde l'implémentation de la `max`. - -## Comment on fait ça? - -. . . - -* On insère les éléments à haute priorité tout devant dans la file! - -# Les files de priorité - -## Trois fonction principales - -```C -booléen est_vide(element) // triviale -element enfiler(element, data, priorite) -data defiler(element) -rien changer_priorite(element, data, priorite) -nombre priorite(element) // utilitaire -``` - -## Pseudo-implémentation: structure (1min) - -. . . - -```C -struct element - data - priorite - element suivant -``` - -# Les files de priorité - -## Pseudo-implémentation: enfiler (2min) - -. . . - -```C -element enfiler(element, data, priorite) - n_element = creer_element(data, priorite) - si est_vide(element) - retourne n_element - si priorite(n_element) > priorite(element) - n_element.suivant = element - retourne n_element - sinon - tmp = element - prec = element - tant que !est_vide(tmp) && priorite < priorite(tmp) - prec = tmp - tmp = tmp.suivant - prev.suivant = n_element - n_element.suivant = tmp - retourne element -``` - -# Les files de priorité - -## Pseudo-implémentation: defiler (2min) - -. . . - -```C -data, element defiler(element) - si est_vide(element) - retourne AARGL! - sinon - tmp = element.data - n_element = element.suivant - liberer(element) - retourne tmp, n_element -``` - -# Algorithme de Dijkstra avec file de priorité min - -```C -distance, precedent dijkstra(graphe, s, t): - distance[source] = 0 - fp = file_p_vide() - pour v dans sommets(graphe) - si v != s - distance[v] = infini - precedent[v] = indéfini - fp = enfiler(fp, v, distance[v]) - tant que !est_vide(fp) - u, fp = defiler(fp) - pour v dans voisinage de u - n_distance = distance[u] + w(u, v) - si n_distance < distance[v] - distance[v] = n_distance - precedent[v] = u - fp = changer_priorite(fp, v, n_distance) - retourne distance, precedent -``` - -# Algorithme de Dijkstra avec file - -\footnotesize - -```C -distance dijkstra(graphe, s, t) ---------------------------------------------------------- - pour v dans sommets(graphe) -O(V) si v != s - distance[v] = infini -O(V) fp = enfiler(fp, v, distance[v]) // notre impl est nulle -------------------O(V * V)------------------------------- - tant que !est_vide(fp) -O(1) u, fp = defiler(fp) ---------------------------------------------------------- -O(E) pour v dans voisinage de u - n_distance = distance[u] + w(u, v) - si n_distance < distance[v] - distance[v] = n_distance -O(V) fp = changer_priorite(fp, v, n_distance) ---------------------------------------------------------- - retourne distance -``` - -* Total: $\mathcal{O}(|V|^2+|E|\cdot |V|)$: - * Graphe dense: $\mathcal{O}(|V|^3)$ - * Graphe peu dense: $\mathcal{O}(|V|^2)$ - -# Algorithme de Dijkstra avec file - -## On peut faire mieux - -* Avec une meilleure implémentation de la file de priorité: - * Tas binaire: $\mathcal{O}(|V|\log|V|+|E|\log|V|)$. - * Tas de Fibonnacci: $\mathcal{O}(|V|+|E|\log|V|)$ -* Graphe dense: $\mathcal{O}(|V|^2\log|V|)$. -* Graphe peu dense: $\mathcal{O}(|V|\log|V|)$. - -# Algorithme de Dijkstra (exercice, 5min) - -{width=60%} - -* Donner la liste de priorité, puis... - -## A chaque étape donner: - -* Le tableau des distances à `a`; -* Le tableau des prédécesseurs; -* L'état de la file de priorité. - -# Algorithme de Dijkstra (corrigé) - - - -# Algorithme de Dijkstra (corrigé) - - - -# Algorithme de Dijkstra (corrigé) - - - -# Algorithme de Dijkstra (corrigé) - - - -# Algorithme de Dijkstra (corrigé) - - - -# Algorithme de Dijkstra (corrigé) - - - -# Limitation de l'algorithme de Dijkstra - -## Que se passe-t-il pour? - -{width=50%} - -## Quel est le problème? - -. . . - -* L'algorithme n'essaiera jamais le chemin `s->x->y->v` et prendra direct `s->v`. -* Ce problème n'apparaît que s'il y a des poids négatifs. - diff --git a/slides/cours_25.md b/slides/cours_25.md deleted file mode 100644 index 8deb43b..0000000 --- a/slides/cours_25.md +++ /dev/null @@ -1,1077 +0,0 @@ ---- -title: "Graphes - Plus court chemin suite" -date: "2023-06-23" ---- - -# Questions - -* A quoi sert l'algorithme de Dijkstra? - -. . . - -* A trouver le plus court chemin entre un sommet, $s$, d'un graphe pondéré et tous les autres sommets. -* Quelle est la limitation de l'algorithme de Dijkstra? - -. . . - -* Les poids doivent être positifs. -* Résumer les étapes de l'algorithme de Dijkstra. - -. . . - -* `distance[source] = 0`, `distance[reste]=inf`; -* enfiler tous les sommets, `distance <=> priorité`; -* tant qu'il y a des sommets dans la file: - * u = défiler; - * pour tous les sommets `v` dans le voisinage de `u`; - * mettre à jour `distance[v]` (priorité et précédence) si `distance[v] > distance[u] + w(u,v)`. - - -# Plus cours chemin pour toute paire de sommets - -## Comment faire pour avoir toutes les paires? - -. . . - -* Appliquer Dijkstra sur tous les sommets d'origine. -* Complexité: - * Graphe dense: $\mathcal{O}(|V|)\mathcal{O}(|V|^2\log|V|)=\mathcal{O}(|V|^3\log|V|)$. - * Graphe peu dense: $\mathcal{O}(|V|)\mathcal{O}(|V|\log|V|)=\mathcal{O}(|V|^2\log|V|)$. - -. . . - -## Solution alternative: Floyd--Warshall - -* Pour toutes paires de sommets $u,v\in V$, trouver le chemin de poids minimal reliant $u$ à $v$. -* Complexité $\mathcal{O}(|V|^3)$, indiqué pour graphes denses. -* Fonctionne avec la matrice d'adjacence. - -# Algorithme de Floyd--Warshall - -## Idée générale - -* Soit l'ensemble de sommets $V=\{1, 2, 3, 4, ..., n\}$. -* Pour toute paire de sommets, $i,j$, on considère tous les chemins passant par les sommets intermédiaires $\in\{1, 2, ..., k\}$ avec $k\leq n$. -* On garde pour chaque $k$ la plus petite valeur. - -## Principe - -* A chaque étape, $k$, on vérifie s'il est plus court d'aller de $i$ à $j$ en passant par le sommet $k$. -* Si à l'étape $k-1$, le coût du parcours est $p$, on vérifie si $p$ est plus petit que $p_1+p_2$, le chemin de $i$ à $k$, et $k$ à $j$ respectivement. - -# Algorithme de Floyd--Warshall - -## The algorithme - -Soit $d_{ij}(k)$ le plus court chemin de $i$ à $j$ passant par les sommets $\in\{1,2,...,k\}$ - -$$ -d_{ij}(k)=\left\{ -\begin{array}{ll} - w(i,j), & \mbox{si } k=0,\\ - \min(d_{ij}(k-1),d_{ik}(k-1)+d_{kj}(k-1)), & \mbox{sinon}. -\end{array} -\right. -$$ - -# Algorithme de Floyd--Warshall (exemple) - - -::: columns - -:::: column - - - - -:::: - -:::: column - -## Que vaut $D^{(0)}$ (3min)? - -. . . - -$$ -D^{(0)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 8 & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & \infty & \infty & 0 & 5 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - - -::: columns - -:::: column - -## On part de $D^{(0)}$? - -$$ -D^{(0)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 8 & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & \infty & \infty & 0 & 5 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - - -:::: - -:::: column - -## Que vaut $D^{(1)}$ (3min)? - -. . . - -$$ -D^{(0)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & \mathbf{6} & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - - -::: columns - -:::: column - -## On part de $D^{(0)}$ - -$$ -D^{(0)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 8 & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & \infty & \infty & 0 & 5 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - - -:::: - -:::: column - -## Que vaut $D^{(1)}$ (3min)? - -. . . - -$$ -D^{(1)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & \mathbf{6} & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & \mathbf{3} & \mathbf{5} & 0 & \mathbf{4} \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -## Exemple - -$$ -D_{42}^{(1)}=D_{41}^{(0)}+D_{12}^{(0)}=1+2<\infty. -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - -::: columns - -:::: column - -## On part de $D^{(1)}$ - -$$ -D^{(1)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 6 & \infty & 1 \\ -6 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - - -:::: - -:::: column - -## Que vaut $D^{(2)}$ (3min)? - -. . . - -$$ -D^{(2)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 6 & \infty & 1 \\ -\mathbf{4} & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - -::: columns - -:::: column - -## On part de $D^{(2)}$ - -$$ -D^{(2)}=\begin{bmatrix} -0 & 2 & 4 & \infty & 3 \\ -2 & 0 & 6 & \infty & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - - -:::: - -:::: column - -## Que vaut $D^{(3)}$ (3min)? - -. . . - -$$ -D^{(3)}=\begin{bmatrix} -0 & 2 & 4 & \mathbf{8} & 3 \\ -2 & 0 & 6 & \mathbf{10} & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - -::: columns - -:::: column - -## On part de $D^{(3)}$ - -$$ -D^{(3)}=\begin{bmatrix} -0 & 2 & 4 & 8 & 3 \\ -2 & 0 & 6 & 10 & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\infty & \infty & \infty & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -:::: column - -## Que vaut $D^{(4)}$ (3min)? - -. . . - -$$ -D^{(4)}=\begin{bmatrix} -0 & 2 & 4 & 8 & 3 \\ -2 & 0 & 6 & 10 & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -\mathbf{2} & \mathbf{4} & \mathbf{6} & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exemple) - -::: columns - -:::: column - -## On part de $D^{(4)}$ - -$$ -D^{(4)}=\begin{bmatrix} -0 & 2 & 4 & 8 & 3 \\ -2 & 0 & 6 & 10 & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -2 & 4 & 6 & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -:::: column - -## Que vaut $D^{(5)}$ (3min)? - -. . . - -$$ -D^{(5)}=\begin{bmatrix} -0 & 2 & 4 & \mathbf{4} & 3 \\ -2 & 0 & 6 & \mathbf{2} & 1 \\ -4 & 2 & 0 & 4 & 3 \\ -1 & 3 & 5 & 0 & 4 \\ -2 & 4 & 6 & 1 & 0 \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall - -## The pseudo-code (10min) - -* Quelle structure de données? -* Quelle initialisation? -* Quel est le code pour le calcul de la matrice $D$? - -# Algorithme de Floyd--Warshall - -## The pseudo-code - -* Quelle structure de données? - -```C -int distance[n][n]; -``` - -. . . - -* Quelle initialisation? - -```C -matrice ini_floyd_warshall(distance, n, w) - pour i de 1 à n - pour j de 1 à n - distance[i][j] = w(i,j) - retourne distance -``` - -# Algorithme de Floyd--Warshall - -## The pseudo-code - -* Quel est le code pour le calcul de la matrice $D$? - -```C -matrice floyd_warshall(distance, n, w) - pour k de 1 à n - pour i de 1 à n - pour j de 1 à n - distance[i][j] = min(distance[i][j], - distance[i][k] + distance[k][j]) - retourne distance -``` - -# Algorithme de Floyd--Warshall - -## La matrice de précédence - -* On a pas encore vu comment reconstruire le plus court chemin! -* On définit, $P_{ij}^{(k)}$, qui est le prédécesseur du sommet $j$ depuis $i$ avec les sommets intermédiaires $\in\{1, 2, ..., k\}$. -$$ -P^{(0)}_{ij}=\left\{ -\begin{array}{ll} - \mbox{vide}, & \mbox{si } i=j\mbox{, ou }w(i,j)=\infty\\ - i, & \mbox{sinon}. -\end{array} -\right. -$$ - -* Mise à jour -$$ -P^{(k)}_{ij}=\left\{ -\begin{array}{ll} - P^{(k-1)}_{\mathbf{i}j}, & \mbox{si } d_{ij}^{(k)}\leq d_{ik}^{(k-1)}+d_{kj}^{(k-1)}\\ - P^{(k-1)}_{\mathbf{k}j}, & \mbox{sinon}. -\end{array} -\right. -$$ - -. . . - -* Moralité: si le chemin est plus court en passant par $k$, alors il faut utiliser son prédécesseur! - -# Algorithme de Floyd--Warshall - -## La matrice de précédence (pseudo-code, 3min) - -. . . - -```C -matrice, matrice floyd_warshall(distance, n, w) - pour k de 1 à n - pour i de 1 à n - pour j de 1 à n - n_distance = distance[i][k] + distance[k][j] - if n_distance < distance[i][j] - distance[i][j] = n_distance - précédence[i][j] = précédence[k][j] - retourne distance, précédence -``` - -# Algorithme de Floyd--Warshall (exercice) - - -::: columns - -:::: column - - - - -:::: - -:::: column - -## Que vaut $P^{(0)}$ (3min)? - -. . . - -$$ -P^{(0)}=\begin{bmatrix} -- & 1 & 1 & - & 1 \\ -2 & - & 2 & - & 2 \\ -3 & 3 & - & 3 & 3 \\ -4 & - & - & - & 4 \\ -- & - & - & 5 & - \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Algorithme de Floyd--Warshall (exercice) - - -::: columns - -:::: column - - - - -:::: - -:::: column - -## Que vaut $P^{(5)}$ (10min)? - -. . . - -$$ -P^{(5)}=\begin{bmatrix} -- & 1 & 1 & 5 & 1 \\ -2 & - & 1 & 5 & 2 \\ -2 & 3 & - & 3 & 3 \\ -4 & 1 & 1 & - & 1 \\ -4 & 1 & 1 & 5 & - \\ -\end{bmatrix} -$$ - -:::: - -::: - -# Exercice: retrouver le chemin entre 1 et 4 (5min) - -$$ -P=\begin{bmatrix} -- & 1 & 1 & 5 & 1 \\ -2 & - & 1 & 5 & 2 \\ -2 & 3 & - & 3 & 3 \\ -4 & 1 & 1 & - & 4 \\ -4 & 1 & 1 & 5 & - \\ -\end{bmatrix} -$$ - -. . . - -## Solution - -* Le sommet $5=P_{14}$, on a donc, $5\rightarrow 4$, on veut connaître le prédécesseur de 5. -* Le sommet $1=P_{15}$, on a donc, $1\rightarrow 5\rightarrow 4$. The end. - -# Exercice complet - -## Appliquer l'algorithme de Floyd--Warshall au graphe suivant - -{width=50%} - -* Bien indiquer l'état de $D$ et $P$ à chaque étape! -* Ne pas oublier de faire la matrice d'adjacence évidemment... - -# La suite - -* Sans transition.... la suite! - -# Trouver un réseau électrique pour - - - -# Solution: pas optimale - - - -* La longueur totale des câbles est super longue! - -# Solution: optimale - - - -# Formalisation: Les arbres couvrants - -## Application: minimisation des coûts - -* Équipement d'un lotissement avec des lignes électriques/téléphoniques, des canalisations, ... - -. . . - -* Pour réduire les coûts, on cherche à minimiser la longueur totale des câbles/tuyaux. - -. . . - -* Les lignes/tuyaux forment un *arbre couvrant*. - -. . . - -* La meilleure option est un *arbre couvrant minimal*. - - -# Formalisation: Les arbres couvrants - -* Qu'est-ce qu'un arbre couvrant? Des idées? De quel objet on part? Où va-t-on? - -. . . - -* Un arbre couvrant d'un graphe non-orienté et connexe est: - * un arbre inclus dans le graphe qui connecte tous les sommets du graphe. - -. . . - - - -# Arbres couvrants - -* Quels algorithmes que nous avons déjà vus permettent de construire des arbres couvrants? - -. . . - -* Les parcours en largeur et en profondeur! - -. . . - - - -# Arbres couvrants minimaux - -* Un *arbre couvrant minimal* est un sous-graphe d'un graphe non-orienté pondéré $G(V,E)$, tel quel: - * C'est un arbre (graphe acyclique); - * Il couvre tous les sommets de $G$ et contient $|V|-1$ arêtes; - * Le coût total associé aux arêtes de l'arbre est minimum parmi tous les arbres couvrants possibles. - -. . . - -* Est-il unique? - -. . . - -* Pas forcément. - -# Arbres couvrants minimaux - -* Comment générer un arbre couvrant minimal? - - - -# Algorithme de Prim - -::: columns - -:::: column - -## Un exemple - - - -:::: - -:::: column - -## On part de `e` (au hasard) - - - -:::: - -::: - -# Algorithme de Prim - -::: columns - -:::: column - -## On choisit comment? - - - -. . . - -* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité. - -:::: - -:::: column - -. . . - -## L'arête `e->d` - - - -:::: - -::: - -# Algorithme de Prim - -::: columns - -:::: column - -## On choisit comment? - - - -. . . - -* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité. - -:::: - -:::: column - -. . . - -## L'arête `d->a` - - - -:::: - -::: - -# Algorithme de Prim - -::: columns - -:::: column - -## On choisit comment? - - - -. . . - -* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité. - -:::: - -:::: column - -. . . - -## L'arête `d->c` - - - -:::: - -::: - -# Algorithme de Prim - -::: columns - -:::: column - -## On choisit comment? - - - -. . . - -* L'arête la plus courte sortant d'un sommet déjà visité, et entrant dans un sommet non-visité. - -:::: - -:::: column - -. . . - -## L'arête `e->b` - - - -* Game over! - -:::: - -::: - -# Algorithme de Prim - -## Structures de données - -* Dans quoi allons nous stocker les sommets? - -. . . - -* File de priorité min. -* Autre chose? - -. . . - -* Tableau des distances (comme pour Dijkstra). -* Autre chose? - -. . . - -* Tableau des parents (presque comme pour Dijkstra). -* Autre chose? - -. . . - -* Non. - -# Algorithme de Prim - -## Initialisation: Pseudo-code (2min) - -. . . - -```C -file_priorité, distance, parent initialisation(graphe) - r = aléatoire(graphe) - distance[r] = 0 - parent[r] = indéfini - fp = file_p_vide() - pour v dans sommets(graphe) - si v != r - distance[v] = infini - parent[v] = indéfini - fp = enfiler(fp, v, distance[v]) - retourne fp, distance, parent -``` - -# Algorithme de Prim - -## Algorithme: Pseudo-code (5min) - -. . . - -```C -sommets, parent prim(file_priorité, distance, parent) - sommets = vide - tant que !est_vide(file_priorité) - u, fp = défiler(file_priorité) - sommets = insérer(sommets, u) - pour v dans voisinage de u et pas dans sommets - // ou dans file_priorité - si w(u, v) < distance[v] - parent[w] = u - distance[w] = w(u, v) - fp = changer_priorité(fp, w, w(u, v)) - retourne sommets, parent -``` - -# Exemple d'algorithme de Prim - -::: columns - -:::: {.column width="40%"} - -## Un exemple - - - -:::: - -:::: column - -``` -FP | e | d | b | c | a | ----------------------------------- -D | 0 | inf | inf | inf | inf | - - | e | d | b | c | a | ----------------------------------- -P | - | - | - | - | - | -``` - -## Devient? - -. . . - -``` -FP | d | b | c | a | ----------------------------- -D | 4 | 5 | 5 | inf | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | e | - | -``` - -:::: - -::: - -# Exemple d'algorithme de Prim - -::: columns - -:::: {.column width="40%"} - -## Un exemple - - - -:::: - -:::: column - -``` -FP | d | b | c | a | ----------------------------- -D | 4 | 5 | 5 | inf | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | e | - | -``` - -## Devient? - -. . . - -``` -FP | a | c | b | ----------------------- -D | 2 | 4 | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -:::: - -::: - -# Exemple d'algorithme de Prim - -::: columns - -:::: {.column width="40%"} - -## Un exemple - - - -:::: - -:::: column - -``` -FP | a | c | b | ----------------------- -D | 2 | 4 | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -## Devient? - -. . . - -``` -FP | c | b | ----------------- -D | 4 | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -:::: - -::: - -# Exemple d'algorithme de Prim - -::: columns - -:::: {.column width="40%"} - -## Un exemple - - - -:::: - -:::: column - -``` -FP | c | b | ----------------- -D | 4 | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -## Devient? - -. . . - -``` -FP | b | ----------- -D | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -:::: - -::: - -# Exemple d'algorithme de Prim - -::: columns - -:::: {.column width="40%"} - -## Un exemple - - - -:::: - -:::: column - -``` -FP | b | ----------- -D | 5 | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -## Devient? - -. . . - -``` -FP | ----- -D | - - | e | d | b | c | a | ----------------------------------- -P | - | e | e | d | d | -``` - -:::: - -::: - -# Exercice: algorithme de Prim - -## Appliquer l'algorithme de Prim à (15min): - - - -# Exercice: algorithme de Prim - -## Solution - - - -# Complexité de l'algorithme de Prim - -\footnotesize - -```C -file_priorité, distance, parent initialisation(graphe) - // choix r et initialisation - pour v dans sommets(graphe) -O(|V|) // initialisation distance et parent - fp = enfiler(fp, v, distance[v]) - retourne fp, distance, parent -sommets, parent prim(file_priorité, distance, parent) - sommets = vide - tant que !est_vide(file_priorité) -O(|V|) u, fp = défiler(file_priorité) - sommets = insérer(sommets, u) - pour v dans voisinage de u et pas dans sommets - O(|E|) si w(u, v) < distance[v] - // mà j dista + parent - O(|V|) fp = changer_priorité(fp, w, w(u, v)) - retourne sommets, parent -``` - -* $O(|V|)+O(|E|)+O(|V|^2)=O(|E|+|V|^2)$ -* Remarque: $O(|E|)$ n'est pas mutliplié par $O(|V|)$, car les arêtes parcourues qu'une fois en **tout**. diff --git a/slides/cours_3.md b/slides/cours_3.md deleted file mode 100644 index f2f3788..0000000 --- a/slides/cours_3.md +++ /dev/null @@ -1,339 +0,0 @@ ---- -title: "Introduction aux algorithmes" -date: "2022-10-05" -patat: - eval: - tai: - command: fish - fragment: false - replace: true - ccc: - command: fish - fragment: false - replace: true - images: - backend: auto ---- - -# Rappel (1/2) - -## Quels algos avons-nous vu la semaine passée? - -. . . - -* L'algorithme de la factorielle. -* L'algorithme du PPCM. -* Le début de l'algorithme du PGCD. - -# Rappel (2/2) - -## Algorithme du PPCM? - -. . . - -```C -int main() { - int m = 15, n = 12; - int mult_m = m, mult_n = n; - while (mult_m != mult_n) { - if (mult_m > mult_n) { - mult_n += n; - } else { - mult_m += m; - } - } - printf("Le ppcm de %d et %d est %d\n", n, m, mult_m); -} -``` - -# Le calcul du PGCD (1/5) - -## Définition - -Le plus grand commun diviseur (PGCD) de deux nombres entiers non nuls est le -plus grand entier qui les divise en même temps. - -## Exemples: - -```C -PGCD(3, 4) = 1, -PGCD(4, 6) = 2, -PGCD(5, 15) = 5. -``` - -. . . - -## Mathématiquement - -Décomposition en nombres premiers: - -$$ -36 = 2^2\cdot 3^2,\quad 90=2\cdot 5\cdot 3^2, -$$ -On garde tous les premiers à la puissance la plus basse -$$ -PGCD(36, 90)=2^{\min{1,2}}\cdot 3^{\min{2,2}}\cdot 5^{\min{0,1}}=18. -$$ - -# Le calcul du PGCD (2/5) - -## Algorithme - -Par groupe de 3 (5-10min): - -* réfléchissez à un algorithme alternatif donnant le PGCD de deux nombres; -* écrivez l'algorithme en pseudo-code. - -. . . - -## Exemple d'algorithme - -```C -PGCD(36, 90): -90 % 36 != 0 // otherwise 36 would be PGCD -90 % 35 != 0 // otherwise 35 would be PGCD -90 % 34 != 0 // otherwise 34 would be PGCD -... -90 % 19 != 0 // otherwise 19 would be PGCD -90 % 18 == 0 // The end! -``` - -* 18 modulos, 18 assignations, et 18 comparaisons. - -# Le calcul du PGCD (3/5) - -## Transcrivez cet exemple en algorithme (groupe de 3) et codez-le (5-10min)! - -. . . - -## Optimisation - -* Combien d'additions / comparaisons au pire? -* Un moyen de le rendre plus efficace? - -. . . - -## Tentative de correction - -```C -void main() { - int n = 90, m = 78; - int gcd = 1; - for (int div = n; div >= 2; div--) { // div = m, sqrt(n) - if (n % div == 0 && m % div == 0) { - gcd = div; - break; - } - } - printf("Le pgcd de %d et %d est %d\n", n, m, gcd); -} -``` - -# Le calcul du PGCD (4/5) - -## Réusinage: l'algorithme d'Euclide - -`Dividende = Diviseur * Quotient + Reste` - -```C -PGCD(35, 60): -35 = 60 * 0 + 35 // 60 -> 35, 35 -> 60 -60 = 35 * 1 + 25 // 35 -> 60, 25 -> 35 -35 = 25 * 1 + 10 // 25 -> 35, 20 -> 25 -25 = 10 * 2 + 5 // 10 -> 25, 5 -> 10 -10 = 5 * 2 + 0 // PGCD = 5! -``` - -. . . - -## Algorithme - -Par groupe de 3 (5-10min): - -* analysez l'exemple ci-dessus; -* transcrivez le en pseudo-code. - -# Le calcul du PGCD (5/5) - -## Pseudo-code - -```C -entier pgcd(m, n) { - tmp_n = n - tmp_m = m - tant que (tmp_m ne divise pas tmp_n) { - tmp = tmp_n - tmp_n = tmp_m - tmp_m = tmp modulo tmp_m - } - retourne tmp_m -} -``` - -# Le code du PGCD de 2 nombres - -## Implémentez le pseudo-code et postez le code sur matrix (5min). - -. . . - -## Un corrigé possible - -```C -#include <stdio.h> -void main() { - int n = 90; - int m = 78; - printf("n = %d et m = %d\n", n, m); - int tmp_n = n; - int tmp_m = m; - while (tmp_n%tmp_m > 0) { - int tmp = tmp_n; - tmp_n = tmp_m; - tmp_m = tmp % tmp_m; - } - printf("Le pgcd de %d et %d est %d\n", n, m, tmp_m); -} -``` - -# Quelques algorithmes simples - -* Remplissage d'un tableau et recherche de la valeur minimal -* Anagrammes -* Palindromes -* Crible d'ératosthène - -. . . - -* Ces algorithme nécessitent d'utiliser des **tableaux**. - -# Collections: tableaux statiques - -\footnotesize - -* Objets de même type: leur nombre est **connu à la compilation**; -* Stockés contigüement en mémoire (très efficace); - - ```C - #define SIZE 10 - int entiers[] = {2, 1, 4, 5, 7}; // taille 5, initialisé - int tab[3]; // taille 3, non initialisé - float many_floats[SIZE]; // taille 10, non initialisé - ``` -* Les indices sont numérotés de `0` à `taille-1`; - - ```C - int premier = entier[0]; // premier = 2 - int dernier = entier[4]; // dernier = 7 - ``` -* Les tableaux sont **non-initialisés** par défaut; -* Les bornes ne sont **jamais** vérifiées. - - ```C - int indetermine_1 = tab[1]; // undefined behavior - int indetermine_2 = entiers[5]; // UB - ``` - -# Remarques - -* Depuis `C99` possibilité d'avoir des tableaux dont la taille est *inconnue à - la compilation*; - - ```C - int size; - scanf("%d", &size); - char string[size]; - ``` - - . . . - -* Considéré comme une mauvaise pratique: que se passe-t-il si `size == 1e9`? -* On préfère utiliser l'allocation **dynamique** de mémoire pour ce genre de - cas-là (spoiler du futur du cours). - -# Initialisation - -* Les variables ne sont **jamais** initialisées en `C` par défaut. -* Question: Que contient le tableau suivant? - - ```C - double tab[4]; - ``` - -. . . - -* Réponse: On en sait absolument rien! -* Comment initialiser un tableau? - -. . . - -```C -#define SIZE 10 -double tab[SIZE]; -for (int i = 0; i < SIZE; ++i) { - tab[i] = rand() / (double)RAND_MAX * 10.0 - 5.0; - // tab[i] contient un double dans [-5;5] -} -``` - -# Recherche du minimum dans un tableau (1/2) - -## Problématique - -Trouver la valeur minimale contenue dans un tableau et l'indice de l'élément le plus petit. - -## Écrire un pseudo-code résolvant ce problème (groupe de 3), 2min - -. . . - -```C -index = 0 -min = tab[0] -pour i de 1 à SIZE - 1 - si min > tab[i] - min = tab[i] - index = i -``` - -# Recherche du minimum dans un tableau (2/2) - -## Implémenter ce bout de code en C (groupe de 3), 4min - -. . . - -```C -int index = 0; -float min = tab[0]; -for (int i = 1; i < SIZE; ++i) { - if min > tab[i] { - min = tab[i]; - index = i; - } -} -``` - -# Tri par sélection (1/2) - -## Problématique - -Trier un tableau par ordre croissant. - -## Idée d'algorithme - -```C -ind = 0 -tant que (ind < SIZE-1) - Trouver le minimum du tableau, tab_min[ind:SIZE]. - Échanger tab_min avec tab[ind] - ind += 1 -``` - -# Tri par sélection (2/2) - -## Implémentation par groupe de 3 - -* Initialiser aléatoirement un tableau de `double` de taille 10; -* Afficher le tableau; -* Trier par sélection le tableau; -* Afficher le résultat trié; -* Vérifier algorithmiquement que le résultat est bien trié. - diff --git a/slides/cours_4.md b/slides/cours_4.md deleted file mode 100644 index 7731096..0000000 --- a/slides/cours_4.md +++ /dev/null @@ -1,851 +0,0 @@ ---- -title: "Introduction aux algorithmes" -date: "2022-10-19" ---- - -# Rappel - -## Quel est l'algorithme du tri par sélection? - -. . . - -1. Soit un tableau d'entiers, `tab[0:SIZE-1]` et `i=0`. -2. Trouver l'indice, `j`, de `tab[i:SIZE-2]` où la valeur est minimale. -3. Échanger `tab[i]` et `tab[j]`. -4. `i+=1` et revenir à 2, tant que `j < SIZE-2`. - -# Un type de tableau particulier - -## Les chaînes de caractères - -```C -string = tableau + char + magie noire -``` - -# Le type `char`{.C} - -- Le type `char`{.C} est utilisé pour représenter un caractère. -- C'est un entier 8 bits signé. -- En particulier: - - Écrire - - ```C - char c = 'A'; - ``` - - Est équivalent à : - - ```C - char c = 65; - ``` -- Les fonctions d'affichage interprètent le nombre comme sa valeur ASCII. - -# Chaînes de caractères (strings) - -- Chaîne de caractère `==` tableau de caractères **terminé par la valeur** `'\0'`{.C} ou `0`{.C}. - -## Exemple - -```C -char *str = "HELLO !"; -char str[] = "HELLO !"; -``` - -Est représenté par - -| `char` | `H` | `E` | `L` | `L` | `O` | | `!` | `\0`| -|---------|------|------|------|------|------|------|------|-----| -| `ASCII` | `72` | `69` | `76` | `76` | `79` | `32` | `33` | `0` | - -. . . - -## A quoi sert le `\0`? - -. . . - -Permet de connaître la fin de la chaîne de caractères (pas le cas des autres -sortes de tableaux). - -# Syntaxe - -```C -char name[5]; -name[0] = 'P'; // = 70; -name[1] = 'a'; // = 97; -name[2] = 'u'; // = 117; -name[3] = 'l'; // = 108; -name[4] = '\0'; // = 0; -char name[] = {'P', 'a', 'u', 'l', '\0'}; -char name[5] = "Paul"; -char name[] = "Paul"; -char name[100] = "Paul is not 100 characters long."; -``` - -# Fonctions - -- Il existe une grande quantités de fonction pour la manipulation de chaînes de caractères dans `string.h`. -- Fonctions principales: - - ```C - // longueur de la chaîne (sans le \0) - size_t strlen(char *str); - // copie jusqu'à un \0 - char *strcpy(char *dest, const char *src); - // copie len char - char *strncpy(char *dest, const char *src, size_t len); - // compare len chars - int strncmp(char *str1, char *str2, size_t len); - // compare jusqu'à un \0 - int strcmp(char *str1, char *str2); - ``` - -- Pour avoir la liste complète: `man string`. - -. . . - -## Quels problèmes peuvent se produire avec `strlen`, `strcpy`, `strcmp`? - -# Les anagrammes - -## Définition - -Deux mots sont des anagrammes l'un de l'autre quand ils contiennent les mêmes -lettres mais dans un ordre différent. - -## Exemple - -| `t` | `u` | `t` | `u` | `t` | `\0` | ` ` | ` ` | -|------|------|------|------|------|------|------|-----| -| `t` | `u` | `t` | `t` | `u` | `\0` | ` ` | ` ` | - - -## Problème: Trouvez un algorithme pour déterminer si deux mots sont des anagrammes. - -# Les anagrammes - -## Il suffit de: - -1. Trier les deux mots. -2. Vérifier s'ils contiennent les mêmes lettres. - -## Implémentation ensemble - -```C -int main() { // pseudo C - tri(mot1); - tri(mot2); - if egalite(mot1, mot2) { - // anagrammes - } else { - // pas anagrammes - } -} -``` - -<!-- TODO: Live implémentation hors des cours? --> - -# Les palindromes - -Mot qui se lit pareil de droite à gauche que de gauche à droite: - -. . . - -* rotor, kayak, ressasser, ... - -## Problème: proposer un algorithme pour détecter un palindrome - -. . . - -## Solution 1 - -```C -while (first_index < last_index { - if (mot[first_index] != mot [last_index]) { - return false; - } - first_index += 1; - last_index -= 1; -} -return true; -``` - -. . . - -## Solution 2 - -```C -mot_tmp = revert(mot); -return mot == mot_tmp; -``` - -# Crible d'Ératosthène - -Algorithme de génération de nombres premiers. - -## Exercice - -* À l'aide d'un tableau de booléens, -* Générer les nombres premiers plus petits qu'un nombre $N$ - -## Pseudo-code - -* Par groupe de trois, réfléchir à un algorithme. - -## Programme en C - -* Implémenter l'algorithme et le poster sur le salon `Element`. - -# Crible d'Ératosthène: solution - -\footnotesize - -```C -#include <stdio.h> -#include <stdbool.h> -#define SIZE 51 -int main() { - bool tab[SIZE]; - for (int i=0;i<SIZE;i++) { - tab[i] = true; - } - for (int i = 2; i < SIZE; i++) { - if (tab[i]) { - printf("%d ", i); - int j = i; - while (j < SIZE) { - j += i; - tab[j] = false; - } - } - } - printf("\n"); -} -``` - - -# Réusinage de code (refactoring) - -## Le réusinage est? - -. . . - -* le processus de restructuration d'un programme: - * en modifiant son design, - * en modifiant sa structure, - * en modifiant ses algorithmes - * mais en **conservant ses fonctionalités**. - -. . . - -## Avantages? - -. . . - -* Amélioration de la lisibilité, -* Amélioration de la maintenabilité, -* Réduction de la complexité. - -. . . - -## "Make it work, make it nice, make it fast", Kent Beck. - -. . . - -## Exercice: - -* Réusiner le code se trouvant sur - [Cyberlearn](https://cyberlearn.hes-so.ch/mod/resource/view.php?id=1627712). - -# Tableau à deux dimensions (1/4) - -## Mais qu'est-ce donc? - -. . . - -* Un tableau où chaque cellule est un tableau. - -## Quels cas d'utilisation? - -. . . - -* Tableau à double entrée; -* Image; -* Écran (pixels); -* Matrice (mathématique); - -# Tableau à deux dimensions (2/4) - -## Exemple: tableau à 3 lignes et 4 colonnes d'entiers - -+-----------+-----+-----+-----+-----+ -| `indices` | `0` | `1` | `2` | `3` | -+-----------+-----+-----+-----+-----+ -| `0` | `7` | `4` | `7` | `3` | -+-----------+-----+-----+-----+-----+ -| `1` | `2` | `2` | `9` | `2` | -+-----------+-----+-----+-----+-----+ -| `2` | `4` | `8` | `8` | `9` | -+-----------+-----+-----+-----+-----+ - -## Syntaxe - -```C -int tab[3][4]; // déclaration d'un tableau 4x3 -tab[2][1]; // accès à la case 2, 1 -tab[2][1] = 14; // assignation de 14 à la position 2, 1 -``` - -# Tableau à deux dimensions (3/4) - -## Exercice: déclarer et initialiser aléatoirement un tableau `50x100` - -. . . - -```C -#define NX 50 -#define NY 100 -int tab[NX][NY]; -for (int i = 0; i < NX; ++i) { - for (int j = 0; j < NY; ++j) { - tab[i][j] = rand() % 256; // 256 niveaux de gris - } -} -``` - -## Exercice: afficher le tableau - -. . . - -```C -for (int i = 0; i < NX; ++i) { - for (int j = 0; j < NY; ++j) { - printf("%d ", tab[i][j]); - } - printf("\n"); -} -``` - -# Tableau à deux dimensions (4/4) - -## Attention - -* Les éléments ne sont **jamais** initialisés. -* Les bornes ne sont **jamais** vérifiées. - - ```C - int tab[3][2] = { {1, 2}, {3, 4}, {5, 6} }; - printf("%d\n", tab[2][1]); // affiche? - printf("%d\n", tab[10][9]); // affiche? - printf("%d\n", tab[3][1]); // affiche? - ``` - -# La couverture de la reine - -* Aux échecs la reine peut se déplacer horizontalement et verticalement -* Pour un échiquier `5x6`, elle *couvre* les cases comme ci-dessous - -+-----+-----+-----+-----+-----+-----+-----+ -| ` ` | `0` | `1` | `2` | `3` | `4` | `5` | -+-----+-----+-----+-----+-----+-----+-----+ -| `0` | `*` | ` ` | `*` | ` ` | `*` | ` ` | -+-----+-----+-----+-----+-----+-----+-----+ -| `1` | ` ` | `*` | `*` | `*` | ` ` | ` ` | -+-----+-----+-----+-----+-----+-----+-----+ -| `2` | `*` | `*` | `R` | `*` | `*` | `*` | -+-----+-----+-----+-----+-----+-----+-----+ -| `3` | ` ` | `*` | `*` | `*` | ` ` | ` ` | -+-----+-----+-----+-----+-----+-----+-----+ -| `4` | `*` | ` ` | `*` | ` ` | `*` | ` ` | -+-----+-----+-----+-----+-----+-----+-----+ - -## Exercice - -* En utilisant les conditions, les tableaux à deux dimensions, et des - `char` uniquement. -* Implémenter un programme qui demande à l'utilisateur d'entrer les - coordonnées de la reine et affiche un tableau comme ci-dessus pour un - échiquier `8x8`. - -## Poster le résultat sur `Element` - -# Types énumérés (1/2) - -* Un **type énuméré**: ensemble de *variantes* (valeurs constantes). -* En `C` les variantes sont des entiers numérotés à partir de 0. - - ```C - enum days { - monday, tuesday, wednesday, - thursday, friday, saturday, sunday - }; - ``` -* On peut aussi donner des valeurs "custom" - - ```C - enum days { - monday = 2, tuesday = 8, wednesday = -2, - thursday = 1, friday = 3, saturday = 12, sunday = 9 - }; - ``` -* S'utilise comme un type standard et un entier - - ```C - enum days d = monday; - (d + 2) == tuesday + tuesday; // true - ``` - -# Types énumérés (2/2) - -* Très utile dans les `switch ... case`{.C} - - ```C - enum days d = monday; - switch (d) { - case monday: - // trucs - break; - case tuesday: - printf("0 ou 1\n"); - break; - } - ``` -* Le compilateur vous prévient qu'il en manque! - -# Utilisation des types énumérés - -## Réusiner votre couverture de la reine avec des `enum` - -# Représentation des nombres (1/2) - -* Le nombre `247`. - -## Nombres décimaux: Les nombres en base 10 - -+--------+--------+--------+ -| $10^2$ | $10^1$ | $10^0$ | -+--------+--------+--------+ -| `2` | `4` | `7` | -+--------+--------+--------+ - -$$ -247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0. -$$ - -# Représentation des nombres (2/2) - -* Le nombre `11110111`. - -## Nombres binaires: Les nombres en base 2 - -+-------+-------+-------+-------+-------+-------+-------+-------+ -| $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ | -+-------+-------+-------+-------+-------+-------+-------+-------+ -| `1` | `1` | `1` | `1` | `0` | `1` | `1` | `1` | -+-------+-------+-------+-------+-------+-------+-------+-------+ - -$$ -1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2 -+1\cdot 2^1 +1\cdot 2^0 -$$ - -. . . - -$$ -= 247. -$$ - -# Conversion de décimal à binaire (1/2) - -## Convertir 11 en binaire? - -. . . - -* On décompose en puissances de 2 en partant de la plus grande possible - - ``` - 11 / 8 = 1, 11 % 8 = 3 - 3 / 4 = 0, 3 % 4 = 3 - 3 / 2 = 1, 3 % 2 = 1 - 1 / 1 = 1, 1 % 1 = 0 - ``` -* On a donc - - $$ - 1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot - 2^0=11. - $$ - -# Conversion de décimal à binaire (2/2) - -## Convertir un nombre arbitraire en binaire: 247? - -* Par groupe établir un algorithme. - -. . . - -## Algorithme - -1. Initialisation - - ```C - num = 247 - while (2^N < num) { - N += 1 - } - ``` - -. . . - -2. Boucle - - ```C - while (N >= 0) { - bit = num / 2^N - num = num % 2^N - N += 1 - } - ``` - -# Les additions en binaire - -Que donne l'addition `1101` avec `0110`? - -* L'addition est la même que dans le système décimal - - ``` - 1101 8+4+0+1 = 13 - + 0110 + 0+4+2+0 = 6 - ------- ----------------- - 10011 16+0+0+2+1 = 19 - ``` -* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits). -* Que se passe-t-il donc ici? - -. . . - -## Dépassement de capacité: le nombre est "tronqué" - -* `10011 (19) -> 0011 (3)`. -* On fait "le tour"." - -# Entier non-signés minimal/maximal - -* Quel est l'entier non-signé maximal représentable avec 4 bit? - -. . . - -$$ -(1111)_2 = 8+4+2+1 = 15 -$$ - -* Quel est l'entier non-signé minimal représentable avec 4 bit? - -. . . - -$$ -(0000)_2 = 0+0+0+0 = 0 -$$ - -* Quel est l'entier non-signé min/max représentable avec N bit? - -. . . - -$$ -0\mbox{ et }2^N-1. -$$ - -* Donc `uint32_t?` maximal est? - -. . . - -$$ -4294967295 -$$ - - -# Les multiplications en binaire (1/2) - -Que donne la multiplication de `1101` avec `0110`? - -* L'addition est la même que dans le système décimal - - ``` - 1101 13 - * 0110 * 6 - --------- -------------- - 0000 78 - 11010 - 110100 - + 0000000 - --------- -------------- - 1001110 64+0+0+8+4+2+0 - ``` - -# Les multiplications en binaire (2/2) - -## Que fait la multiplication par 2? - -. . . - -* Décalage de un bit vers la gauche! - - ``` - 0110 - * 0010 - --------- - 0000 - + 01100 - --------- - 01100 - ``` - -. . . - -## Que fait la multiplication par $2^N$? - -. . . - -* Décalade de $N$ bits vers la gauche! - -# Entiers signés (1/2) - -Pas de nombres négatifs encore... - -## Comment faire? - -. . . - -## Solution naïve: - -* On ajoute un bit de signe (le bit de poids fort): - - ``` - 00000010: +2 - 10000010: -2 - ``` - -## Problèmes? - -. . . - -* Il y a deux zéros (pas trop grave): `10000000` et `00000000` -* Les additions différentes que pour les non-signés (très grave) - - ``` - 00000010 2 - + 10000100 + -4 - ---------- ---- - 10000110 = -6 != -2 - ``` - -# Entiers signés (2/2) - -## Beaucoup mieux - -* Complément à un: - * on inverse tous les bits: `1001 => 0110`. - -## Encore un peu mieux - -* Complément à deux: - * on inverse tous les bits, - * on ajoute 1 (on ignore les dépassements). - -. . . - -* Comment écrit-on `-4` en 8 bits? - -. . . - -``` - 4 = 00000100 - ________ - -4 => 00000100 - - 11111011 - + 00000001 - ---------- - 11111100 -``` - -# Le complément à 2 (1/2) - -## Questions: - -* Comment on écrit `+0` et `-0`? -* Comment calcule-t-on `2 + (-4)`? -* Quel est le complément à 2 de `1000 0000`? - -. . . - -## Réponses - -* Comment on écrit `+0` et `-0`? - - ``` - +0 = 00000000 - -0 = 11111111 + 00000001 = 100000000 => 00000000 - ``` -* Comment calcule-t-on `2 + (-4)`? - - ``` - 00000010 2 - + 11111100 + -4 - ---------- ----- - 11111110 -2 - ``` -* En effet - - ``` - 11111110 => 00000001 + 00000001 = 00000010 = 2. - ``` - -# Le complément à 2 (1/2) - -## Quels sont les entiers représentables en 8 bits? - -. . . - -``` -01111111 => 127 -10000000 => -128 // par définition -``` - -## Quels sont les entiers représentables sur $N$ bits? - -. . . - -$$ --2^{N-1} ... 2^{N-1}-1. -$$ - -## Remarque: dépassement de capacité en `C` - -* Comportement indéfini! - - -<!-- # TODO -- - -<!-- ## Entiers, entiers non-signés --> - -<!-- ## Complément à 1, 2 --> - -<!-- ## Nombres à virgule flottante, simple/double précision --> - -# Types composés: `struct`{.C} (1/6) - -## Fractions - -* Numérateur: `int num`; -* Dénominateur: `int denom`. - -## Addition - -```C -int num1 = 1, denom1 = 2; -int num2 = 1, denom2 = 3; -int num3 = num1 * denom2 + num2 * denom1; -int denom3 = denom1 * denom2; -``` - -## Pas super pratique.... - -# Types composés: `struct`{.C} (2/6) - -## On peut faire mieux - -* Plusieurs variables qu'on aimerait regrouper dans un seul type: `struct`{.C}. - -```C -struct fraction { // déclaration du type - int32_t num, denom; -} - -struct fraction frac; // déclaration de frac -``` - -# Types composés: `struct`{.C} (3/6) - -## Simplifications - -- `typedef`{.C} permet de définir un nouveau type. - - ```C - typedef unsinged int uint; - typedef struct fraction fraction_t; - typedef struct fraction { - int32_t num, denom; - } fraction_t; - ``` -- L'initialisation peut aussi se faire avec - - ```C - fraction_t frac = {1, -2}; // num = 1, denom = -2 - fraction_t frac = {.denom = 1, .num = -2}; - fraction_t frac = {.denom = 1}; // argl! .num non initialisé - fraction_t frac2 = frac; // copie - ``` - -# Types composés: `struct`{.C} (4/6) - -## Pointeurs - -- Comme pour tout type, on peut avoir des pointeurs vers un `struct`{.C}. -- Les champs sont accessible avec le sélecteur `->`{.C} - - ```C - fraction_t *frac; // on crée un pointeur - frac->num = 1; // seg fault... - frac->denom = -1; // mémoire pas allouée. - ``` - -{width=50%} - -# Types composés: `struct`{.C} (5/6) - -## Initialisation - -- Avec le passage par **référence** on peut modifier un struct *en place*. -- Les champs sont accessible avec le sélecteur `->`{.C} - - ```C - void fraction_init(fraction_t *frac, - int32_t re, int32_t im) - { - // frac a déjà été allouée - frac->num = frac; - frac->denom = denom; - } - int main() { - fraction_t frac; // on alloue une fraction - fraction_init(&frac, 2, -1); // on l'initialise - } - ``` - -# Types composés: `struct`{.C} (6/6) - -## Initialisation version copie - -* On peut allouer une fraction, l'initialiser et le retourner. -* La valeur retournée peut être copiée dans une nouvelle structure. - - ```C - fraction_t fraction_create(int32_t re, int32_t im) { - fraction_t frac; - frac.num = re; - frac.denom = im; - return frac; - } - int main() { - // on crée une fraction et on l'initialise - // en copiant la fraction créé par fraction_create - // deux allocation et une copie - fraction_t frac = fraction_create(2, -1); - } - ``` - -<!-- # TODO jusqu'aux vacances --> - -<!-- * Refactorisation --> -<!-- * Tris et complexité --> -<!-- * Récursivité --> diff --git a/slides/cours_5.md b/slides/cours_5.md deleted file mode 100644 index da21610..0000000 --- a/slides/cours_5.md +++ /dev/null @@ -1,834 +0,0 @@ ---- -title: "Représentation des nombres et récursivité" -date: "2022-11-02" ---- - -# Types énumérés (1/2) - -* Un **type énuméré**: ensemble de *variantes* (valeurs constantes). -* En `C` les variantes sont des entiers numérotés à partir de 0. - - ```C - enum days { - monday, tuesday, wednesday, - thursday, friday, saturday, sunday - }; - ``` -* On peut aussi donner des valeurs "custom" - - ```C - enum days { - monday = 2, tuesday = 8, wednesday = -2, - thursday = 1, friday = 3, saturday = 12, sunday = 9 - }; - ``` -* S'utilise comme un type standard et un entier - - ```C - enum days d = monday; - (d + 2) == monday + monday; // true - ``` - -# Types énumérés (2/2) - -* Très utile dans les `switch ... case`{.C} - - ```C - enum days d = monday; - switch (d) { - case monday: - // trucs - break; - case tuesday: - printf("0 ou 1\n"); - break; - } - ``` -* Le compilateur vous prévient qu'il en manque! - -# Utilisation des types énumérés - -## Réusiner votre couverture de la reine avec des `enum` - -A faire à la maison comme exercice! - -# Représentation des nombres (1/2) - -* Le nombre `247`. - -## Nombres décimaux: Les nombres en base 10 - -+--------+--------+--------+ -| $10^2$ | $10^1$ | $10^0$ | -+--------+--------+--------+ -| `2` | `4` | `7` | -+--------+--------+--------+ - -$$ -247 = 2\cdot 10^2 + 4\cdot 10^1 + 7\cdot 10^0. -$$ - -# Représentation des nombres (2/2) - -* Le nombre `11110111`. - -## Nombres binaires: Les nombres en base 2 - -+-------+-------+-------+-------+-------+-------+-------+-------+ -| $2^7$ | $2^6$ | $2^5$ | $2^4$ | $2^3$ | $2^2$ | $2^1$ | $2^0$ | -+-------+-------+-------+-------+-------+-------+-------+-------+ -| `1` | `1` | `1` | `1` | `0` | `1` | `1` | `1` | -+-------+-------+-------+-------+-------+-------+-------+-------+ - -$$ -1\cdot 2^7 + 1\cdot 2^6 +1\cdot 2^5 +1\cdot 2^4 +0\cdot 2^3 +1\cdot 2^2 -+1\cdot 2^1 +1\cdot 2^0 -$$ - -. . . - -$$ -= 247. -$$ - -# Conversion de décimal à binaire (1/2) - -## Convertir 11 en binaire? - -. . . - -* On décompose en puissances de 2 en partant de la plus grande possible - - ``` - 11 / 8 = 1, 11 % 8 = 3 - 3 / 4 = 0, 3 % 4 = 3 - 3 / 2 = 1, 3 % 2 = 1 - 1 / 1 = 1, 1 % 1 = 0 - ``` -* On a donc - - $$ - 1011 \Rightarrow 1\cdot 2^3 + 0\cdot 2^2 + 1\cdot 2^1 + 1\cdot - 2^0=11. - $$ - -# Conversion de décimal à binaire (2/2) - -## Convertir un nombre arbitraire en binaire: 247? - -* Par groupe établir un algorithme. - -. . . - -## Algorithme - -1. Initialisation - - ```C - num = 247 - tant que (2^N < num) { - N += 1 - } - ``` - -. . . - -2. Boucle - - ```C - tant que (N >= 0) { - bit = num / 2^N - num = num % 2^N - N -= 1 - } - ``` - -# Les additions en binaire - -Que donne l'addition `1101` avec `0110`? - -* L'addition est la même que dans le système décimal - - ``` - 1101 8+4+0+1 = 13 - + 0110 + 0+4+2+0 = 6 - ------- ----------------- - 10011 16+0+0+2+1 = 19 - ``` -* Les entiers sur un ordinateur ont une précision **fixée** (ici 4 bits). -* Que se passe-t-il donc ici? - -. . . - -## Dépassement de capacité: le nombre est "tronqué" - -* `10011 (19) -> 0011 (3)`. -* On fait "le tour"." - -# Entier non-signés minimal/maximal - -* Quel est l'entier non-signé maximal représentable avec 4 bit? - -. . . - -$$ -(1111)_2 = 8+4+2+1 = 15 -$$ - -* Quel est l'entier non-signé minimal représentable avec 4 bit? - -. . . - -$$ -(0000)_2 = 0+0+0+0 = 0 -$$ - -* Quel est l'entier non-signé min/max représentable avec N bit? - -. . . - -$$ -0\mbox{ et }2^N-1. -$$ - -* Donc `uint32_t?` maximal est? - -. . . - -$$ -2^{32}-1=4'294'967'295 -$$ - - -# Les multiplications en binaire (1/2) - -Que donne la multiplication de `1101` avec `0110`? - -* La mutliplication est la même que dans le système décimal - - ``` - 1101 13 - * 0110 * 6 - --------- -------------- - 0000 78 - 11010 - 110100 - + 0000000 - --------- -------------- - 1001110 64+0+0+8+4+2+0 - ``` - -# Les multiplications en binaire (2/2) - -## Que fait la multiplication par 2? - -. . . - -* Décalage de un bit vers la gauche! - - ``` - 0110 - * 0010 - --------- - 0000 - + 01100 - --------- - 01100 - ``` - -. . . - -## Que fait la multiplication par $2^N$? - -. . . - -* Décalade de $N$ bits vers la gauche! - -# Entiers signés (1/2) - -Pas de nombres négatifs encore... - -## Comment faire? - -. . . - -## Solution naïve: - -* On ajoute un bit de signe (le bit de poids fort): - - ``` - 00000010: +2 - 10000010: -2 - ``` - -## Problèmes? - -. . . - -* Il y a deux zéros (pas trop grave): `10000000` et `00000000` -* Les additions différentes que pour les non-signés (très grave) - - ``` - 00000010 2 - + 10000100 + -4 - ---------- ---- - 10000110 = -6 != -2 - ``` - -# Entiers signés (2/2) - -## Beaucoup mieux - -* Complément à un: - * on inverse tous les bits: `1001 => 0110`. - -## Encore un peu mieux - -* Complément à deux: - * on inverse tous les bits, - * on ajoute 1 (on ignore les dépassements). - -. . . - -* Comment écrit-on `-4` en 8 bits? - -. . . - -``` - 4 = 00000100 - ________ - -4 => 00000100 - - 11111011 - + 00000001 - ---------- - 11111100 -``` - -# Le complément à 2 (1/2) - -## Questions: - -* Comment on écrit `+0` et `-0`? -* Comment calcule-t-on `2 + (-4)`? -* Quel est le complément à 2 de `1000 0000`? - -. . . - -## Réponses - -* Comment on écrit `+0` et `-0`? - - ``` - +0 = 00000000 - -0 = 11111111 + 00000001 = 100000000 => 00000000 - ``` -* Comment calcule-t-on `2 + (-4)`? - - ``` - 00000010 2 - + 11111100 + -4 - ---------- ----- - 11111110 -2 - ``` -* En effet - - ``` - 11111110 => 00000001 + 00000001 = 00000010 = 2. - ``` - -# Le complément à 2 (2/2) - -## Quels sont les entiers représentables en 8 bits? - -. . . - -``` -01111111 => 127 -10000000 => -128 // par définition -``` - -## Quels sont les entiers représentables sur $N$ bits? - -. . . - -$$ --2^{N-1} ... 2^{N-1}-1. -$$ - -## Remarque: dépassement de capacité en `C` - -* Comportement indéfini! - - -# Nombres à virgule (1/3) - -## Comment manipuler des nombres à virgule? - -$$ -0.1 + 0.2 = 0.3. -$$ - -Facile non? - -. . . - -## Et ça? - -```C -#include <stdio.h> -#include <stdlib.h> -int main(int argc, char *argv[]) { - float a = atof(argv[1]); - float b = atof(argv[2]); - printf("%.10f\n", (double)(a + b)); -} -``` - -. . . - -## Que se passe-t-il donc? - -# Nombres à virgule (2/3) - -## Nombres à virgule fixe - -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ -| $2^3$ | $2^2$ | $2^1$ | $2^0$ | `.` | $2^{-1}$ | $2^{-2}$ | $2^{-3}$ | $2^{-4}$ | -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ -| `1` | `0` | `1` | `0` | `.` | `0` | `1` | `0` | `1` | -+-------+-------+-------+-------+-----+----------+----------+----------+----------+ - -## Qu'est-ce ça donne en décimal? - -. . . - -$$ -2^3+2^1+\frac{1}{2^2}+\frac{1}{2^4} = 8+2+0.5+0.0625=10.5625. -$$ - -## Limites de cette représentation? - -. . . - - -* Tous les nombres `> 16`. -* Tous les nombres `< 0.0625`. -* Tous les nombres dont la décimale est pas un multiple de `0.0625`. - -# Nombres à virgule (3/3) - -## Nombres à virgule fixe - -* Nombres de $0=0000.0000$ à $15.9375=1111.1111$. -* Beaucoup de "trous" (au moins $0.0625$) entre deux nombres. - -## Solution partielle? - -. . . - -* Rajouter des bits. -* Bouger la virgule. - -# Nombres à virgule flottante (1/2) - -## Notation scientifique - -* Les nombres sont représentés en terme: - * Une mantisse - * Une base - * Un exposant - -$$ -\underbrace{22.1214}_{\mbox{nombre}}=\underbrace{221214}_{\mbox{mantisse}}\cdot -{\underbrace{10}_{\mbox{base}}}{\overbrace{^{-4}}^{\mbox{exp.}}}, -$$ - -. . . - -On peut donc séparer la représentation en 2: - -* La mantisse -* L'exposant - -# Nombres à virgule flottante (2/2) - -## Quel est l'avantage? - -. . . - -On peut représenter des nombres sur énormément d'ordres de grandeur avec un -nombre de bits fixés. - -## Différence fondamentale avec la virgule fixe? - -. . . - -La précision des nombres est **variable**: - -* On a uniquement un nombre de chiffres **significatifs**. -$$ -123456\cdot 10^{23}+ 123456\cdot 10^{-23}. -$$ - -## Quel inconvénient y a-t-il? - -. . . - -Ce mélange d'échelles entraîne un **perte de précision**. - -# Nombres à virgule flottante simple précision (1/4) - -Aussi appelés *IEEE 754 single-precision binary floating point*. - -](figs/Float_example_bare.svg) - -## Spécification - -* 1 bit de signe, -* 8 bits d'exposant, -* 23 bits de mantisse. - -$$ -(-1)^{b_{31}}\cdot 2^{(b_{30}b_{29}\dots b_{23})_{2}-127}\cdot (1.b_{22}b_{21}\dots b_{0})_{2}, -$$ - -## Calculer la valeur décimale du nombre ci-dessus - -# Nombres à virgule flottante simple précision (2/4) - -](figs/Float_example.svg) - -. . . - -\begin{align} -\mbox{exposant}&=\sum_{i=0}^7 b_{23+i}2^i=2^2+2^3+2^4+2^5+2^6=124-127,\\ -\mbox{mantisse}&=1+\sum_{i=1}^{23}b_{23-i}2^{-i}=1+2^{-2}=1.25,\\ -&\Rightarrow (-1)^0\cdot 2^{-3}\cdot 1.25=0.15625 -\end{align} - -# Nombres à virgule flottante simple précision (3/4) - -## Quel nombre ne peux pas être vraiment représenté? - -. . . - -## Zéro: exception pour l'exposant - -* Si l'exposant est `00000000` (zéro) -$$ -(-1)^{\mbox{sign}}\cdot 2^{-126}\cdot 0.\mbox{mantisse}, -$$ -* Sinon si l'exposant est `00000001` à `11111110` -$$ -\mbox{valeur normale}, -$$ -* Sinon `11111111` donne `NaN`. - -# Nombres à virgule flottante simple précision (4/4) - -## Quels sont les plus petits/grands nombres positifs représentables? - -. . . - -\begin{align} -0\ 0\dots0\ 0\dots01&=2^{-126}\cdot 2^{-23}=1.4...\cdot -10^{-45},\\ -0\ 1\dots10\ 1\dots1&=2^{127}\cdot (2-2^{-23})=3.4...\cdot -10^{38}. -\end{align} - -## Combien de chiffres significatifs en décimal? - -. . . - -* 24 bits ($23 + 1$) sont utiles pour la mantisse, soit $2^{24}-1$: - * La mantisse fait $\sim2^{24}\sim 10^7$, - * Ou encore $\sim \log_{10}(2^{24})\sim 7$. -* Environ **sept** chiffres significatifs. - -# Nombres à virgule flottante double précision (64bits) - -## Spécification - -* 1 bit de signe, -* 11 bits d'exposant, -* 52 bits de mantisse. - -. . . - -## Combien de chiffres significatifs? - -* La mantisse fait $\sim 2^{53}\sim10^{16}$, -* Ou encore $\sim \log_{10}(2^{53})\sim 16$, -* Environ **seize** chiffres significatifs. - -## Plus petit/plus grand nombre représentable? - -. . . - -* Plus petite mantisse et exposant: $\sim 2^{-52}\cdot 2^{-1022}\sim 4\cdot 10^{-324}$, -* Plus grande mantisse et exposant: $\sim 2\cdot 2^{1023}\sim \cdot 1.8\cdot 10^{308}$. - -# Précision finie (1/3) - -## Erreur de représentation - -* Les nombres réels ont potentiellement un **nombre infini** de décimales - * $1/3=0.\overline{3}$, - * $\pi=3.1415926535...$. -* Les nombres à virgule flottante peuvent en représenter qu'un **nombre - fini**. - * $1/3\cong 0.33333$, erreur $0.00000\overline{3}$. - * $\pi\cong3.14159$, erreur $0.0000026535...$. - -On rencontre donc des **erreurs de représentation** ou **erreurs -d'arrondi**. - -. . . - -## Et quand on calcule? - -* Avec deux chiffres significatifs -\begin{align} -&8.9+(0.02+0.04)=8.96=9.0,\\ -&(8.9+0.02)+0.04=8.9+0.04=8.9. -\end{align} - -. . . - -## Même pas associatif! - -# Précision finie (2/3) - -## Erreur de représentation virgule flottante - -$$ -(1.2)_{10} = 1.\overline{0011}\cdot 2^0\Rightarrow 0\ 01111111\ -00110011001100110011010. -$$ -Erreur d'arrondi dans les deux derniers bits et tout ceux qui viennent -ensuite -$$ -\varepsilon_2 = (00000000000000000000011)_2. -$$ -Ou en décimal -$$ -\varepsilon_{10} = 4.76837158203125\cdot 10^{-8}. -$$ - -# Précision finie (3/3) - -## Comment définir l'égalité de 2 nombres à virgule flottante? - -. . . - -Ou en d'autres termes, pour quel $\varepsilon>0$ (appelé `epsilon-machine`) on a -$$ -1+\varepsilon = 1, -$$ -pour un nombre à virgule flottante? - -. . . - -Pour un `float` (32 bits) la différence est à -$$ -2^{-23}=1.19\cdot 10^{-7}, -$$ -Soit la précision de la mantisse. - -## Comment le mesurer (par groupe)? - -. . . - -```C -float eps = 1.0; -while ((float)1.0 + (float)0.5 * eps != (float)1.0) { - eps = (float)0.5 * eps; -} -printf("eps = %g\n", eps); -``` - -# Erreurs d'arrondi - -Et jusqu'ici on a encore pas fait d'arithmétique! - -## Multiplication avec deux chiffres significatifs, décimal - -$$ -(1.1)_{10}\cdot (1.1)_{10}=(1.21)_{10}=(1.2)_{10}. -$$ -En continuant ce petit jeu: -$$ -\underbrace{1.1\cdot 1.1\cdots 1.1}_{\mbox{10 fois}}=2.0. -$$ -Alors qu'en réalité -$$ -1.1^{10}=2.5937... -$$ -Soit une erreur de près de 1/5e! - -. . . - -## Le même phénomène se produit (à plus petite échelle) avec les `float` ou `double`. - -# And now for something completely different - -\Huge La récursivité - -# Exemple de récursivité (1/2) - -## La factorielle - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -. . . - -## Que se passe-t-il quand on fait `factorial(4)`? - -. . . - -## On empile les appels - -+----------------+----------------+----------------+----------------+ -| | | | `factorial(1)` | -+----------------+----------------+----------------+----------------+ -| | | `factorial(2)` | `factorial(2)` | -+----------------+----------------+----------------+----------------+ -| | `factorial(3)` | `factorial(3)` | `factorial(3)` | -+----------------+----------------+----------------+----------------+ -| `factorial(4)` | `factorial(4)` | `factorial(4)` | `factorial(4)` | -+----------------+----------------+----------------+----------------+ - -# Exemple de récursivité (2/2) - -## La factorielle - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -. . . - -## Que se passe-t-il quand on fait `factorial(4)`? - -. . . - -## On dépile les calculs - -+----------------+----------------+----------------+----------------+ -| `1` | | | | -+----------------+----------------+----------------+----------------+ -| `factorial(2)` | `2 * 1 = 2` | | | -+----------------+----------------+----------------+----------------+ -| `factorial(3)` | `factorial(3)` | `3 * 2 = 6` | | -+----------------+----------------+----------------+----------------+ -| `factorial(4)` | `factorial(4)` | `factorial(4)` | `4 * 6 = 24` | -+----------------+----------------+----------------+----------------+ - -# La récursivité (1/4) - -## Formellement - -* Une condition de récursivité - qui *réduit* les cas successifs vers... -* Une condition d'arrêt - qui retourne un résultat - -## Pour la factorielle, qui est qui? - -```C -int factorial(int n) { - if (n > 1) { - return n * factorial(n - 1); - } else { - return 1; - } -} -``` - -# La récursivité (2/4) - -## Formellement - -* Une condition de récursivité - qui *réduit* les cas successifs vers... -* Une condition d'arrêt - qui retourne un résultat - -## Pour la factorielle, qui est qui? - -```C -int factorial(int n) { - if (n > 1) { // Condition de récursivité - return n * factorial(n - 1); - } else { // Condition d'arrêt - return 1; - } -} -``` - -# La récursivité (3/4) - -## Exercice: trouver l'$\varepsilon$-machine pour un `double` - -. . . - -Rappelez-vous vous l'avez fait en style **impératif** plus tôt. - -. . . - -```C -double epsilon_machine(double eps) { - if (1.0 + eps != 1.0) { - return epsilon_machine(eps / 2.0); - } else { - return eps; - } -} -``` - -# La récursivité (4/4) - -\footnotesize - -## Exercice: que fait ce code récursif? - -```C -void recurse(int n) { - printf("%d ", n % 2); - if (n / 2 != 0) { - recurse(n / 2); - } else { - printf("\n"); - } -} -recurse(13); -``` - -. . . - -```C -recurse(13): n = 13, n % 2 = 1, n / 2 = 6, - recurse(6): n = 6, n % 2 = 0, n / 2 = 3, - recurse(3): n = 3, n % 2 = 1, n / 2 = 1, - recurse(1): n = 1, n % 2 = 1, n / 2 = 0. - -// affiche: 1 1 0 1 -``` - -. . . - -Affiche la représentation binaire d'un nombre! diff --git a/slides/cours_6.md b/slides/cours_6.md deleted file mode 100644 index fcb044d..0000000 --- a/slides/cours_6.md +++ /dev/null @@ -1,488 +0,0 @@ ---- -title: "Récursivité et complexité" -date: "2022-11-09" ---- - -# La récursivité (1/2) - -* Code récursif - - ```C - int factorial(int n) { - if (n > 1) { // Condition de récursivité - return n * factorial(n - 1); - } else { // Condition d'arrêt - return 1; - } - } -``` - -. . . - -* Code impératif - - ```C - int factorial(int n) { - int f = 1; - for (int i = 1; i < n; ++i) { - f *= i; - } - return f; - } - ``` - -# Exercice: réusinage et récursivité (1/4) - -## Réusiner le code du PGCD avec une fonction récursive - -## Étudier l'exécution - -```C -42 = 27 * 1 + 15 -27 = 15 * 1 + 12 -15 = 12 * 1 + 3 -12 = 3 * 4 + 0 -``` - -# Exercice: réusinage et récursivité (2/4) - -## Réusiner le code du PGCD avec une fonction récursive - -## Étudier l'exécution - -```C -42 = 27 * 1 + 15 | PGCD(42, 27) -27 = 15 * 1 + 12 | PGCD(27, 15) -15 = 12 * 1 + 3 | PGCD(15, 12) -12 = 3 * 4 + 0 | PGCD(12, 3) -``` - -# Exercice: réusinage et récursivité (3/4) - -## Réusiner le code du PGCD avec une fonction récursive - -## Étudier l'exécution - -```C -42 = 27 * 1 + 15 | PGCD(42, 27) -27 = 15 * 1 + 12 | PGCD(27, 15) -15 = 12 * 1 + 3 | PGCD(15, 12) -12 = 3 * 4 + 0 | PGCD(12, 3) -``` - -## Effectuer l'empilage - dépilage - -. . . - -```C -PGCD(12, 3) | 3 -PGCD(15, 12) | 3 -PGCD(27, 15) | 3 -PGCD(42, 27) | 3 -``` - -# Exercice: réusinage et récursivité (4/4) - -## Écrire le code - -. . . - -```C -int pgcd(int n, int m) { - if (n % m > 0) { - return pgcd(m, n % m); - } else { - return m; - } -} -``` - -# La suite de Fibonacci (1/2) - -## Règle - -$$ -\mathrm{Fib}(n) = \mathrm{Fib}(n-1) + \mathrm{Fib}(n-2),\quad -\mathrm{Fib}(0)=0,\quad \mathrm{Fib}(1)=1. -$$ - -## Exercice: écrire la fonction $\mathrm{Fib}$ en récursif et impératif - -. . . - -## En récursif (6 lignes) - -```C -int fib(int n) { - if (n > 1) { - return fib(n - 1) + fib(n - 2); - } - return n; -} -``` - -# La suite de Fibonacci (2/2) - -## Et en impératif (11 lignes) - -```C -int fib_imp(int n) { - int fib0 = 1; - int fib1 = 1; - int fib = n == 0 ? 0 : fib1; - for (int i = 2; i < n; ++i) { - fib = fib0 + fib1; - fib0 = fib1; - fib1 = fib; - } - return fib; -} -``` - -# Exponentiation rapide ou indienne (1/4) - -## But: Calculer $x^n$ - -* Quel est l'algorithmie le plus simple que vous pouvez imaginer? - -. . . - -```C -int pow(int x, int n) { - if (0 == n) { - return 1; - } - for (int i = 1; i < n; ++i) { - x = x * x; // x *= x - } - return x; -} -``` - -* Combien de multiplication et d'assignations en fonction de `n`? - -. . . - -* `n` assignations et `n` multiplications. - -# Exponentiation rapide ou indienne (2/4) - -* Proposez un algorithme naïf et récursif - -. . . - -```C -int pow(x, n) { - if (n != 0) { - return x * pow(x, n-1); - } else { - return 1; - } -} -``` - -# Exponentiation rapide ou indienne (3/4) - -## Exponentiation rapide ou indienne de $x^n$ - -* Écrivons $n=\sum_{i=0}^{d-1}b_i 2^i,\ b_i=\{0,1\}$ (écriture binaire sur $d$ bits, avec -$d\sim\log_2(n)$). -* -$$ -x^n={x^{2^0}}^{b_0}\cdot {x^{2^1}}^{b_1}\cdots {x^{2^{d-1}}}^{b_{d-1}}. -$$ -* On a besoin de $d$ calculs pour les $x^{2^i}$. -* On a besoin de $d$ calculs pour évaluer les produits de tous les termes. - -## Combien de calculs en terme de $n$? - -. . . - -* $n$ est représenté en binaire avec $d$ bits $\Rightarrow d\sim\log_2(n)$. -* il y a $2\log_2(n)\sim \log_2(n)$ calculs. - -# Exponentiation rapide ou indienne (4/4) - -## Le vrai algorithme - -* Si n est pair: calculer $\left(x^{n/2}\right)^2$, -* Si n est impair: calculer $x \cdot \left(x^{(n-1)/2}\right)^2$. - -## Exercice: écrire l'algorithme récursif correspondant - -. . . - -```C -double pow(double x, int n) { - if (1 == n) { - return x; - } else if (n % 2 == 0) { - return pow(x, n / 2) * pow(x, n/2); - } else { - return x * pow(x, (n-1)); - } -} -``` - - -# Efficacité d'un algorithmique - -Comment mesurer l'efficacité d'un algorithme? - -. . . - -* Mesurer le temps CPU, -* Mesurer le temps d'accès à la mémoire, -* Mesurer la place prise mémoire, - -. . . - -Dépendant du **matériel**, du **compilateur**, des **options de compilation**, etc! - -## Mesure du temps CPU - -```C -#include <time.h> -struct timespec tstart={0,0}, tend={0,0}; -clock_gettime(CLOCK_MONOTONIC, &tstart); -// some computation -clock_gettime(CLOCK_MONOTONIC, &tend); -printf("computation about %.5f seconds\n", - ((double)tend.tv_sec + 1e-9*tend.tv_nsec) - - ((double)tstart.tv_sec + 1e-9*tstart.tv_nsec)); -``` - -# Programme simple: mesure du temps CPU - -## Preuve sur un [petit exemple](../source_codes/complexity/sum.c) - -```bash -source_codes/complexity$ make bench -RUN ONCE -O0 -the computation took about 0.00836 seconds -RUN ONCE -O3 -the computation took about 0.00203 seconds -RUN THOUSAND TIMES -O0 -the computation took about 0.00363 seconds -RUN THOUSAND TIMES -O3 -the computation took about 0.00046 seconds -``` - -Et sur votre machine les résultats seront **différents**. - -. . . - -## Conclusion - -* Nécessité d'avoir une mesure indépendante du/de la - matériel/compilateur/façon de mesurer/météo. - -# Analyse de complexité algorithmique (1/4) - -* On analyse le **temps** pris par un algorithme en fonction de la **taille de - l'entrée**. - -## Exemple: recherche d'un élément dans une liste triée de taille N - -```C -int sorted_list[N]; -bool in_list = is_present(N, sorted_list, elem); -``` - -* Plus `N` est grand, plus l'algorithme prend de temps sauf si... - -. . . - -* l'élément est le premier de la liste (ou à une position toujours la même). -* ce genre de cas pathologique ne rentre pas en ligne de compte. - -# Analyse de complexité algorithmique (2/4) - -## Recherche linéaire - -```C -bool is_present(int n, int tab[], int elem) { - for (int i = 0; i < n; ++i) { - if (tab[i] == elem) { - return true; - } else if (elem < tab[i]) { - return false; - } - } - return false; -} -``` - -* Dans le **meilleurs des cas** il faut `1` comparaison. -* Dans le **pire des cas** (élément absent p.ex.) il faut `n` comparaisons. - -. . . - -La **complexité algorithmique** est proportionnelle à `N`: on double la taille -du tableau $\Rightarrow$ on double le temps pris par l'algorithme. - -# Analyse de complexité algorithmique (3/4) - -## Recherche dichotomique - -```C -bool is_present_binary_search(int n, int tab[], int elem) { - int left = 0; - int right = n - 1; - while (left <= right) { - int mid = (right + left) / 2; - if (tab[mid] < elem) { - left = mid + 1; - } else if (tab[mid] > elem) { - right = mid - 1; - } else { - return true; - } - } - return false; -} -``` - -# Analyse de complexité algorithmique (4/4) - -## Recherche dichotomique - -](figs/Binary_search_complexity.svg){width=80%} - -. . . - -* Dans le **meilleurs de cas** il faut `1` comparaison. -* Dans le **pire des cas** il faut $\log_2(N)+1$ comparaisons - -. . . - -## Linéaire vs dichotomique - -* $N$ vs $\log_2(N)$ comparaisons logiques. -* Pour $N=1000000$: `1000000` vs `21` comparaisons. - -# Notation pour la complexité - -## Constante de proportionnalité - -* Pour la recherche linéaire ou dichotomique, on a des algorithmes qui sont $\sim N$ ou $\sim \log_2(N)$ -* Qu'est-ce que cela veut dire? - -. . . - -* Temps de calcul est $t=C\cdot N$ (où $C$ est le temps pris pour une comparaisons sur une machine/compilateur donné) -* La complexité ne dépend pas de $C$. - -## Le $\mathcal{O}$ de Leibnitz - -* Pour noter la complexité d'un algorithme on utilise le symbole $\mathcal{O}$ (ou "grand Ô de"). -* Les complexités les plus couramment rencontrées sont - -. . . - -$$ -\mathcal{O}(1),\quad \mathcal{O}(\log(N)),\quad \mathcal{O}(N),\quad -\mathcal{O}(\log(N)\cdot N), \quad \mathcal{O}(N^2), \quad -\mathcal{O}(N^3). -$$ - -# Ordres de grandeur - -\begin{table}[!h] -\begin{center} -\caption{Valeurs approximatives de quelques fonctions usuelles de complexité.} -\medskip -\begin{tabular}{|c|c|c|c|c|} -\hline -$\log_2(N)$ & $\sqrt{N}$ & $N$ & $N\log_2(N)$ & $N^2$ \\ -\hline\hline -$3$ & $3$ & $10$ & $30$ & $10^2$ \\ -\hline -$6$ & $10$ & $10^2$ & $6\cdot 10^2$ & $10^4$ \\ -\hline -$9$ & $31$ & $10^3$ & $9\cdot 10^3$ & $10^6$ \\ -\hline -$13$ & $10^2$ & $10^4$ & $1.3\cdot 10^5$ & $10^8$ \\ -\hline -$16$ & $3.1\cdot 10^2$ & $10^5$ & $1.6\cdot 10^6$ & $10^{10}$ \\ -\hline -$19$ & $10^3$ & $10^6$ & $1.9\cdot 10^7$ & $10^{12}$ \\ -\hline -\end{tabular} -\end{center} -\end{table} - - -# Quelques exercices (1/3) - -## Complexité de l'algorithme de test de primalité naïf? - -```C -for (i = 2; i < sqrt(N); ++i) { - if (N % i == 0) { - return false; - } -} -return true; -``` - -. . . - -## Réponse - -$$ -\mathcal{O}(\sqrt{N}). -$$ - -# Quelques exercices (2/3) - -## Complexité de trouver le minimum d'un tableau? - -```C -int min = MAX; -for (i = 0; i < N; ++i) { - if (tab[i] < min) { - min = tab[i]; - } -} -return min; -``` - -. . . - -## Réponse - -$$ -\mathcal{O}(N). -$$ - -# Quelques exercices (3/3) - -## Complexité du tri par sélection? - -```C -int ind = 0 -while (ind < SIZE-1) { - min = find_min(tab[ind:SIZE]); - swap(min, tab[ind]); - ind += 1 -} -``` - -. . . - -## Réponse - -### `min = find_min` - -$$ -(N-1)+(N-2)+...+2+1=\sum_{i=1}^{N-1}i=N\cdot(N-1)/2=\mathcal{O}(N^2). -$$ - -## Finalement - -$$ -\mathcal{O}(N^2\mbox{ comparaisons}) + \mathcal{O}(N\mbox{swaps})=\mathcal{O}(N^2). -$$ - - diff --git a/slides/cours_7.md b/slides/cours_7.md deleted file mode 100644 index 1ee5638..0000000 --- a/slides/cours_7.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -title: "Tris" -date: "2022-11-16" ---- - -# Rappel: Complexité - -\footnotesize - -Soit `tab` un tableau de longueur `N` de `double`. - -1. Comment déclare-t-on un tel tableau? - -. . . - -```C -double tab[N]; -``` - -2. Quelle est la complexité du calcul de la moyenne de `tab`? - -. . . - -```C -double mean = 0.0; -for (int i = 0; i < N; ++i) { // N assignations - mean += tab[i]; // N additions -} -mean /= N; // O(N) -``` - -. . . - -3. Quelle est la complexité du calcul de l'écart type de `tab` ($\sigma=\sqrt{\sum_i (t_i - m)^2}/N$? - -. . . - -```C -double mean = moyenne(N, tab); // O(N) -double dev = 0.0; -for (int i = 0; i < N; ++i) { - dev += pow(tab[i] - moyenne, 2); // N tours -} -dev = sqrt(dev); dev /= N; // O(2*N) = O(N) -``` - -# Tri par insertion (1/3) - -## But - -* trier un tableau par ordre croissant - -## Algorithme - -Prendre un élément du tableau et le mettre à sa place parmis les éléments déjà -triés du tableau. - - - -# Tri par insertion (2/3) - -## Exercice: Proposer un algorithme (en C) - -. . . - -```C -void tri_insertion(int N, int tab[N]) { - for (int i = 1; i < N; i++) { - int tmp = tab[i]; - int pos = i; - while (pos > 0 && tab[pos - 1] > tmp) { - tab[pos] = tab[pos - 1]; - pos = pos - 1; - } - tab[pos] = tmp; - } -} -``` - -# Tri par insertion (3/3) - -## Question: Quelle est la complexité? - -. . . - -* Parcours de tous les éléments ($N-1$ passages dans la boucle) - * Placer: en moyenne $i$ comparaisons et affectations à l'étape $i$ -* Moyenne: $\mathcal{O}(N^2)$ - -. . . - -* Pire des cas, liste triée à l'envers: $\mathcal{O}(N^2)$ -* Meilleurs des cas, liste déjà triée: $\mathcal{O}(N)$ - -# L'algorithme à la main - -## Exercice *sur papier* - -* Trier par insertion le tableau `[5, -2, 1, 3, 10]` - -```C - - - - - - - - - - - - - -``` - -# Tri rapide ou quicksort (1/8) - -## Idée: algorithme `diviser pour régner` (`divide-and-conquer`) - -* Diviser: découper un problème en sous problèmes; -* Régner: résoudre les sous-problèmes (souvent récursivement); -* Combiner: à partir des sous problèmes résolu, calculer la solution. - -## Le pivot - -* Trouver le **pivot**, un élément qui divise le tableau en 2, tels que: - 1. Éléments à gauche sont **plus petits** que le pivot. - 2. Élements à droite sont **plus grands** que le pivot. - -# Tri rapide ou quicksort (2/8) - -## Algorithme `quicksort(tableau)` - -1. Choisir le pivot et l'amener à sa place: - * Les éléments à gauche sont plus petits que le pivot. - * Les éléments à droite sont plus grand que le pivot. -2. `quicksort(tableau_gauche)` en omettant le pivot. -3. `quicksort(tableau_droite)` en omettant le pivot. -4. S'il y a moins de deux éléments dans le tableau, le tableau est trié. - -. . . - -Compris? - -. . . - -Non c'est normal, faisons un exemple. - -# Tri rapide ou quicksort (3/8) - -\footnotesize - -Deux variables sont primordiales: - -```C -entier ind_min, ind_max; // les indices min/max des tableaux à trier -``` - - - -# Tri rapide ou quicksort (4/8) - -\footnotesize - -Deux variables sont primordiales: - -```C -entier ind_min, ind_max; // les indices min/max des tableaux à trier -``` - -## Pseudocode: quicksort - -```python -rien quicksort(entier tableau[], entier ind_min, entier ind_max) - si (longueur(tab) > 1) - ind_pivot = partition(tableau, ind_min, ind_max) - si (longueur(tableau[ind_min:ind_pivot-1]) != 0) - quicksort(tableau, ind_min, pivot_ind - 1) - si (longueur(tableau[ind_pivot+1:ind_max-1]) != 0) - quicksort(tableau, ind_pivot + 1, ind_max) -``` - -# Tri rapide ou quicksort (5/8) - -\footnotesize - -## Pseudocode: partition - -```C -entier partition(entier tableau[], entier ind_min, entier ind_max) - pivot = tableau[ind_max] // choix arbitraire - i = ind_min - j = ind_max-1 - tant que i < j: - en remontant i trouver le premier élément > pivot - en descendant j trouver le premier élément < pivot - échanger(tableau[i], tableau[j]) - // les plus grands à droite - // mettre les plus petits à gauche - - // on met le pivot "au milieu" - échanger(tableau[i], tableau[ind_max]) - retourne i // on retourne l'indice pivot -``` - -# Tri rapide ou quicksort (6/8) - -## Exercice: implémenter les fonctions `quicksort` et `partition` - -. . . - -```C -void quicksort(int size, int array[size], int first, - int last) -{ - if (first < last) { - int midpoint = partition(size, array, first, last); - if (first < midpoint - 1) { - quicksort(size, array, first, midpoint - 1); - } - if (midpoint + 1 < last) { - quicksort(size, array, midpoint + 1, last); - } - } -} -``` - - -# Tri rapide ou quicksort (7/8) - -\footnotesize - -## Exercice: implémenter les fonctions `quicksort` et `partition` - -```C -int partition(int size, int array[size], int first, int last) { - int pivot = array[last]; - int i = first - 1, j = last; - do { - do { - i += 1; - } while (array[i] < pivot && i < j); - do { - j -= 1; - } while (array[j] > pivot && i < j); - if (j > i) { - swap(&array[i], &array[j]); - } - } while (j > i); - swap(&array[i], &array[last]); - return i; -} -``` - -# Tri rapide ou quicksort (8/8) - -## Quelle est la complexité du tri rapide? - -. . . - -* Pire des cas plus: $\mathcal{O}(N^2)$ - * Quand le pivot sépare toujours le tableau de façon déséquilibrée ($N-1$ - éléments d'un côté $1$ de l'autre). - * $N$ boucles et $N$ comparaisons $\Rightarrow N^2$. -* Meilleur des cas (toujours le meilleur pivot): $\mathcal{O}(N\cdot \log_2(N))$. - * Chaque fois le tableau est séparé en $2$ parties égales. - * On a $\log_2(N)$ partitions, et $N$ boucles $\Rightarrow N\cdot - \log_2(N)$. -* En moyenne: $\mathcal{O}(N\cdot \log_2(N))$. - -# L'algorithme à la main - -## Exercice *sur papier* - -* Trier par tri rapide le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` - -```C - - - - - - - - - - - - - -``` - - - diff --git a/slides/cours_8.md b/slides/cours_8.md deleted file mode 100644 index 2cfa2a3..0000000 --- a/slides/cours_8.md +++ /dev/null @@ -1,281 +0,0 @@ ---- -title: "Backtracking et piles" -date: "2022-11-23" ---- - -# Tri à bulle (1/4) - -## Algorithme - -* Parcours du tableau et comparaison des éléments consécutifs: - - Si deux éléments consécutifs ne sont pas dans l'ordre, ils sont échangés. -* On recommence depuis le début du tableau jusqu'à avoir plus d'échanges à - faire. - -## Que peut-on dire sur le dernier élément du tableau après un parcours? - -. . . - -* Le plus grand élément est **à la fin** du tableau. - * Plus besoin de le traiter. -* A chaque parcours on s'arrête un élément plus tôt. - -# Tri à bulle (2/4) - -## Exemple - - - - -# Tri à bulle (3/4) - -## Exercice: écrire l'algorithme (poster le résultat sur matrix) - -. . . - -```C -rien tri_a_bulles(entier tableau[]) - pour i de longueur(tableau)-1 à 1: - trié = vrai - pour j de 0 à i-1: - si (tableau[j] > tableau[j+1]) - échanger(array[j], array[j+1]) - trié = faux - - si trié - retourner -``` - -# Tri à bulle (4/4) - -## Quelle est la complexité du tri à bulles? - -. . . - -* Dans le meilleurs des cas: - * Le tableau est déjà trié: $\mathcal{O}(N)$ comparaisons. -* Dans le pire des cas, $N\cdot (N-1)/2\sim\mathcal{O}(N^2)$: -$$ -\sum_{i=1}^{N-1}i\mbox{ comparaison et }3\sum_{i=1}^{N-1}i \mbox{ affectations -(swap)}\Rightarrow \mathcal{O}(N^2). -$$ -* En moyenne, $\mathcal{O}(N^2)$ ($N^2/2$ comparaisons). - -# L'algorithme à la main - -## Exercice *sur papier* - -* Trier par tri à bulles le tableau `[5, -2, 1, 3, 10, 15, 7, 4]` - -```C - - - - - - - - - - - - - -``` - - -# Problème des 8-reines - -* Placer 8 reines sur un échiquier de $8 \times 8$. -* Sans que les reines ne puissent se menacer mutuellement (92 solutions). - -## Conséquence - -* Deux reines ne partagent pas la même rangée, colonne, ou diagonale. -* Donc chaque solution a **une** reine **par colonne** ou **ligne**. - -## Généralisation - -* Placer $N$ reines sur un échiquier de $N \times - N$. -- Exemple de **backtracking** (retour en arrière) $\Rightarrow$ récursivité. - -](./figs/fig_recursivite_8_reines.png){width=35%} - -# Problème des 2-reines - -{width=50%} - -# Comment trouver les solutions? - -* On pose la première reine sur la première case disponible. -* On rend inaccessibles toutes les cases menacées. -* On pose la reine suivante sur la prochaine case non-menacée. -* Jusqu'à ce qu'on puisse plus poser de reine. -* On revient alors en arrière jusqu'au dernier coup où il y avait plus qu'une - possibilité de poser une reine. -* On recommence depuis là . - -. . . - -* Le jeu prend fin quand on a énuméré *toutes* les possibilités de poser les - reines. - -# Problème des 3-reines - - - -# Problème des 4-reines - - - -# Problème des 4-reines, symétrie - - - -# Problème des 5 reines - -## Exercice: Trouver une solution au problème des 5 reines - -* Faire une capture d'écran / une photo de votre solution et la poster sur - matrix. - -```C - - - - - - - - - - - - - -``` - -# Quelques observations sur le problème - -* Une reine par colonne au plus. -* On place les reines sur des colonnes successives. -* On a pas besoin de "regarder en arrière" (on place "devant" uniquement). -* Trois étapes: - * On place une reine dans une case libre. - * On met à jour le tableau. - * Quand on a plus de cases libres on "revient dans le temps" ou c'est qu'on - a réussi. - -# Le code du problème des 8 reines (1/N) - -## Quelle structure de données? - -. . . - -Une matrice de booléens fera l'affaire: - -```C -bool board[n][n]; -``` - -## Quelles fonctionnalités? - -. . . - -```C -// Pour chaque ligne placer la reine sur toutes les colonnes -// et compter les solutions -void nbr_solutions(board, column, counter); -// Copier un tableau dans un autre -void copy(board_in, board_out); -// Placer la reine à li, co et rendre inaccessible devant -void placer_devant(board, li, co); -``` - -# Le code du problème des 8 reines (2/N) - -## Le calcul du nombre de solutions - -```C -// Calcule le nombre de solutions au problème des <n> reines -nbr_solutions(board, column, count) - // pour chaque ligne - // si la case libre - // si column < n - 1 - // copier board dans un "new" board, - // y poser une reine - // et mettre à jour ce "new" board - // nbr_solutions(new_board, column+1, count) - // sinon - // on a posé la n-ème et on a gagné - // count += 1 -``` - -# Le code du problème des 8 reines (3/N) - -## Le calcul du nombre de solutions - -```C -// Placer une reine et mettre à jour -placer_devant(board, ligne, colonne) - // board est occupé à ligne/colonne - // toutes les cases des colonnes - // suivantes sont mises à jour -``` - -# Le code du problème des 8 reines (4/N) - -## Compris? Alors écrivez le code et postez le! - -. . . - -## Le nombre de solutions - -\footnotesize - -```C -// Calcule le nombre de solutions au problème des <n> reines -void nb_sol(int n, bool board[n][n], int co, int *ptr_cpt) { - for (int li = 0; li < n; li++) { - if (board[li][co]) { - if (co < n-1) { - bool new_board[n][n]; // alloué à chaque nouvelle tentative - copy(n, board, new_board); - prises_devant(n, new_board, li, co); - nb_sol(n, new_board, co+1, ptr_cpt); - } else { - *ptr_cpt = (*ptr_cpt)+1; - } - } - } -} -``` - - -# Le code du problème des 8 reines (5/N) - -\footnotesize - -## Placer devant - -```C -// Retourne une copie du tableau <board> complété avec les positions -// prises sur la droite droite par une reine placée en <board(li,co)> -void prises_devant(int n, bool board[n][n], int li, int co) { - board[li][co] = false; // position de la reine - for (int j = 1; j < n-co; j++) { - // horizontale et diagonales à droite de la reine - if (j <= li) { - board[li-j][co+j] = false; - } - board[li][co+j] = false; - if (li+j < n) { - board[li+j][co+j] = false; - } - } -} -``` - diff --git a/slides/cours_9.md b/slides/cours_9.md deleted file mode 100644 index d3b4a02..0000000 --- a/slides/cours_9.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -title: "Piles" -date: "2022-12-07" ---- - -# Les piles (1/5) - -## Qu'est-ce donc? - -* Structure de données abstraite... - -. . . - -* de type `LIFO` (*Last in first out*). - -](figs/Stack.svg){width=70%} - -## Des exemples de la vraie vie - -. . . - -* Pile d'assiettes, de livres, ... -* Adresses visitées par un navigateur web. -* Les calculatrices du passé (en polonaise inverse). -* Les boutons *undo* de vos éditeurs de texte (aka *u* dans vim). - -# Les piles (2/5) - -## 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 retrouner. -3. Liste vide? (is_empty?). - -. . . - -4. Jeter un oeil (peek): retourner l'élément du sommet de la pile (sans le dépiler). -5. Nombre d'éléments (length). - -## Comment faire les 4,5 à partir de 1 à 3? - -. . . - -4. Dépiler l'élément, le copier, puis l'empiler à nouveau. -5. Dépiler jusqu'à ce que la pile soit vide, puis empiler à nouveau. - -. . . - -## Existe en deux goûts - -* Pile avec ou sans limite de capacité (à concurrence de la taille de la -mémoire). - -# Les piles (3/5) - -## Implémentation - -* Jusqu'ici on n'a pas du tout parlé d'implémentation (d'où le nom de structure - abstraite). -* Pas de choix unique d'implémentation. - -## Quelle structure de données allons nous utiliser? - -. . . - -Et oui vous avez deviné: un tableau! - -## La structure: de quoi avons-nous besoin (pile de taille fixe)? - -. . . - -```C -#define MAX_CAPACITY 500 -typedef struct _stack { - int data[MAX_CAPACITY]; // les données - int top; // indice du sommet -} stack; -``` - -# Les piles (4/5) - -## Initialisation - -. . . - -```C -void stack_init(stack *s) { - s->top = -1; -} -``` - -## Est vide? - -. . . - -```C -bool stack_is_empty(stack s) { - return s.top == -1; -} -``` - -## Empiler (ajouter un élément au sommet) - -. . . - -```C -void stack_push(stack *s, int val) { - s->top += 1; - s->data[s->top] = val; -} -``` - -# Les piles (5/5) - -## Dépiler (enlever l'élément du sommet) - -. . . - -```C -int stack_pop(stack *s) { - s->top -= 1; - return s->data[s->top+1]; -} -``` - -## Jeter un oeil (regarder le sommet) - -. . . - -```C -int stack_peek(stack *s) { - return s->data[s->top]; -} -``` - -## Quelle est la complexité de ces opérations? - -. . . - -## Voyez-vous des problèmes potentiels avec cette implémentation? - -. . . - -* Empiler avec une pile pleine. -* Dépiler avec une pile vide. -* Jeter un oeil au sommet d'une pile vide. - -# Gestion d'erreur, level 0 - -* Il y a plusieurs façon de traiter les erreur: - * Ne rien faire (laisser la responsabilité à l'utilisateur). - * Faire paniquer le programme (il plante plus ou moins violemment). - * Utiliser des codes d'erreurs. - -## La panique - -* En C, on a les `assert()` pour faire paniquer un programme. - - -# Assertions (1/3) - -```C -#include <assert.h> -void assert(int expression); -``` - -## Qu'est-ce donc? - -- Macro permettant de tester une condition lors de l'exécution d'un programme: - - Si `expression == 0`{.C} (condition fausse), `assert()`{.C} affiche un message d'erreur sur `stderr`{.C} et termine l'exécution du programme. - - Sinon l'exécution se poursuit normalement. - - Peuvent être désactivés à la compilation avec `-DNDEBUG` (équivalent à `#define - NDEBUG`) - -## À quoi ça sert? - -- Permet de réaliser des tests unitaires. -- Permet de tester des conditions catastrophiques d'un programme. -- **Ne permet pas** de gérer les erreurs. - -# Assertions (2/3) - -<!-- \footnotesize --> - -## Exemple - -```C -#include <assert.h> -void stack_push(stack *s, int val) { - assert(s->top < MAX_CAPACITY-1); - s->top += 1; - s->data[s->top] = val; -} -int stack_pop(stack *s) { - assert(s->top >= 0); - s->top -= 1; - return s->data[s->top+1]; -} -int stack_peek(stack *s) { - assert(s->top >= 0); - return s->data[s->top]; -} -``` - -# Assertions (3/3) - -## Cas typiques d'utilisation - -- Vérification de la validité des pointeurs (typiquement `!= NULL`{.C}). -- Vérification du domaine des indices (dépassement de tableau). - -## Bug vs. erreur de *runtime* - -- Les assertions sont là pour détecter les bugs (erreurs d'implémentation). -- Les assertions ne sont pas là pour gérer les problèmes externes au programme (allocation mémoire qui échoue, mauvais paramètre d'entrée passé par l'utilisateur, ...). - -. . . - -- Mais peuvent être pratiques quand même pour ça... -- Typiquement désactivées dans le code de production. - -# La pile dynamique - -## Comment modifier le code précédent pour avoir une taille dynamique? - -. . . - -```C -// alloue une zone mémoire de size octets -void *malloc(size_t size); -// change la taille allouée à size octets (contiguïté garantie) -void *realloc(void *ptr, size_t size); -``` - -## Et maintenant? - -. . . - -```C -stack_create(); // crée une pile avec une taille par défaut -// vérifie si la pile est pleine et réalloue si besoin -stack_push(); -// vérifie si la pile est vide/trop grande -// et réalloue si besoin -stack_pop(); -``` - -## Exercice: ouvrir un repo/issues pour l'implémentation - -* Oui-oui cela est une introduction au développement collaboratif (et - hippie). - -# 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 - - - - - - - - - - - - - - - - -``` -- GitLab