From e94a7a1565b54c682cfb8b5621e1aba165c45393 Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Thu, 6 Mar 2025 21:48:16 +0100 Subject: [PATCH] updated 2025 --- slides/cours_16.md | 666 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 slides/cours_16.md diff --git a/slides/cours_16.md b/slides/cours_16.md new file mode 100644 index 0000000..6286cda --- /dev/null +++ b/slides/cours_16.md @@ -0,0 +1,666 @@ +--- +title: "Arbres binaires" +date: "2024-03-07" +--- + +# Les arbres binaires + +\Huge Les arbres binaires + +# L'arbre binaire + +* Structure de données abstraite, +* Chaque nœud a au plus deux enfants: gauche et droite, +* Chaque enfants 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; +``` + +# 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. + +:::: + +::: + +# 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/4) + +* Deux parties: + * Recherche le parent où se passe l'insertion. + * Ajout de l'enfant 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/4) + +* Deux parties: + * Recherche de la position. + * Ajout dans l'arbre. + +## Ajout de l'enfant + +``` +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 + +## Recherche du parent (ensemble) + +. . . + +```C +node *position(node *tree, key_t key) { + node * current = tree; + if (NULL != current) { + node *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; +} +``` + +# 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 +node *add_key(node **tree, key_t key) { + node *new_node = calloc(1, sizeof(*new_node)); + new_node->key = key; + if (NULL == *tree) { + *tree = new_node; + } else { + node * 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; +} +``` + +# 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; +void tree_print(node *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**[^2] 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(node *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(node *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; +node *search(key_t key, node *tree) { + node *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(node *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(node *tree) { + if (NULL == tree) { + return 0; + } else { + return 1 + tree_size(tree->left) + + tree_size(tree->right); + } +} +``` + +[^2]: Copyright cours de mathématiques pendant trop d'années. -- GitLab