Considérez les arbres binaires sans étiquette et enracinés. Nous pouvons compressons ces arbres: chaque fois qu'il ya des pointeurs vers des sous - arbres et avec (interprétation l'égalité structurelle), nous enregistrons (wlog) et remplaçons tous les pointeurs vers avec des pointeurs vers . Voir la réponse d' uli pour un exemple. T = T ′ = T T ′ T
Donnez un algorithme qui prend un arbre dans le sens ci-dessus en entrée et calcule le nombre (minimal) de nœuds qui restent après la compression. L'algorithme doit s'exécuter dans le temps (dans le modèle de coût uniforme) avec le nombre de nœuds en entrée.n
Cela a été une question d'examen et je n'ai pas été en mesure de trouver une bonne solution, ni n'en ai vu une.
Réponses:
Oui, vous pouvez effectuer cette compression en temps , mais ce n'est pas facile :) Nous faisons d'abord quelques observations puis présentons l'algorithme. Nous supposons que l'arbre n'est pas compressé au départ - ce n'est pas vraiment nécessaire mais facilite l'analyse.O ( n logn )
Premièrement, nous caractérisons «l'égalité structurelle» de manière inductive. Soit et deux (sous) arbres. Si et sont tous les deux des arbres nuls (n'ayant aucun sommet), ils sont structurellement équivalents. Si et sont pas tous deux des arbres nuls, alors ils sont structurellement équivalents si leurs enfants gauches sont structurellement équivalents et leurs enfants droits sont structurellement équivalents. L '«équivalence structurelle» est le point fixe minimal sur ces définitions.T ′ T T ′ T T ′T T′ T T′ T T′
Par exemple, deux nœuds foliaires sont structurellement équivalents, car ils ont tous deux les arbres nuls comme leurs enfants, qui sont structurellement équivalents.
Comme il est assez ennuyeux de dire «leurs enfants de gauche sont structurellement équivalents et leurs enfants de droite aussi», nous dirons souvent «leurs enfants sont structurellement équivalents» et entendons la même chose. Notez également que nous disons parfois «ce sommet» lorsque nous voulons dire «le sous-arbre enraciné sur ce sommet».
La définition ci-dessus nous donne immédiatement un indice sur la façon d'effectuer la compression: si nous connaissons l'équivalence structurelle de tous les sous-arbres de profondeur au plus , alors nous pouvons facilement calculer l'équivalence structurelle des sous-arbres de profondeur . Nous devons faire ce calcul de manière intelligente pour éviter un temps d'exécution .d + 1 O ( n 2 )ré ré+ 1 O ( n2)
L'algorithme attribuera des identifiants à chaque sommet lors de son exécution. Un identifiant est un nombre dans l'ensemble . Les identifiants sont uniques et ne changent jamais: nous supposons donc que nous définissons une variable (globale) à 1 au début de l'algorithme, et chaque fois que nous attribuons un identifiant à un sommet, nous attribuons la valeur actuelle de cette variable au sommet et l'incrémentons la valeur de cette variable.{ 1 , 2 , 3 , … , n }
Nous transformons d'abord l'arbre d'entrée en (au plus ) listes contenant des sommets d'égale profondeur, ainsi qu'un pointeur vers leur parent. Cela se fait facilement en temps .O ( n )n O ( n )
Nous compressons d'abord toutes les feuilles (nous pouvons trouver ces feuilles dans la liste avec des sommets de profondeur 0) en un seul sommet. Nous attribuons à ce sommet un identifiant. La compression de deux sommets se fait en redirigeant le parent de l'un des sommets pour pointer vers l'autre sommet à la place.
Nous faisons deux observations: premièrement, tout sommet a des enfants de profondeur strictement plus petite, et deuxièmement, si nous avons effectué une compression sur tous les sommets de profondeur inférieure à (et leur avons donné des identificateurs), alors deux sommets de profondeur sont structurellement équivalents et peut être compressé si les identifiants de leurs enfants coïncident. Cette dernière observation découle de l'argument suivant: deux sommets sont structurellement équivalents si leurs enfants sont structurellement équivalents, et après compression, cela signifie que leurs pointeurs pointent vers les mêmes enfants, ce qui signifie que les identifiants de leurs enfants sont égaux.dré ré
Nous parcourons toutes les listes avec des nœuds de profondeur égale de petite profondeur à grande profondeur. Pour chaque niveau, nous créons une liste de paires entières, où chaque paire correspond aux identifiants des enfants de certains sommets de ce niveau. Nous avons que deux sommets de ce niveau sont structurellement équivalents si leurs paires entières correspondantes sont égales. En utilisant l'ordre lexicographique, nous pouvons les trier et obtenir les ensembles de paires entières qui sont égales. Nous compressons ces ensembles en sommets uniques comme ci-dessus et leur donnons des identificateurs.
Les observations ci-dessus prouvent que cette approche fonctionne et aboutit à l'arbre compressé. La durée totale d'exécution est plus le temps nécessaire pour trier les listes que nous créons. Comme le nombre total de paires entières que nous créons est , cela nous donne que le temps d'exécution total est , comme requis. Compter le nombre de nœuds qu'il nous reste à la fin de la procédure est trivial (il suffit de regarder le nombre d'identifiants que nous avons distribués).n O ( n log n )O ( n ) n O ( n logn )
la source
degree
degré strictement inférieur » devrait probablement l'êtredepth
? Et malgré les arbres CS qui poussent vers le bas, je trouve la «hauteur d'un arbre» moins déroutante que la «profondeur d'un arbre».La compression d'une structure de données non mutable afin qu'elle ne reproduise aucun sous-terme structurellement égal est connue sous le nom de contre- hachage . Il s'agit d'une technique importante dans la gestion de la mémoire dans la programmation fonctionnelle. Le hachage est une sorte de mémorisation systématique des structures de données.
Nous allons contre-hacher l'arbre et compter les nœuds après avoir haché. Le hachage contenant une structure de données de taille peut toujours être fait dans O ( nn opérations; compter le nombre de nœuds à la fin est linéaire dans le nombre de nœuds.O(nlg(n))
Je considérerai les arbres comme ayant la structure suivante (écrite ici dans la syntaxe Haskell):
Pour chaque constructeur, nous devons maintenir un mappage de ses arguments possibles au résultat de l'application du constructeur à ces arguments. Les feuilles sont triviales. Pour les nœuds, nous maintenons une carte partielle finie où T est l'ensemble des identificateurs d'arbre et N est l'ensemble des identificateurs de nœud; T = N ⊎ { ℓ } où ℓ est le seul identifiant de feuille. (Concrètement, un identifiant est un pointeur vers un bloc de mémoire.)nodes:T×T→N T N T=N⊎{ℓ} ℓ
Nous pouvons utiliser une structure de données à temps logarithmique pour
nodes
, comme un arbre de recherche binaire équilibré. Ci-dessous, j'appellerailookup nodes
l'opération qui recherche une clé dans lanodes
structure de données etinsert nodes
l'opération qui ajoute une valeur sous une nouvelle clé et renvoie cette clé.Maintenant, nous traversons l'arbre et ajoutons les nœuds au fur et à mesure. Bien que j'écrive en pseudocode de type Haskell, je traiterai
nodes
comme une variable globale mutable; nous n'y ajouterons jamais, mais les insertions doivent être enfilées partout. Laadd
fonction revient sur un arbre, en ajoutant ses sous-arbres à lanodes
carte et renvoie l'identifiant de la racine.Le nombre d'
insert
appels, qui est également la taille finale de lanodes
structure de données, est le nombre de nœuds après compression maximale. (Ajoutez-en un pour l'arbre vide si nécessaire.)la source
nodes
insert
etadd
devrait être rendu explicite et une fonction qui résout réellement le problème devrait être donnée, à mon humble avis.nodes
une variable mutable pour plus de commodité, mais vous pouvez l'enfiler tout au long. Je ne vais pas donner le code complet, ce n'est pas SO.Voici une autre idée qui vise à coder (de manière injective) la structure des arbres en nombres, plutôt que de simplement les étiqueter arbitrairement. Pour cela, nous utilisons que la factorisation première de tout nombre est unique.
Cette dernière hypothèse est un étirement sur des machines réelles; dans ce cas, on préférerait utiliser quelque chose de similaire à la fonction d'appariement de Cantor au lieu de l'exponentiation.
la source
Comme les photos ne sont pas autorisées dans les commentaires:
en haut à gauche: une arborescence d'entrée
en haut à droite: les sous-arbres enracinés dans les nœuds 5 et 7 sont également isomorphes.
en bas à gauche et à droite: les arbres compressés ne sont pas définis de manière unique.
la source
Edit: J'ai lu la question car T et T ′ étaient des enfants du même parent. J'ai pris la définition de la compression pour être récursive également, ce qui signifie que vous pouvez compresser deux sous-arbres précédemment compressés. Si ce n'est pas la vraie question, ma réponse pourrait ne pas fonctionner.
Où
hasSameStructure()
est une fonction qui compare deux sous-arbres déjà compressés en temps linéaire pour voir s'ils ont exactement la même structure. L'écriture d'une fonction récursive temporelle linéaire qui traverse chacune et vérifie si l'un sous-arbre a un enfant gauche à chaque fois que l'autre le fait, etc. ne devrait pas être difficile.la source