您的位置:首页 > 其它

逆转链方法遍历二叉树 不使用栈与递归 Robson方法 与 Morris方法

2012-05-07 11:58 375 查看
部分图文转自: http://hi.baidu.com/chenxiong0115/blog/item/2ace8512751e950f5baf53e5.html

参考: /article/10608643.html

一、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;
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐