diff --git a/slides/cours_15.md b/slides/cours_15.md index b95dc0ef12df786aed30f2539b57ccfffa040c7e..1f7c0e08effe909d822e64bfae9227b20fbb9271 100644 --- a/slides/cours_15.md +++ b/slides/cours_15.md @@ -539,605 +539,3 @@ A ::: -# L'arbre binaire - -* Structure de données abstraite. -* Chaque nœud a au plus deux enfants: gauche et droite. -* Chaque enfant 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 n'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 n'est pas vide. - 2. On visite la racine du sous-arbre. - 3. On descend dans le sous-arbre de droite (s'il n'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**[^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(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) - tant_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 > key) { - current = current->gauche; - } else if (current->key < key){ - 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 du 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 ne 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 dessin 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 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/2) - -* 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 -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; -} -``` - -[^2]: Copyright cours de mathématiques pendant trop d'années.