Morris算法实现二叉树的遍历
我们知道,实现二叉树的前序(preorder)、中序(inorder)、后序(postorder)。遍历有两个常用的方法,一是递归 (recursive),二是栈 (stack+iterative)。这两种方法都是 O(n) 的空间复杂度。
而 Morris Traversal 只需要 O(1) 的空间复杂度。这种算法跟线索二叉树很像,不过 MorrisTraversal 一边建线索,一边访问数据,访问完后销毁线索,保持二叉树不变。
一、先序遍历
步骤:
- 初始化当前节点 cur 为 root 节点
- 如果 cur 没有左孩子,则输出当前节点并将其右孩子作为当前节点,即 cur = cur->rchild。
- 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角结点。
a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,输出当前节点(在这里输出,这是与中序遍历唯一的不同点),当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),当前节点更新为当前节点的右孩子。 - 重复 2、3 步骤,直到 cur 为空。
如图所示:当前节点为1(cur),有左孩子,则找前驱,即cur的左子树的最右下角的结点,遍历到6的位置,该结点右孩子为空,那么右孩子(即右孩子的指针指向)指向cur,可看到图中虚线,当前结点更新为cur的左孩子,即2的位置,后序操作类似,可根据图按照原理推导
`
void pre_order_morris(bt_node_t *root)
{
bt_node_t *cur;
cur = root;
while (cur != NULL)
{
if (cur->left == NULL )
{
cout<<cur->elem<<endl; //输出结点值
cur = cur->right;
}
else
{
/* 查找前驱 */
bt_node_t *node = cur->left;
while (node->right != NULL && node->right != cur)
node = node->right;
if (node->right == NULL )
{
/* 还没线索化,则建立线索 */
cout<<cur->elem<<endl; //输出结点值 /* 仅这一行的位置与中序不同 */
node->right = cur;
cur = cur->left;
}
else
{
/* 已经线索化,则删除线索 */
node->right = NULL;
/* prev = cur; 不能有这句,cur 已经被访问 */
cur = cur->right;
}
}
}
}
二、中序遍历
步骤:
- 初始化当前节点 cur 为 root 节点
- 如果 cur 没有左孩子,则输出当前节点并将其右孩子作为当前节点,即 cur = cur->rchild。
- 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角结点。
a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),输出当前节点,当前节点更新为当前节点的右孩子。 - 重复 2、3 步骤,直到 cur 为空。
如图所示,中序遍历和先序遍历基本原理差不多,大家可根据先序遍历的方法理解
void in_order_morris(bt_node_t *root)
{
bt_node_t *cur;
cur = root;
while (cur != NULL )
{
//没有左孩子
if (cur->left == NULL )
{
cout<<cur->elem; //输出结点的值
cur = cur->right;
}
else
{
/* 查找前驱 */
bt_node_t *node = cur->left;
while (node->right != NULL && node->right != cur)
node = node->right;
if (node->right == NULL )
{
/* 还没线索化,则建立线索 */
node->right = cur;
cur = cur->left;
}
else
{
/* 已经线索化,则访问节点,并删除线索 */
cout<<cur->elem<<endl; //输出结点值
node->right = NULL;
prev = cur;
cur = cur->right;
}
}
}
}
三、后序遍历
步骤:
- 初始化当前节点 cur 为 root 节点
- 如果 cur 没有左孩子,则将其右孩子作为当前节点,即 cur = cur->rchild。
- 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角点。
a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。 - 重复 2、3 步骤,直到 cur 为空。
后序遍历和前面有点不同,增加一个临时结点temp,并令左孩子指向根节点,如图结点0位temp,大致分析方法一致的,在图中第十个树中,from和to结点不同,那么需要倒序输出结点的值,具体可参照代码
void post_order_morris(bt_node_t *root)
{
bt_node_t dummy = { 0, NULL, NULL };
bt_node_t *cur, *prev = NULL;
dummy.left = root;
cur = &dummy;
while (cur != NULL )
{
if (cur->left == NULL )
{
prev = cur; /* 必须要有 */
cur = cur->right;
}
else
{
bt_node_t *node = cur->left;
while (node->right != NULL && node->right != cur)
node = node->right;
if (node->right == NULL )
{
/* 还没线索化,则建立线索 */
node->right = cur;
prev = cur; /* 必须要有 */
cur = cur->left;
}
else
{
/* 已经线索化,则访问节点,并删除线索 */
visit_reverse(cur->left, prev); // call print
prev->right = NULL;
prev = cur; /* 必须要有 */
cur = cur->right;
}
}
}
}
//逆序
static void reverse(bt_node_t *from, bt_node_t *to)
{
bt_node_t *x = from, *y = from->right, *z;
if (from == to) return;
while (x != to)
{
z = y->right;
y->right = x;
x = y;
y = z;
}
}
static void visit_reverse(bt_node_t* from, bt_node_t *to)
{
bt_node_t *p = to;
//逆序输出
reverse(from, to);
while (1)
{
cout<<p->elem<<endl; //输出结点值
if (p == from)
break;
p = p->right;
}
//逆序之后再还原原来的顺序
reverse(to, from);
}