前面介绍了一般的二叉树遍历方法,包括递归和迭代。这两种方法的空间复杂度都是O(n)。
二叉树遍历的另外一种方法:Morris遍历二叉树 的空间复杂度为O(1),并且还不改变原本的树结构,时间复杂度依然为O(n)。Morris的核心是利用叶子节点的空闲右孩子指针返回到根节点。
中序遍历
最初Morris是为中序遍历二叉树提出的方法,在中序的基础上可实现先序和后序遍历,这里先整理中序遍历。
中序遍历的顺序是: 左子树->根->右子树。在访问完根root的左子树以后要返回root再访问右子树。这里的做法是每次访问一个子树的时候将找到中序遍历下root的前序节点,并将该前驱节点的右孩子指向root节点,这样在访问该前驱节点之后就可以回到root节点进行访问。具体步骤:
如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空(第一次访问该前驱点时其右孩子必定为空),将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点(第二次访问该前前驱节点时其右孩子不为空并指向root),将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
重复以上1、2直到当前节点为空。
例如二叉树:
4
/ \
2 6
/ \ / \
1 3 5 7
寻找前驱的过程中:
节点 3 的右孩子应该指向节点 4;
节点 1 的右孩子应该指向节点 2;
节点 5 的右孩子应该指向节点 6;
在第二次访问的时候以上前驱的右孩子重新被置为NULL;
代码如下:
void Tree::MorrisInorder(){
Node *cur, *pre;
for(cur = root; cur != NULL; ){
if(cur->left == NULL){
std::cout << cur->data << " ";
cur = cur->right;
continue;
}
for(pre = cur->left; pre->right != NULL && pre->right != cur; pre = pre->right){}
if(pre->right == NULL){
pre->right = cur;
cur = cur->left;
}else{
pre->right = NULL;
std::cout << cur->data << " ";
cur = cur->right;
}
}
}
前序遍历
和中序遍历类似,只是访问节点的顺序不同,在第一次访问一颗子树时先要访问其根节点,因此 cout 部分被放到了第一次访问前区节点的 if 判断分支中,首先输出当前节点。具体步骤:
如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。输出当前节点(在这里输出,这是与中序遍历唯一一点不同)。当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。当前节点更新为当前节点的右孩子。
重复以上1、2直到当前节点为空。
代码如下:
void Tree::MorrisPreorder(){
Node *cur, *pre;
for(cur = root; cur != NULL; ){
if(cur->left == NULL){
std::cout << cur->data << " ";
cur = cur->right;
continue;
}
for(pre = cur->left; pre->right != NULL && pre->right != cur; pre = pre->right){}
if(pre->right == NULL){
pre->right = cur;
std::cout << cur->data << " ";
cur = cur->left;
}else{
pre->right = NULL;
cur = cur->right;
}
}
}
后续遍历
后序遍历比价复杂,首先需要建立一个虚拟的临时节点dummy,令其左孩子为root。上例中的后序遍历结果是:1 3 2 5 7 6 4. 可以拆解为:
1:最左下角的节点;
3 2:节点 2 3 的倒序;
5:右子树的最左下角的节点;
7 6 4:右边 4 6 7的倒序
于是可以在中序遍历中第二次访问到某个节点时,把它左儿子到它前驱的路径上的节点倒序打印,即可得到后序遍历结果。但是这样的话根节点到最右下角的路径将访问不到,因此添加了虚拟节点dummy,作为辅助节点。
具体步骤:
当前节点设置为临时节点dummy。
如果当前节点的左孩子为空,则将其右孩子作为当前节点。
如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。
重复以上1、2直到当前节点为空。
代码如下:
void Tree::MorrisReverse(Node *from, Node *to){
if(from == to)
return;
Node *x = from, *y = from->right, *z;
while(true){
z = y->right;
y->right = x;
x = y;
y = z;
if(x == to)
break;
}
}
void Tree::MorrisPrintReverse(Node *from, Node *to){
MorrisReverse(from, to);
for(Node *p = to; ; p = p->right){
cout << p->data << " ";
if(p == from)
break;
}
MorrisReverse(to, from);
}
void Tree::MorrisPostorder(){
Node dummy(0, NULL, root);
Node *cur = &dummy, *pre;
for(; cur != NULL; ){
if(cur->left == NULL){
cur = cur->right;
continue;
}
for(pre = cur->left; pre->right != NULL && pre->right != cur; pre = pre->right){}
if(pre->right == NULL){
pre->right = cur;
cur = cur->left;
}else{
pre->right = NULL;
MorrisPrintReverse(cur->left, pre);
cur = cur->right;
}
}
}
程序执行结果
和上节的遍历方法放在一起,主函数为:
#include<iostream>
#include<vector>
#include"BinTree.h"
using namespace std;
int main(){
vector<int> v;
for(int i = 0; i < 21; ++i)
v.push_back(i + 1);
Tree tree;
tree.buildTree(v);
tree.printTree();
// tree.printTree_level();
std::cout << "\n";
std::cout << "\n-------------Recursion Traversal-----------------";
std::cout << "\nRecursion Inorder: " << endl;
tree.RecursionInorder();
std::cout << "\nRecursion Preorder: " << endl;
tree.RecursionPreorder();
std::cout << "\nRecursion Postorder: " << endl;
tree.RecursionPostorder();
std::cout << "\n-------------Stack Iteration Traversal------------";
std::cout << "\nIteration Inorder: " << endl;
tree.StackInorder();
std::cout << "\nIteration Preorder: " << endl;
tree.StackPreorder();
std::cout << "\nIteration Postorder: " << endl;
tree.StackPostorder();
std::cout << "\n-------------Morris Traversal-------------------";
std::cout << "\nMorris Inorder: " << endl;
tree.MorrisInorder();
std::cout << "\nMorris Preorder: " << endl;
tree.MorrisPreorder();
std::cout << "\nMorris Postorder: " << endl;
tree.MorrisPostorder();
std::cout << endl;
return 0;
}
运行: