Alguém pode me ajudar a entender o seguinte algoritmo de passagem de árvore inorder de Morris sem usar pilhas ou recursão? Eu estava tentando entender como funciona, mas está apenas me escapando.
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
Eu entendo que a árvore é modificada de forma que o current node
, é feito o right child
de max node
em right subtree
e usa essa propriedade para travessia inorder. Mas, além disso, estou perdido.
EDIT: Encontrado este código c ++ acompanhante. Eu estava tendo dificuldade em entender como a árvore é restaurada depois de modificada. A mágica está na else
cláusula, que é atingida assim que a folha certa é modificada. Veja o código para obter detalhes:
/* 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
fonte
fonte
pre->right = NULL;
Respostas:
Se estou lendo o algoritmo corretamente, este deve ser um exemplo de como funciona:
Primeiro,
X
é a raiz, portanto, é inicializado comocurrent
.X
tem um filho à esquerda, entãoX
é tornado o filho mais à direita daX
subárvore esquerda de - o predecessor imediato deX
em uma travessia inorder. EntãoX
é feita a criança direitaB
, entãocurrent
está definido paraY
. A árvore agora se parece com isto:(Y)
acima se refere aY
e todos os seus filhos, que são omitidos por problemas de recursão. A parte importante é listada de qualquer maneira. Agora que a árvore tem um link para o X, a travessia continua ...Em seguida,
A
é emitido, porque não tem filho esquerdo, ecurrent
é retornado paraY
, que foi tornadoA
filho direito na iteração anterior. Na próxima iteração, Y tem os dois filhos. No entanto, a condição dupla do loop o faz parar quando atinge a si mesmo, o que é uma indicação de que sua subárvore esquerda já foi percorrida. Então, ele se imprime e continua com sua subárvore certa, que éB
.B
imprime a si mesmo, e entãocurrent
se tornaX
, que passa pelo mesmo processo de verificação queY
fez, também percebendo que sua subárvore esquerda foi percorrida, continuando com oZ
. O resto da árvore segue o mesmo padrão.Nenhuma recursão é necessária, porque em vez de depender do retrocesso através de uma pilha, um link de volta à raiz da (sub) árvore é movido para o ponto em que seria acessado em um algoritmo de passagem de árvore de ordem recursiva de qualquer maneira - após seu a subárvore esquerda terminou.
fonte
A travessia de-ordem em recursiva é:
(in-order(left)->key->in-order(right))
. (é semelhante ao DFS)Quando fazemos o DFS, precisamos saber para onde voltar (é por isso que normalmente mantemos uma pilha).
À medida que passamos por um nó pai para o qual precisaremos retroceder para -> encontramos o nó do qual precisaremos retroceder e atualizar seu link para o nó pai.
Quando voltarmos? Quando não podemos ir mais longe. Quando não podemos ir mais longe? Quando nenhuma criança esquerda está presente.
Para onde voltamos? Aviso: ao SUCESSOR!
Assim, conforme seguimos os nós ao longo do caminho filho esquerdo, defina o predecessor em cada etapa para apontar para o nó atual. Dessa forma, os predecessores terão links para sucessores (um link para retrocesso).
Seguimos para a esquerda enquanto podemos até que precisamos voltar atrás. Quando precisamos voltar, imprimimos o nó atual e seguimos o link correto para o sucessor.
Se apenas recuamos -> precisamos seguir a criança certa (terminamos com a criança esquerda).
Como saber se acabamos de voltar? Obtenha o predecessor do nó atual e verifique se ele possui um link correto (para este nó). Se foi - então nós o seguimos. remova o link para restaurar a árvore.
Se não houvesse link para a esquerda => não retrocedemos e devemos seguir seguindo os filhos esquerdos.
Este é meu código Java (desculpe, não é C ++)
fonte
Fiz uma animação para o algoritmo aqui: https://docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing
Espero que isso ajude a entender. O círculo azul é o cursor e cada slide é uma iteração do loop while externo.
Aqui está o código para morris traversal (eu copiei e modifiquei de geeks para geeks):
fonte
print(cursor.value)
após apre.right = None
linhaAcho que seria melhor entender esse código, basta usar um nulo para evitar loops infinitos, não precisa usar mais magia. Ele pode ser facilmente modificado para pré-encomenda.
fonte
temp.left = null
árvore será perdida.Encontrei uma explicação pictórica muito boa de Morris Traversal .
fonte
Espero que o pseudo-código abaixo seja mais revelador:
Referindo-se ao código C ++ na questão, o loop while interno encontra o predecessor em ordem do nó atual. Em uma árvore binária padrão, o filho certo do predecessor deve ser nulo, enquanto na versão encadeada o filho certo deve apontar para o nó atual. Se o filho certo for nulo, ele é definido como o nó atual, criando efetivamente o threading , que é usado como um ponto de retorno que, de outra forma, teria que estar armazenado, geralmente em uma pilha. Se o filho certo não for nulo, o algoritmo garante que a árvore original seja restaurada e, a seguir, continua o percurso na subárvore direita (neste caso, sabe-se que a subárvore esquerda foi visitada).
fonte
Complexidade do tempo da solução Python: O (n) Complexidade do espaço: O (1)
Excelente explicação do Morris Inorder Traversal
fonte
Explicação PFB de Morris In-Order Traversal.
fonte