部分图文转自: http://hi.baidu.com/chenxiong0115/blog/item/2ace8512751e950f5baf53e5.html
参考: http://blog.youkuaiyun.com/billfranck/article/details/4525633
一、J.M.Robson方法
基于逆转链二叉树遍历思想,非递归无额外栈的二叉树遍历算法。
该算法是非递归的,指所有标题中无栈打了引号,是因为该算法其实是使用了栈的,只不过它充分利用了二叉树的叶子节点,算法中二叉树的叶子节点就是栈节点。
下图为实例与说明:
说明:
(a)初始化指向根的节点SuperRoot,cur指向当前访问节点,dad始终指向cur的父节点。cur一直向下访问,直到第一个叶子节点D,由于任一叶子节点X进栈的条件是:存在一个出度为2的节点Y,满足X在Y的左子树中;X进栈的时机是:从X处向上回溯,dad第一次遇到满足条件的节点Y。因此,用一临时指针av暂时保存将可能进栈的叶子节点X。
(b)中av指向节点D。
(c)中dad回溯至节点A,此时av指向的叶节点D入栈,并将D的右指针指向节点A,当dad再次回溯到节点A时就可根据栈指针标志判断出是从右子树回溯。
从图c到图d,cur转向dad的右子树继续向下遍历。图e,cur访问叶节点G,G为将可能入栈节点,用av标记。图f, dad回溯至节点C,节点G入栈,将G的右指针指向节点C。图f到图g,cur右转,遇叶节点F,用av标记,向上回溯。由于此时dad与当前栈顶右指针指向同一节点C,说明已从C左子树回溯过,本次是从C的右子树回溯,故退栈,接续向上回溯。图h中,因dad==top->LeftChild,退栈,继续向上回溯。如图i,dad回溯到SuperRoot,遍历完,然后释放节点SuperRoot在堆区的内存,算法结束。
设节点个数为N,该算法时间开销为O(N) 空间开销为O(1)。由于遍历时改变了树结构,在多线程环境中使用可要注意了。
非递归的二叉树遍历重点在于回溯,有两个问题:其一,需要找到父节点,其二,需要告知父节点,本节点是左子节点还是右子节点。第一个问题可以使用Parent指针,肯定比逆转链快,但增加了空间消耗。 第二个问题一般是用栈来解决,Robson算法也是如此。
采用逆转链的方法,即在遍历深入时,每深入一层,就将其再深入的孩子结点的地址取出,并将其双亲结点的地址存入,// 当深入不下去需返回时,可逐级取出双亲结点的地址,沿原路返回。
void intrace( Node* p){
if( NULL==p) return;
// dad:当前指针的父亲,top:最后一次访问的叶子(模拟的栈顶),deg2:第一个度为2的父节点
// av:可能的入栈叶子节点
Node *dad=p, cur=p->lc, top=NULL, deg2=NULL, av=NULL, tmp=NULL;
for(;;){
// if( NULL!=dad->lc && NULL!=dad->rc ) deg2 = dad; // 更新
if( cur ){
while ( cur->lc ){ // 向左走到底
// dad = p;
if( NULL!=dad->lc && NULL!=dad->rc ) deg2 = dad; // 更新deg2节点
tmp = cur->lc; curl->lc = dad; dad = cur; // 更新dad,设置lc指针为回溯指针
cur = tmp; // 向下遍历
}
cout << *cur << endl; // 访问该节点
if( cur-> rc ){ // 处理右子树
cur->lc = dad;
dad = cur; cur = cur->rc; // 转向右子树
}
else{ // cur为叶子节点. ??如何判断该
// av = cur;
if( deg2==top->rc ){ // 叶子从右子树进入
// 退栈
top = top->lc;
cur = deg2->rc; dad = cur->lc ; // 当前子树已经遍历完,退到更高一层
if( p==dad ) // 回到树根,所有树枝已经遍历,终止
break;
}
else{
// 更新top指针,并记录前一个top到左子树
tmp = top; top = cur; top->lc = tmp ;
// 叶子的右孩子指向deg2(或NULL),用于访问右子树时判断该deg2已经遍历完
cur->rc = deg2;
// 回溯并访问,直到deg2节点,然后计入上面的if(cur->rc)语句中
while( deg2!=cur ){
tmp = cur; cur = dad; cur->lc=tmp; // 回溯,并恢复父亲的左孩子
cout << *cur << endl; // 访问
}
dad = cur; cur = cur->rc ; // 转向右子树
}
}
}
else{ // 无左子树
// cout << *cur << endl;
cur = dad; dad = dad->lc; // 回溯到原始树根,dad指向NULL
cout << *cur << endl;
cur=cur->rc;
}
}
}
二、 Morris方法
原理: 每个有左分支的子树,该树根的前缀为左子树的最右侧叶子节点。刚进入该子树时,先将该叶子节点的 rightchild 指针 rc 指向子树的树根 p, 对p 从左子树向右子树遍历时,如果叶子指向 p ,则表示该子树已经遍历完成。
以下为C++实现:
void InOrder(Node* p, status (*vis)(Data) ){
Node *tmp=NULL;
while(NULL!=p){
if(NULL==p->lc){
vis(p->data);
p=p->rc; // 如果为叶子,则跳回其父节点
}
else{
tmp = p->lc; // p为根的左子树
while( NULL!=tmp->rc && p!=tmp->rc ) //左子树的最右叶子
tmp = tmp->rc;
if( NULL==tmp->rc ){
tmp->rc = p; // 将p的前驱指向p
p = p->lc ; // 遍历左子树
}
else{ // 左子树已经遍历完
vis(p->data);
tmp->rc = NULL; // 还原p的前驱叶子的右孩子指针
p = p->rc;
}
}
}
}
1425



被折叠的 条评论
为什么被折叠?



