Renaud Marlet – ENPC MOPSI 2013-2014 1 ENPC – MOPSI Petit arboretum e (1 partie) Renaud Marlet Laboratoire LIGM-IMAGINE http://imagine.enpc.fr/~marletr Renaud Marlet – ENPC MOPSI 2013-2014 Les arbres : en informatique et ailleurs... Structure de donnée omniprésente − représentation d'une hiérarchie d'objets ■ − décomposition organisationnelle arbres de décision ■ simulation − recherche d'information − compression − routage − ... 2 Renaud Marlet – ENPC MOPSI 2013-2014 3 Exemple : expression arithmétique 2 2 a +b +2ab cos ϕ (2 × (­(3))) + (((4 × 5) + 6) / (9 ­ 7)) + + × ÷ a 2 ­ + ^ + 2 ­ b × 3 4 6 5 × ^ 9 2 × 2 7 texte d'une expression : structure d'arbre implicite (conventionnelle) × a b cos φ Renaud Marlet – ENPC MOPSI 2013-2014 4 Exemple : programme for = i < 0 i ++ 5 ; i if > for (i = 0; i < 5; i++) { if (x < i) x += i%2; f(3*x+i); } x call += i x {} f i % x i + * 3 i i 2 i 3 i x - x Renaud Marlet – ENPC MOPSI 2013-2014 5 Structure, vocabulaire, représentation Représentation tête en bas racine branche nœud feuille sous-arbre Renaud Marlet – ENPC MOPSI 2013-2014 6 Structure, vocabulaire, représentation Représentation tête en bas racine branche nœud feuille sous-arbre Renaud Marlet – ENPC MOPSI 2013-2014 7 Orientation et identification des branches Nœud père et nœuds fils racine variante : les fils connaissent aussi leur père branche nœud père branche 1 nœud fils 1 2 2 variante (plus rare) : les père ne connaissent pas leurs fils feuille (nœud terminal) sous-arbre Renaud Marlet – ENPC MOPSI 2013-2014 8 Informations accrochées sur l'arbre Sur les nœuds (parfois feuilles seules) et/ou branches N.B. formulations équivalentes sauf à la racine 1 12 12 4 2 2 6 6 3 3 5 5 8 8 4 10 12 10 12 1 42 42 1 Renaud Marlet – ENPC MOPSI 2013-2014 9 Feuilles & nœuds (non-)terminaux Opérateurs n-aires sans fils vs feuille terminale () () 8 4 () () 3 nœud terminal () () 42 () 1 nœud non-terminal sans fils ((8 (3)) 4 (((42) () 1))) ou [[8, [3]], 4, [[[42], [], 1]]] Renaud Marlet – ENPC MOPSI 2013-2014 10 Exemple : (dé)composition géométrique Géométrie de construction de solides (constructive solid geometry, CSG) Sur un nœud non feuille : une opération de composition (union, intersection, différence...) Sur une feuille : un objet géométrique élémentaire (cube, sphère, cylindre...) Renaud Marlet – ENPC MOPSI 2013-2014 Exemple : représentation compacte d'une image Noir & Blanc info N/B : seulement sur les feuilles, pas sur les nœuds intermédiaires 11 Renaud Marlet – ENPC MOPSI 2013-2014 12 Arbre explicite vs implicite ● ● Arbre explicite (finalité) − créé intentionnellement − opérations : ■ modifications : ajouter, retirer, déplacer... ■ parcours : recherche d'info, énumération... ■ calcul : combinaisons des infos sur les nœuds... Arbre implicite (support) − hébergement de données − modif. des données → modif. automatique de l'arbre Renaud Marlet – ENPC MOPSI 2013-2014 13 Parcours d'un arbre 1 2 3 1 6 7 4 5 2 8 9 11 5 12 10 En profondeur d'abord (depth first) [DFS = depth-first search] 3 4 6 8 7 9 10 11 12 En largeur d'abord (breadth first) [BFS = breadth-first search] Renaud Marlet – ENPC MOPSI 2013-2014 14 Parcours d'un arbre 1 2 3 1 6 7 4 5 2 8 9 11 5 12 3 4 6 8 10 7 9 10 11 12 En profondeur d'abord (depth first) En largeur d'abord (breadth first) Comment cela s'implémente-t-il ? Renaud Marlet – ENPC MOPSI 2013-2014 15 Parcours en profondeur d'abord Algorithme : 1 2 3 6 7 4 5 profDabord(nœud) − 8 9 11 pour chaque nœud fils, profDabord(nœud fils) 12 10 N.B. programme récursif ! Renaud Marlet – ENPC MOPSI 2013-2014 16 Parcours en largeur d'abord Algorithme : 1 2 5 3 4 6 8 largeurDabord(noeud) 7 9 12 10 11 − créer une file − mettre la racine en tête de file − tant que la file n'est pas vide ■ prendre le nœud en tête de file ■ mettre les fils du nœud dans la file Renaud Marlet – ENPC MOPSI 2013-2014 17 Opérations en descendant vs en montant En profondeur d'abord 1 2 3 12 6 7 4 5 4 8 9 11 1 12 5 11 3 2 10 7 8 9 10 6 Opération sur les nœuds en descendant Opération sur les nœuds en montant Renaud Marlet – ENPC MOPSI 2013-2014 18 Attributs hérités, attributs synthétisés 0 1 2 0 3 1 2 3 information héritée : information sur un nœud fils dépendant de l'information sur le nœud père information synthétisée : information sur le nœud père dépendant de l'information sur les nœuds fils infoi = fi(info0) info0 = f (info1 , info2 , info3) Renaud Marlet – ENPC MOPSI 2013-2014 19 Structure de données cf. cours correspondant ● Création ■ ■ ● nœud / arbre constructeur − − avec info initiale ou valeur par défaut ■ ■ sous-arbre (recursive) nœud 18 − 4 29 10 23 les fils d'un nœud les nœuds d'un arbre un fils (sous-arbre) nouvelle branche − à une position ou un rang donné 0 1 ajouter/supprimer ■ ? lecture, écriture parcourir (ex. itérateur) ■ Destruction ■ accéder à l'info d'un nœud ■ − x ● Opérations : 2 36 51 12 17 24 8 9 Renaud Marlet – ENPC MOPSI 2013-2014 20 Exemple d'interface // Node of a tree containing an integer at each node class IntTree { // Node information int data; // Node sons ??? quelle structure de données sous-jacente utiliser ??? sons; public: // Create a node with given information IntTree(int d); // Return node information int getData(); // Set node information void setData(int d); // Return the number of sons of this node int nbSons(); // Return the son at position pos (left-most at position 0) IntTree* getSon(int pos); // Set the son at position pos (left-most at position 0) void setSon(int pos, IntTree* tree); // Add argument as supplementary right-most son void addAsLastSon(IntTree* tree); // Remove right-most son void removeLastSon(); }; pos = 0 15 42 1 6 2 8 Renaud Marlet – ENPC MOPSI 2013-2014 21 Exemple d'interface // Node of a tree containing an integer at each node class IntTree { // Node information int data; // Node sons vector<IntTree*> sons; public: // Create a node with given information IntTree IntTree(int d); // Return node information int getData(); // Set node information 15 data void setData(int d); sons // Return the number of sons of this node int nbSons(); // Return the son at position pos (left-most at position 0) IntTree* getSon(int pos); // Set the son at position pos (left-most at position 0) void setSon(int pos, IntTree* tree); // Add argument as supplementary right-most son void addAsLastSon(IntTree* tree); // Remove right-most son void removeLastSon(); }; pos = 0 15 data 42 1 2 6 8 42 sons data sons 6 data 8 sons opérations déjà +/- disponibles dans la classe vector <T> Renaud Marlet – ENPC MOPSI 2013-2014 22 Exercice 1 1) Définissez les méthodes de IntTree (≈ 1 ligne par fonction) 2) Construisez l'arbre ci-contre dans une variable root IntTree* root = new IntTree(12); root->addAsLastSon(new IntTree(8)); root->getSon(0)->addAsLastSon(new IntTree(4)); … 3) Ajoutez une méthode d'affichage récursive void display(string prefix = "", string indent = " ") telle que root->display("* ") affiche : * 12 * 8 * 4 * 9 * 23 * 17 * 15 prefix : au début de chaque ligne (pour chaque nœud) indent : ajouté pour chaque niveau de profondeur 12 8 4 23 9 17 15 Renaud Marlet – ENPC MOPSI 2013-2014 23 Exercice 1 (suite) 4) Ajoutez dans IntTree les fonctions suivantes (≈ 1 ligne) : void addSon(int pos, IntTree* tree); void removeSon(int pos); 5) Rendez robustes les fonctions de IntTree : − testez la validité des arguments et levez des exceptions − utilisez pour ça les exceptions de #include <stdexcept> 6) Rendez IntTree générique pour le type des données : − faites une classe Tree<T> qui prend le type en argument 7) Rendez un programme qui illustre chaque point précédent avec des opérations (y compris erronées) sur l'arbre root et des affichages correspondants via root->display() Renaud Marlet – ENPC MOPSI 2013-2014 24 Exercice 2 1) Construisez un arbre et affichez sur une ligne la liste des informations portées par chacun de ses nœuds, avec les trois manières suivantes : a) parcourez-le en profondeur d'abord en affichant la donnée de chaque nœud en descendant b)parcourez-le en profondeur d'abord en affichant la donnée de chaque nœud en montant c) parcourez-le en largeur d'abord en affichant la donnée de chaque nœud Renaud Marlet – ENPC MOPSI 2013-2014 25 Exercice 2 (suite) 2) Définissez une fonction int maxDepth() − qui calcule rapidement la profondeur maximale d'un arbre (= profondeur de la feuille la plus profonde) − mieux vaut-il utilisez un parcours en profondeur d'abord ou en largeur d'abord — pourquoi ? 3) Définissez une fonction int minDepth() − qui calcule rapidement la profondeur minimale d'un arbre (= profondeur de la feuille la moins profonde) − mieux vaut-il utilisez un parcours en profondeur d'abord ou en largeur d'abord — pourquoi ? 4) Rendez un programme qui illustre ces deux fontions Renaud Marlet – ENPC MOPSI 2013-2014 26 Arbre binaire ● Tout nœud a au plus deux fils : − ● ● fils gauche, fils droit Variante : pour tout nœud − 0 fils (feuille), ou bien − exactement 2 fils Cas dégénéré : peigne (gauche ou droit) Renaud Marlet – ENPC MOPSI 2013-2014 27 Arbre binaire de recherche ● Représente un ensemble ordonné ● Info sur nœud nommé « clé » ● Propriété (pour tout nœud) : ● − clés sur fils gauche ≤ clé du père − clés sur fils droit ≥ clé du père 12 8 4 23 9 6 17 15 20 Recherche ≈ par dichotomie − descente dans le sous-arbre qui contient la clé − O(log n) en moyenne pour n nœuds − O(n) dans le pire cas (peigne) 16 Renaud Marlet – ENPC MOPSI 2013-2014 28 Insertion dans un arbre binaire de recherche ● Comment faire ? − ex. insérer 10 − ex. insérer 18 12 8 4 9 6 ● Quelle est la complexité ? 23 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 29 Insertion dans un arbre binaire de recherche ● Comment faire ? − ex. insérer 10 − ex. insérer 18 12 8 4 23 9 17 18 10 6 ● Quelle est la complexité ? 15 20 18 16 Renaud Marlet – ENPC MOPSI 2013-2014 30 Insertion dans un arbre binaire de recherche ● Pour insérer la clé x : − − 12 rechercher un nœud t tel que ■ x < clé(t) et t n'a pas de fils gauche ou ■ x > clé(t) et t n'a pas de fils droit créer un nouveau fils (gauche ou droit) de t avec x 8 4 23 9 10 6 ● Complexité − 17 comme pour la recherche ■ O(log(n)) en moyenne, si arbre à peu près équilibré ■ O(n) dans le pire cas 15 20 18 16 Renaud Marlet – ENPC MOPSI 2013-2014 31 Suppression dans un arbre binaire de recherche ● ● Comment faire ? − ex. supprimer 6 − ex. supprimer 4 − ex. supprimer 8 − ex. supprimer 12 Quelle est la complexité ? 12 8 4 23 9 6 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 32 Suppression dans un arbre binaire de recherche ● Comment faire ? − 12 ex. supprimer 6 8 4 9 6 ● Quelle est la complexité ? 23 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 33 Suppression dans un arbre binaire de recherche ● Comment faire ? − 12 8 ex. supprimer 4 4 9 6 ● Quelle est la complexité ? 23 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 34 Suppression dans un arbre binaire de recherche ● Comment faire ? 12 8 − ex. supprimer 8 4 9 6 ● Quelle est la complexité ? 23 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 35 Suppression dans un arbre binaire de recherche ● Comment faire ? 12 8 4 − ● ex. supprimer 12 Quelle est la complexité ? 23 9 6 17 15 20 16 Renaud Marlet – ENPC MOPSI 2013-2014 36 Suppression dans un arbre binaire de recherche ● Pour supprimer la clé x : − − − − ● rechercher le nœud t avec la clé x 8 si t est une feuille, la supprimer si t a un fils, supprimer t et le remplacer par son fils 4 9 si t a deux fils, ■ chercher t' le nœud le plus à droite 6 du sous-arbre gauche, c.-à.d. prédécesseur de x dans l'arbre [ou symétrique gauche/droite] ■ remplacer clé(t) par clé(t') ■ supprimer t' (qui n'a que 0 ou 1 fils), c.-à-d. attribuer comme fils du père de t' l'éventuel fils gauche de t' 12 23 17 15 20 16 Complexité : O(log(n)) en moyenne si équil., O(n) dans le pire cas Renaud Marlet – ENPC MOPSI 2013-2014 37 Exemple d'interface Pointeur nul class BinarySearchTree { en C++11 int key; // Ici juste un entier pour simplifier BinarySearchTree *left, *right; // nullptr ⇔ pas de fils public: BinarySearchTree(int k); // Construit un nœud k sans fils bool contains(int k); // Teste si l'arbre contient k void insert(int k); // Insert k dans l'arbre (si absent) void remove(int k); // Supprime k dans l'arbre (si présent) }; Renaud Marlet – ENPC MOPSI 2013-2014 38 Exercice 3 1) Rendez publics les champs left et right pour le debug 2) Construisez « à la main » l'arbre ci-dessous dans une variable BinarySearchTree* bst; à lire en penchant la tête sur le côté gauche... 3) Ajouter une fonction récursive void display(string prefix = "") telle que bst.display() affiche : / 23 / \ 17 / \ \ 15 12 \ / 9 \ 8 \ \ 4 12 8 4 23 9 17 15 Renaud Marlet – ENPC MOPSI 2013-2014 39 Exercice 3 (suite) 4) Définissez et testez la fonction contains 5) Définissez et testez la fonction insert − créez un arbre initial avec la valeur 12, insérez-y les valeurs 8, 4, 9, 23, 17, 15, et affichez-le − faites des essais avec des ordres d'insertion différents − dans quel cas (général) l'arbre résultant est-il déséquilibré ? 6) Définissez et testez la fonction remove 7) Quand c'est débogué, rendez à nouveau privés les champs left et right: seuls insert/remove peuvent les modifier 8) Rendez un programme qui illustre successivement différents cas d'insertion, de tests d'appartenance et de suppression Renaud Marlet – ENPC MOPSI 2013-2014 40 Exercice 3 (fin) à lire sur le côté... 9) Bonus : modifier la fonction display (< 10 lignes) pour qu'elle affiche les arbres comme ceci : /--23 | \--17 | \--15 | \--14 12 | /--11 | | \--10 | /--9 \--8 \--4 | /--3 | /--2 \--1 \--0 12 8 4 23 9 17 1 0 11 2 10 3 15 14 Renaud Marlet – ENPC MOPSI 2013-2014 41 Pour les curieux : arbre équilibré ● Arbre avec critère d'équilibre (ex. nb nœuds des fils...) ● Arbre binaire équilibré : − − ● critère d'équilibre ■ entre fils gauche et fils droit ■ total ou partiel évite notamment les peignes Avantages − recherche en O(log(n)) pire cas pour un arbre à n nœuds − insertion et suppression O(n) ou O(log(n)) suivant variantes Renaud Marlet – ENPC MOPSI 2013-2014 42 Arbre AVL ● Georgii Adelson-Velsky & Evguenii Landis (1962) − ● principe général toujours d'actualité Arbre AVL − arbre binaire de recherche équilibré tq la hauteur de deux sous-arbres d'un même nœud diffère au plus de 1 hauteur au plus 1 Renaud Marlet – ENPC MOPSI 2013-2014 43 Arbre non AVL : exemple arbre non AVL 12 7 4 2 15 10 Pourquoi ? 16 8 23 9 21 Renaud Marlet – ENPC MOPSI 2013-2014 44 Arbre AVL : exemple arbre non AVL arbre AVL (après rééquilibrage) 12 12 7 15 7 16 h(gauche) = 0 h(droite) = 3 4 2 10 16 8 4 23 9 21 2 9 8 15 10 21 23 Renaud Marlet – ENPC MOPSI 2013-2014 45 Arbre AVL ● Propriété − ● Principe (transposable dans d'autres circonstances) − ● recherche, insertion, suppression en O(log(n)) insertion et suppression font du rééquilibrage Voir : plein d'exemples sur le web... (par ex. http://oopweb.com/Algorithms/Documents/AvlTrees/Volume/AvlTrees.htm)