¿Alguien puede ayudarme a comprender el siguiente algoritmo transversal de árbol de orden de Morris sin usar pilas o recursividad? Estaba tratando de entender cómo funciona, pero simplemente se me escapa.
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
Entiendo que el árbol se modifica de manera que current node
, se convierte en el right child
de max node
in right subtree
y usa esta propiedad para el recorrido en orden. Pero más allá de eso, estoy perdido.
EDITAR: Encontré este código c ++ adjunto. Me costaba entender cómo se restaura el árbol después de que se modifica. La magia está en la else
cláusula, que se activa una vez que se modifica la hoja derecha. Consulte el código para obtener más detalles:
/* 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
fuente
fuente
pre->right = NULL;
Respuestas:
Si estoy leyendo el algoritmo correctamente, este debería ser un ejemplo de cómo funciona:
Primero,
X
es la raíz, por lo que se inicializa comocurrent
.X
tiene un hijo izquierdo, por lo queX
se convierte en el hijo situado más a la derecha delX
subárbol izquierdo de, el predecesor inmediato deX
en un recorrido en orden. EntoncesX
se convierte en el hijo correcto deB
, luegocurrent
se establece enY
. El árbol ahora se ve así:(Y)
arriba se refiere ayY
todos sus elementos secundarios, que se omiten por problemas de recursividad. La parte importante se enumera de todos modos. Ahora que el árbol tiene un vínculo de regreso a X, el recorrido continúa ...Luego
A
se genera una salida, porque no tiene un hijo izquierdo, ycurrent
se devuelve aY
, que se hizoA
el hijo derecho en la iteración anterior. En la siguiente iteración, Y tiene ambos hijos. Sin embargo, la condición dual del bucle hace que se detenga cuando llega a sí mismo, lo cual es una indicación de que el subárbol izquierdo ya ha sido atravesado. Entonces, se imprime y continúa con su subárbol derecho, que esB
.B
se imprime y luego securrent
convierte enX
, que pasa por el mismo proceso de verificación que loY
hizo, y también se da cuenta de que su subárbol izquierdo ha sido atravesado, continuando con elZ
. El resto del árbol sigue el mismo patrón.No es necesaria la recursividad, porque en lugar de depender del retroceso a través de una pila, un enlace de regreso a la raíz del (sub) árbol se mueve al punto en el que se accedería en un algoritmo recursivo de recorrido de árbol en orden de todos modos, después de su el subárbol izquierdo ha terminado.
fuente
El en-orden de recorrido recursivo es:
(in-order(left)->key->in-order(right))
. (esto es similar a DFS)Cuando hacemos el DFS, necesitamos saber hacia dónde retroceder (por eso normalmente mantenemos una pila).
A medida que pasamos por un nodo principal al que tendremos que retroceder -> encontramos el nodo desde el que tendremos que retroceder y actualizar su enlace al nodo principal.
¿Cuándo retrocedemos? Cuando no podemos ir más lejos. ¿Cuándo no podemos ir más lejos? Cuando ningún niño dejado presente.
¿A dónde retrocedemos? Aviso: ¡al SUCESOR!
Entonces, a medida que seguimos los nodos a lo largo de la ruta secundaria izquierda, configure el predecesor en cada paso para que apunte al nodo actual. De esta forma, los predecesores tendrán enlaces a sucesores (un enlace para retroceder).
Seguimos a la izquierda mientras podamos hasta que necesitemos dar marcha atrás. Cuando necesitamos retroceder, imprimimos el nodo actual y seguimos el enlace correcto al sucesor.
Si acabamos de retroceder -> tenemos que seguir al niño derecho (hemos terminado con el niño izquierdo).
¿Cómo saber si acabamos de dar marcha atrás? Obtenga el predecesor del nodo actual y verifique si tiene un enlace correcto (a este nodo). Si es así, lo seguimos. elimine el enlace para restaurar el árbol.
Si no había un enlace izquierdo => no retrocedimos y deberíamos continuar siguiendo a los niños izquierdos.
Aquí está mi código Java (lo siento, no es C ++)
fuente
He hecho una animación para el algoritmo aquí: https://docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing
Con suerte, esto debería ayudar a comprender. El círculo azul es el cursor y cada diapositiva es una iteración del ciclo exterior while.
Aquí está el código para morris traversal (lo copié y modifiqué de geeks para geeks):
fuente
print(cursor.value)
después de lapre.right = None
líneaCreo que este código sería mejor de entender, solo use un nulo para evitar bucles infinitos, no tiene que usar magia más. Se puede modificar fácilmente para reservar.
fuente
temp.left = null
árbol se perderá.Encontré una muy buena explicación pictórica de Morris Traversal .
fuente
Espero que el pseudocódigo a continuación sea más revelador:
Refiriéndose al código C ++ en la pregunta, el bucle while interno encuentra el predecesor en orden del nodo actual. En un árbol binario estándar, el hijo derecho del predecesor debe ser nulo, mientras que en la versión con subprocesos, el hijo derecho debe apuntar al nodo actual. Si el hijo correcto es nulo, se establece en el nodo actual, creando de manera efectiva el subproceso , que se usa como un punto de retorno que de otra manera tendría que estar almacenado, generalmente en una pila. Si el hijo derecho no es nulo, entonces el algoritmo se asegura de que se restaure el árbol original y luego continúa el recorrido en el subárbol derecho (en este caso se sabe que se visitó el subárbol izquierdo).
fuente
Complejidad del tiempo de la solución de Python: O (n) Complejidad del espacio: O (1)
Excelente explicación transversal de Morris Inorder
fuente
Explicación del PFB del recorrido en orden de Morris.
fuente