逆转链方法遍历二叉树 不使用栈与递归 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算法也是如此。
采用逆转链的方法,即在遍历深入时,每深入一层,就将其再深入的孩子结点的地址取出,并将其双亲结点的地址存入,// 当深入不下去需返回时,可逐级取出双亲结点的地址,沿原路返回。
二、 Morris方法
原理: 每个有左分支的子树,该树根的前缀为左子树的最右侧叶子节点。刚进入该子树时,先将该叶子节点的 rightchild 指针 rc 指向子树的树根 p, 对p 从左子树向右子树遍历时,如果叶子指向 p ,则表示该子树已经遍历完成。
以下为C++实现:
参考: /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; } } } }
相关文章推荐
- zz Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
- 二叉树前序、中序和后序的遍历方法(递归、用栈和使用线索化)
- 数据结构——使用非递归方法后序遍历二叉树
- C++使用递归和非递归算法实现的二叉树叶子节点个数计算方法
- 递归的一些方法使用:深度遍历路径中的所有文件和目录,删除一个有文件的文件夹
- java使用递归,非递归方式实现二叉树的三种常见遍历方式
- 二叉树的遍历方法及递归实现
- 用递归方法对二叉树进行先序、中序和后序遍历
- 先序,中序,后序,求叶子结点数,深度,拷贝,几种二叉树的常见递归使用方法
- Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
- 二叉树的递归和非递归遍历方法
- Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
- 用非递归方式实现二叉树的前、中、后三种遍历方法
- 用递归方法对二叉树进行先序、中序和后序遍历
- 二叉树的四种遍历 (六个方法递归 非递归都有 包含二叉树的创建java方法)
- [转载]Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)
- 带父节点的二叉树的后序遍历 不使用递归和stack
- Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间,修改二叉树)
- 二叉树的三种遍历方法(递归)
- iOS:二叉树多级表格的使用,使用三方库TreeTableView-master实现对json解析数据的递归遍历整理成树状结构