1. Modify the BST During the Traversal
Use O(n) time and canstant space.
Idea:
Maintain two points of the nodes - parent and current. When processing, set parent.right to current.right, and set current.right to parent.
Java代码
void traverse (Node root) {
traverse (root.left, root);
}
void traverse (Node current, Node parent) {
while (current != null) {
if (parent != null) {
parent.left = current.right;
current.right = parent;
}
if (current.left != null) {
// go left until the left child is null
parent = current;
current = current.left;
} else {
// the nodes in the left subtree
// are all visited, print and go right.
print(current);
current = current.right;
parent = null;
}
}
}
示意图:
1.对左子树的处理
图中彩色虚线为更改之前的指针;
彩色实线为更改之后的指针。
2.对右子树的处理
执行过程中,节点left child的指针会依次指向其左子树中最右侧一列的每个节点。
Morris Traversal
参考:
http://comsci.liu.edu/~murali/algo/Morris.htm
这里有一个比较具体的解释:
http://stackoverflow.com/questions/5502916/explain-morris-inorder-tree-traversal-without-using-stacks-or-recursion
该算法需要O(n)时间和O(1)空间,且不会破坏BST的数据结构(在执行中会暂时改变个别节点的连接)。
主要思想是利用每个子树中最大(最右)的节点,该节点的right child一定为空,且在inorder traversal顺序中,该节点是该子树root节点的直接前驱。将该节点的right child暂时的设置为该子树的root节点,则在遍历过程中可以自然地从左子树过度到根节点,并递归地遍历右子树。
Reset the rightmost node in the current subtree(还原改变的节点): 只需在print某个节点时(第二次访问该节点),再次寻找该节点的直接前驱,并将其right child置为null。
// Morris Traversal
struct Node {
int data;
struct Node *left, *right;
int mark;
};
void MorrisTraversal(struct Node *root) {
struct Node *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 predecessor of current
// (rightmost node in the subtree)
pre = current->left;
while (pre->right != NULL && pre->right != current)
pre = pre->right;
if(pre->right == NULL) {
// firt visit the predecessor node
pre->right = current;
current = current->left;
} else {
// revisit, reset predecessor's right child
pre->right = NULL;
printf(" %d ", current->data);
current = current->right;
}
}
}
}
示意图
第一次访问的标志:pre->right == NULL
第一次访问节点0时,添加4->0的虚线所表示的指针(节点4的right child);
第一次访问节点1时,添加3->1的虚线所表示的指针;
第一次访问节点2时,添加5->2的虚线所表示的指针。
第二次访问的标志:pre->right == current
第二次访问节点0时,删除指针4->0;
第二次访问节点0时,删除指针3->1;
第二次访问节点0时,删除指针5->2。
时间复杂度
Mirros Traversal 花费O(n)时间,其中最关键的步骤在于它需要(两次)查找一个节点的直接前驱,对应的代码为
while (pre->right != NULL && pre->right != current)
pre = pre->right;
实际上,
查找每个节点的直接前驱所需遍历的边的数量 ~ 图中所有指向right child的边数 ~ N(# of nodes)
因此该步骤所需时间为O(n)。