Quelqu'un peut-il s'il vous plaît m'aider à comprendre l'algorithme de traversée d'arbre inorder Morris suivant sans utiliser de piles ni de récursivité? J'essayais de comprendre comment ça marche, mais ça m'échappe.
1. Initialize current as root
2. While current is not NULL
If current does not have left child
a. Print current’s data
b. Go to the right, i.e., current = current->right
Else
a. In current's left subtree, make current the right child of the rightmost node
b. Go to this left child, i.e., current = current->left
Je comprends l'arbre est modifiée de façon que le current node
, est fait la right child
de max node
dans right subtree
et d' utiliser cette propriété pour afinde traversal. Mais au-delà de ça, je suis perdu.
EDIT: trouvé ce code C ++ d'accompagnement. J'avais du mal à comprendre comment l'arbre est restauré après sa modification. La magie réside dans la else
clause, qui est frappée une fois que la feuille de droite est modifiée. Voir le code pour plus de détails:
/* Function to traverse binary tree without recursion and
without stack */
void MorrisTraversal(struct tNode *root)
{
struct tNode *current,*pre;
if(root == NULL)
return;
current = root;
while(current != NULL)
{
if(current->left == NULL)
{
printf(" %d ", current->data);
current = current->right;
}
else
{
/* Find the inorder predecessor of current */
pre = current->left;
while(pre->right != NULL && pre->right != current)
pre = pre->right;
/* Make current as right child of its inorder predecessor */
if(pre->right == NULL)
{
pre->right = current;
current = current->left;
}
// MAGIC OF RESTORING the Tree happens here:
/* Revert the changes made in if part to restore the original
tree i.e., fix the right child of predecssor */
else
{
pre->right = NULL;
printf(" %d ",current->data);
current = current->right;
} /* End of if condition pre->right == NULL */
} /* End of if condition current->left == NULL*/
} /* End of while */
}
c++
binary-tree
tree-traversal
brainydexter
la source
la source
pre->right = NULL;
Réponses:
Si je lis bien l'algorithme, cela devrait être un exemple de son fonctionnement:
Tout d'abord,
X
est la racine, elle est donc initialisée en tant quecurrent
.X
a un enfant à gauche, ilX
devient donc l'enfant le plus à droite duX
sous-arbre gauche de 's - le prédécesseur immédiat deX
dans une traversée en ordre. AinsiX
est fait le bon enfant deB
, puiscurrent
est mis àY
. L'arbre ressemble maintenant à ceci:(Y)
ci-dessus fait référence àY
et à tous ses enfants, qui sont omis pour les problèmes de récursivité. La partie importante est de toute façon répertoriée. Maintenant que l'arbre a un lien vers X, le parcours continue ...Puis
A
est sorti, car il n'a pas d'enfant gauche, etcurrent
est retourné àY
, qui a été crééA
pour l'enfant droit dans l'itération précédente. À l'itération suivante, Y a les deux enfants. Cependant, la double condition de la boucle la fait s'arrêter lorsqu'elle atteint elle-même, ce qui indique que son sous-arbre gauche a déjà été traversé. Ainsi, il s'imprime et continue avec son sous-arbre droit, qui estB
.B
s'imprime, puiscurrent
devientX
, qui passe par le même processus de vérification que l'aY
fait, réalisant également que son sous-arbre gauche a été traversé, en continuant avec leZ
. Le reste de l'arbre suit le même schéma.Aucune récursivité n'est nécessaire, car au lieu de s'appuyer sur un retour en arrière à travers une pile, un lien vers la racine du (sous) arbre est déplacé vers le point auquel il serait accessible dans un algorithme de traversée d'arbre inorder récursif de toute façon - après son le sous-arbre de gauche est terminé.
la source
Le traversal dans l'ordre récursif est:
(in-order(left)->key->in-order(right))
. (c'est similaire à DFS)Lorsque nous faisons le DFS, nous devons savoir vers où revenir en arrière (c'est pourquoi nous gardons normalement une pile).
En passant par un nœud parent vers lequel nous devrons revenir en arrière -> nous trouvons le nœud à partir duquel nous devrons revenir en arrière et mettre à jour son lien vers le nœud parent.
Quand on fait marche arrière? Quand on ne peut pas aller plus loin. Quand on ne peut pas aller plus loin? Quand aucun enfant n'est présent.
Où revenons-nous? Avis: au SUCCESSEUR!
Ainsi, lorsque nous suivons les nœuds le long du chemin de l'enfant gauche, définissez le prédécesseur à chaque étape pour qu'il pointe vers le nœud actuel. De cette façon, les prédécesseurs auront des liens vers les successeurs (un lien pour revenir en arrière).
Nous suivons à gauche tant que nous le pouvons jusqu'à ce que nous ayons besoin de revenir en arrière. Lorsque nous avons besoin de revenir en arrière, nous imprimons le nœud actuel et suivons le bon lien vers le successeur.
Si nous venons de revenir en arrière -> nous devons suivre le bon enfant (nous en avons terminé avec l'enfant gauche).
Comment savoir si nous venons de revenir en arrière? Récupérez le prédécesseur du nœud actuel et vérifiez s'il a un bon lien (vers ce nœud). Si c'est le cas, nous l'avons suivi. supprimez le lien pour restaurer l'arborescence.
S'il n'y avait pas de lien gauche => nous n'avons pas fait marche arrière et devrions continuer en suivant les enfants laissés.
Voici mon code Java (Désolé, ce n'est pas du C ++)
la source
J'ai fait une animation pour l'algorithme ici: https://docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing
Cela devrait, espérons-le, aider à comprendre. Le cercle bleu est le curseur et chaque diapositive est une itération de la boucle while externe.
Voici le code pour morris traversal (je l'ai copié et modifié à partir de geeks pour geeks):
la source
print(cursor.value)
après lapre.right = None
ligneJe pense que ce code serait mieux à comprendre, utilisez simplement un null pour éviter les boucles infinies, ne devez pas utiliser la magie autrement. Il peut être facilement modifié en précommande.
la source
temp.left = null
arbre sera perdu.J'ai trouvé une très bonne explication picturale de Morris Traversal .
la source
J'espère que le pseudo-code ci-dessous est plus révélateur:
En se référant au code C ++ de la question, la boucle while interne trouve le prédécesseur dans l'ordre du nœud actuel. Dans une arborescence binaire standard, le bon enfant du prédécesseur doit être nul, tandis que dans la version threadée, le bon enfant doit pointer vers le nœud actuel. Si le bon enfant est nul, il est défini sur le nœud actuel, créant effectivement le thread , qui est utilisé comme un point de retour qui devrait autrement être stocké, généralement sur une pile. Si le bon enfant n'est pas nul, alors l'algorithme s'assure que l'arborescence d'origine est restaurée, puis continue la traversée dans le sous-arbre de droite (dans ce cas, on sait que le sous-arbre de gauche a été visité).
la source
Complexité temporelle de la solution Python: O (n) Complexité spatiale: O (1)
Excellente explication de la traversée de Morris Inorder
la source
Explication PFB de Morris In-order Traversal.
la source