Algorithmie PC 3 : Arbres corrigé 1 Arbres Maximier On a vu en cours la définition et les possibles utilitées des arbres maximier. Par définition, un arbre maximier est un arbre binaire tel que les clefs des fils d’un noeud soient plus petites que la clef du noeud. Cette définition implique en particulier que la clef de la racine soit la plus grande. Nous allons nous intéresser ici à l’implémentation la plus concise possible d’un arbre maximier. 1.1 Codage d’arbre 1.1.1 La figure 1 montre 3 arbres binaires. Lequel de ces arbres est plein ou complet ? (a) (b) (c) Figure 1: Trois arbres binaires 1 L’arbre (a) est un arbre ni plein (le niveau 3, qui n’est pas le dernier niveau n’est pas rempli), ni complet (puisqu’il n’est pas plein). L’arbre (b) est plein, car tous les niveaux excepté le dernier sont remplis. Il n’est par contre pas complet, car tous les nœuds du dernier niveau ne sont pas à gauche. L’arbre (c) est plein et complet. 1.1.2 Soit A un arbre binaire complet planté. Quel est le nombre de nœuds par niveaux ? Au niveau 1 de l’arbre se trouve la racine et au niveau 2 ses deux fils. Les deux fils ayant chacuns 2 fils, il y en a au maximum 4, etc. Un arbre binaire complet a donc 2i−1 éléments par niveau, à part pour le dernier niveau. 1.1.3 Si on numérote les éléments d’un arbre complet planté de gauche à droite et de haut en bas (la racine aura ainsi le numéro 1, son fils de gauche le numéro 2, son fils de droite le numéro 3, etc), quel est l’indice du père du nœud i ? 1 2 3 4 8 5 9 10 6 7 11 Figure 2: Code d’un arbres binaires La figure 2 montre un tel code en action. Il est alors facile de se convaincre que si le numéro d’un nœud est i, son père est en i/2 (on prend la partie entière si i est impair). De là, on voit aussi que les fils du nœud i sont en 2i et en 2i + 1. Ce code est particulier au arbres binaires complets. En effet, si les niveaux intermédiaires ne sont pas remplis et si les nœuds du dernier niveau ne sont pas à gauche, ça ne marchera pas. On pourra essayer sur les arbres (a) et (b) de la figure 1 pour s’en convaincre. 1.1.4 Conclure de ce qui prédède une façon simple, élégante et prenant très peu de mémoire de coder un arbre binaire complet. 2 Il suffit d’utiliser un tableau de taille N où N est le nombre de nœuds. L’indice de la racine est 1, et pour tout nœud d’indice i , les indices de ses fils sont 2i et 2i + 1. 1.1.5 À quel arbre binaire complet correspond le tableau [S,U,G,A,T,A,S,S,L,L,E,R] en utilisant le code précédent ? L’arbre est présenté en figure 3. 1 S 2 3 U G 4 5 6 7 A T A S S L L E R 8 9 10 11 12 Figure 3: Arbre correspondant à [S,U,G,A,T,A,S,S,L,L,E,R] 1.2 Manipulations d’arbres binaires complets 1.2.1 Proposez un algorithme permettant d’insérer un élément dans un arbre maximier codé comme dans la partie précédente. On commence par ajouter un élément à l’arbre binaire complet à la fin du tableau. Si son père est plus petit que lui, on échange. Si après l’échange le nouveau père est plus petit on échange encore, et ainsi de suite jusqu’à la racine. Ces échanges à répétitions assurent le fait qu’à la fin la structure maximier est respectée. Il faut cependant faire attention à ne pas dépasser la racine (une sentinelle est placée). L’algorithme 1 explicite cette méthode. 1.2.2 Même question pour le codage du remplacement de la clef de la racine. Cela se fait de la façon opposée à l’insertion. On commence par remplacer la valeur de la racine par la nouvelle valeure. Si la valeur de la nouvelle racine est plus petite que la plus grande valeur de ses fils, on échange les valeurs. Si le problème presiste, on continue les échanges de la même manière. Ces échanges successifs assurent le fait à la fin de la procédure, la structure maximier est respectée. 3 Algorithme 1 Insertion d’un élément dans un arbre maximier Données tab (les indices vont de 0 à n-1) n obj (l’objet à rajouter) Début tab[n]=obj n = n+1 k = n+1 Tant que k>1 et tab[k/2-1] < tab[k-1] faire (Attention : dèpart à l’indice 0) tempo=tab[k-1], tab[k-1] = tab[k/2-1], tab[k/2-1] = tempo k = k/2 rendre tab Fin Il faut cependant faire attention à la fin du tableau ainsi qu’au cas où un père ne possède qu’un seul fils. L’algorithme 2 explicite cette méthode. 1.2.3 Enfin, codez une façon de supprimer la racine d’un arbre maximier. La suppression d’un élément peut se faire très simplement (comme vu en cours). Il suffit remplacer (en utilisant l’algorithme 2) la valeur de la racine par la valeur du dernier élément du tableau. 2 Méthodes de recherche Cette partie va s’atteler à savoir si un élément particulier se trouve dans un ensemble d’éléments. On verra que la performance de la recherche dépend de la structure utilisée pour stocker les éléments. 2.1 Recherche séquentielle Ici, la structure pour stocker les éléments est un tableau. Tout nouvel élément est placé en queue de tableau. Quelle est la complexité de recherche d’un élément dans une telle structure ? Et en moyenne ? Ici, pour trouver un élément, il faut parcourir le tableau élément après élément. Ainsi, en cas de recherche infructueuse, il faut comparer tous les éléments du tableau à l’élément recherché : on a donc besoin de N comparaisons. 4 Algorithme 2 de la racine dans un arbre maximier Données tab (les indices vont de 0 à n-1) n obj (l’objet à rajouter) Début tab[0]=obj k=1 Tant que k<=n/2 faire decalage=0 Si 2k < N et tab[2k-1] < tab[(2k+1)-1] alors decalage=1 Si tab[k-1] < tab[2k-1+decalage] tempo=tab[k-1], tab[k-1] = tab[2k-1+decalage], tab[2k-1+decalage] = tempo k = 2k+decalage Sinon k =n (sortie de boucle) rendre tab Fin Dans le cas d’une recherche fructueuse, l’élément recherché peut être de façon équiprobable dans toutes les cases du tableau (il a 1/N chance d’être en position i, pour tout 1 ≤ i ≤ N ). Ainsi, s’il est en position i, il y P aura juste i comparaisons à effectuer. L’espérance du nombre d’opération est alors 1≤i≤N ( N1 i) = N2+1 2.2 Recherche dichotomique Ici, notre structure est un tableau trié. À chaque nouvel élément, on re-trie le tableau. 2.2.1 Quel est la complexité d’un ajout ou d’une suppression d’élément ? Il faut retrier à chaque fois le tableau. Comme on ne rajoute ou on ne supprime qu’un élément, ceci revient à trouver où l’élément doit être placé ou supprimer, puis à décaler tous les autres éléments. De là, le fait de décaler les éléments revient – au pire – à tous les décaler et donc amène à une complexité de N. En moyenne, comme l’élément à déplacer peut être de façon équiprobable à toutes les positions du tableau, et comme si le premier élément à décaler en décaler N − i, le nombre P est en 1position i, il faut N +1 moyen de décalage est : 1≤i≤N ( N (N − i)) = 2 . 5 2.2.2 Quel est le coup d’une recherche ? De façon dichotomique, on coupe successivement le tableau en 2 et on choisit où regarder (puisque le tableau est trié, on sait a priori dans quelle partie du tableau est l’élément recherché s’il est présent). On a donc besoin de log2 (N) opération. En effet, à chaque étape on coupe le tableau en 2 jusqu’à trouver le bon élément. En effet, s’il y a 2 éléments une étape suffit, s’il y a au plus 4 éléments, 2 étapes suffisent, et ainsi de suite. Par là, trouver un élément parmi 2k peut se faire en k étapes. 2.3 Arbres binaires de recherche On utilise ici des arbres binaires de recherche stocker nos données. Un arbre binaire de recherche est un arbre tel que pour tout nœud, son sous arbre gauche ne contienne que des valeurs strictement plus petites que sa propre valeur, et que son sous-arbre droit ne contienne que des valeurs plus grande ou égale à sa valeur. La figure 4 montre un Arbre binaire de recherche. 5 1 20 9 3 15 14 19 12 Figure 4: Un arbre binaire de recherche Nous ne détaillerons pas ici les algorithmes d’insertion et de suppression mais leur complexité est la même : • complexité maximale O(n), • complexité moyenne O(log(n)). Effecuter une recherche se fait depuis la racine et on parcours les nœuds (à gauche si la valeur à chercher est inférieure à la valeur du nœud, à droite sinon) jusqu’à arriver à la solution. L’insertion peut se faire selon le même principe, on parcours l’arbre pour rechercher la valeur et on rajoute la valeur à la dernière 6 feuille visitée. Ainsi, si on veut rajouter la valeur 8 à l’arbre de la figure 4, on parcours l’arbre de la façon suivante 5 ← 20 ← 9 et on insère 8 à gauche de 9. Pour la suppression, c’est un petit peu plus technique. Certains nœuds sont faciles à supprimer : ceux qui n’ont qu’au plus 1 fils. Pour ceux qui ont deux fils (comme la racine), il suffit de prendre la valeur qui lui est strictement supérieure (ici 9). Ce nœud ne peut posséder qu’un seul fils à droite (car entre la racine et sa valeur strictmeent supérieure il n’y a personne dans l’arbre). On peut alors échanger la valeur de la racine avec le nœud trouvé (ici 9) et de le supprimer. Le résultat est présenté en figure 5 9 1 20 3 15 14 19 12 Figure 5: Un arbre binaire de recherche 2.4 Quel est le coup maximal et moyen d’une recherche ? La hauteur d’un arbre de recherche peut être égale au nombre de données si l’arbre est mal construit (des données triées par exemple), ainsi, au maximum la recherche, la suppression ou l’insertion d’une donnée peut prendre un temps en O(n). Le temps moyen est donc égal à 1/N fois la longeur totale interne (la somme de tous les chemins des nœuds qui ne sont pas des feuilles à la racine) moyenne d’un arbre binaire de recherche. La longeur totale moyenne d’un arbre binaire de recherche peut être trouvée à partir de la formule de récurrence suivante : X C(N ) = N − 1 + 1/N (C(i − 1) + C(N − i)) 1≤i≤N Et C(1)=1. Le premier terme tient compte du fait que la racine contribue de 1 à la longueur des chemins des N-1 autres éléments, et le deuxième terme précise la longeur des sous-arbres à gauche et à droite de la racine. La racine à en effet 7 1/N chance d’être la i ième plus petite valeures et donc que son arbre gauche contienne i éléments et son arbre droit N-i éléments. Cette récurrence est presque identique à celle trouvée en PC2 pour le tri rapide et peut être résolue de la même manière. On a alors C(N) =O(n log(n)). On peut prouver que, de même que pour l’insertion ou la suppression, ce temps moyen est en O(log(n)). Cette méthode est don plus efficace que la recherche dichotomique et séquencielle. 8