Morris Traversal遍历二叉树(非递归)

  这里介绍非递归法遍历二叉树,与使用stack递归不同,该方法只需要O(1)空间,而且同样可以在O(n)时间内完成。要使用O(1)空间进行遍历,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),由于不能用栈作为辅助空间。为了解决这个问题,Morris方法用到了(线索二叉树threaded binary tree)的概念。在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了。
  这里来看中序遍历,步骤如下:
  1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
  2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
   a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
   b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
  3. 重复以上1、2直到当前节点为空。

来看代码:

struct node
{
    int data;
    struct node *left, *right;
};

/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
struct node* newNode(int data)
{
    struct node* temp = (struct node*) malloc(sizeof(struct node));
    temp->data = data;
    temp->left = temp->right = NULL;
    return temp;
}

// Preorder traversal without recursion and without stack
void morrisTraversalPreorder(struct node* current)
{
    while (current)
    {
        // If left child is null, print the current node data. Move to
        // right child.
        if (current->left == NULL)
        {
            printf( "%d ", current->data );
            current = current->right;
        }
        else
        {
            // Find inorder predecessor 寻找该节点的前序节点
            struct node* pre = current->left;
            while (pre->right && pre->right != current)
                pre = pre->right;

            // If the right child of inorder predecessor already points to
            // this node
            if (pre->right == current)  //左子树已遍历完,从左子节点回来
            {
                pre->right = NULL;
                current = current->right;
            }

            // If right child doesn't point to this node, then print this
            // node and make right child point to this node
            else
            {
                printf("%d ", current->data);
                pre->right = current;
                current = current->left;
            }
        }
    }
}

// Inorder traversal without recursion and without stack
void morrisTraversalInorder(struct node* current)
{
    while (current)
    {
        // If left child is null, print the current node data. Move to
        // right child.
        if (current->left == NULL)
        {
            printf( "%d ", current->data );
            current = current->right;
        }
        else
        {
            // Find inorder predecessor
            struct node* pre = current->left;
            while (pre->right && pre->right != current)
                pre = pre->right;

            // If the right child of inorder predecessor already points to
            // this node
            if (pre->right == current)  //左子树已遍历完,从左子节点回来
            {
                printf("%d ", current->data);
                pre->right = NULL;
                current = current->right;
            }

            // If right child doesn't point to this node, then print this
            // node and make right child point to this node
            else
            {
                pre->right = current;
                current = current->left;
            }
        }
    }
}

之前提到Morris Traversal时间复杂度为O(n),为什么呢?
Quora上有个很好的解释:(http://www.quora.com/Why-does-the-Morris-in-order-traversal-algorithm-have-O-n-time-complexity
这里摘抄如下:
Each edge is traversed at most 3 times and there are n-1 edges in a tree, hence the O(n).
I think the part that is confusing you is the predecessor finding loop because it goes down the tree following the rightmost node.

/* Find the inorder predecessor of current */
pre = current->left;
while (pre->right != NULL && pre->right != current)
     pre = pre->right;

This full path is only processed twice:
when the current pointer reaches the node
when we have processed its left subtree

Also, the key is that this path is not processed again while we’re on the left subtree.
Maybe a larger tree makes it clearer.
1
2
3
4
5
6
7

1
/ \
2 3
/ \
4 5
/ \
6 7

Current is 1
Find the predecessor (pred) of 1. Start at 2, traverse edges 2 -> 5 -> 7.
Node 7 -> right points to 1
Current is 2
Find the pred of 2. Start at 4 and finish right away.
4 ->right = 2
Current is 4
No pred, print 4. current->right is 2
Current is 2 again
Find pred of 2. Start at 4 and finish right away.
4 -> right is 2, which we added before. clear it, print 2 and move to 2->right
Current is 5
Find pref of 5. It is 6
6 -> right = 5
Current is 6
No pred, print 6. current->right is 5
Current is 5 again
Same as with 2. Finds 6, clears 6->right, print 5 and move to 7
Current is 7
No pred, print 7 and move to 7 -> right which is 1
Current is 1 again
Find pref of 1. Traverse edges 2 -> 5 -> 7. Since 7->right is 1, print 1, move to 3
Current is 3
Print 3

  As I said, notice that the path 1 -> 2 -> 5 -> 7 is only calculated twice. When we move on node 1’s left subtree, we traverse it but only 1 edge at a time. So each edge is traversed at most 3 times.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

One2zeror

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值