这里介绍非递归法遍历二叉树,与使用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.