M1 MEEF SD maths option info Représentation d’arbres en Python 1 Plusieurs implémentations 1.1 Le minimum vital Pour chacune de ces représentations, on pourra coder les fonctions : noeud(x) crée un nouvel arbre formé d’un seul nœud de valeur x. racine(a), fg(a) et fd(a) renvoient respectivement la racine, le fils gauche et le fisl droit de a. modifR(a,x) affecte la nouvelle valeur x à la racine de a. modifG(a,b) remplace le fils gauche de a par b. 1.2 Chaînage par indices Dans tout langage proposant des tableaux, on peut construire des arbres sous forme d’un tableau à 3 colonnes : indice 0 1 2 .. . contenu du nœud A B C .. . indice de son fils gauche 1 None None .. . indice de son fils droit 2 None None .. . On note que l’indice None représente un fils vide. Il faut de plus mémoriser : — l’indice de la racine (on pourrait décider que celle-ci est à l’indice 0 mais pour réorganiser l’arbre ce n’est pas toujours pratique) — le nombre de nœuds dans l’arbre (là encore pas indispensable, mais pratique pour savoir quelle ligne est utilisable pour créer un nouveau nœud) 1.3 Construction « récursive » Une idée très simple et qui peut suffire ici est de représenter chaque nœud par une liste à 3 éléments [contenu, fg, fd] ou par une liste vide pour l’arbre vide. C’est cependant assez limité : — La taille étant fixée à 3, il serait plus logique d’utiliser un triplet, mais on perdrait le côté mutable (si on fait du pur fonctionnel ça marche). — Il devient vite illisible d’écrire a[0] pour le contenu du nœud, a[1] pour son fils gauche, etc. Une option similaire mais plus agréable est l’utilisation d’un dictionnaire : >>> a = {’val’:1, ’fg’:None, ’fd’:None} On accède alors plus confortablement aux composantes du nœud par des « indices » nommés : a[’fg’] est son fils gauche, etc. 1.4 Un détail un peu pénible Quelle que soit la représentation choisie, on peut avoir de mauvaises surprises lorsqu’on cherche à modifier l’arbre par effet de bord. En effet, s’il est possible de modifier n’importe quelle partie de l’arbre, il est plus difficile de modifier le nœud racine. On rencontre notamment le problème lorsqu’on veut passer d’un arbre vide à un arbre non vide et réciproquement. Cela s’explique par le fait que toutes les parties de l’arbre sont embarquées par référence (le modèle mémoire prédominant en Python), mais que le nœud racine, qui est passé en argument à une fonction, ne peut lui-même être modifié. 1 Une solution est d’ajouter un niveau d’indirection pour la racine de l’arbre, ce qui permet de la modifier dans les fonctions. Par exemple, on représente un arbre comme une liste à un seul élément [a] qui est alors passée en argument aux fonctions. Cela reste lourd à utiliser et peu fiable ; on verra avec les objets une façon plus lisible de réaliser cette indirection. 2 Applications Plusieurs idées d’exercices pour se familiariser avec ces implémentations : Exercice 1 Écrire des fonctions permettant de traduire un arbre depuis une de ces représentations vers une autre. Exercice 2 Écrire les parcours en profondeur et en largeur pour les représentations de votre choix. Exercice 3 Utiliser l’une de ces représentations pour implémenter des arbres binaires de recherche. 2