遍历二叉树(非递归)
来源:互联网 发布:淘宝电动车加热手套 编辑:程序博客网 时间:2024/06/05 11:11
遍历二叉树(非递归)
博客链接:递归遍历二叉树
语言 = C++;
博客摘要:
回顾层序遍历的遍历方式
采用非递归的方式遍历二叉树;
主要三种遍历方式:
@前序遍历
@中序遍历
@后序遍历
在上一篇博客中我们提到了遍历二叉树的四种方式:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
其中前三种采用了递归的方式,而层序遍历采用的是非递归的方式,为我们本篇博客坐了铺垫;
我们先来回忆一下层序遍历的大体过程;
用队列辅助存储结点的方式遍历,(具体过程请阅读博客:二叉树的遍历(递归));
代码回顾:
层序遍历 void LevelOrder1() { queue<Node> q; //队列(先进先出) q.push (_root); //根结点先存入队列 while(!q.empty ()) { Node top = q.front (); cout<<top->_data <<" "; q.pop (); if(top->_left ) q.push (top->_left ); if(top->_right ) q.push (top->_right ); } }
既然层序遍历可以通过队列的辅助实现,那么,前三种方式是不是也可以借助一种容器实现呢?
回忆一下我们学习递归的时候经常说得,递归的过程其实和压栈的过程差不多,我们是不是可以利用栈来实现非递归的二叉树遍历呢?
来试一下:
还是以这棵二叉树为例:
- 前序遍历:
依然按照先访问当前结点,再访问左孩子,最后访问右孩子的顺序来实现!
如果借助栈来实现的话我们需要先访问当前结点,然后当前结点压栈(因为最后访问右孩子的时候需要)再指向当前结点的左孩子,直到左孩子为NULL;
再从栈顶取出元素访问右孩子(为什么从栈顶取? 因为栈顶是最后一个压入栈的结点说明她的左孩子为NULL,当前结点也访问过了,就该访问右孩子了),注意:这里右孩子也有可能有左右孩子,就得注意这里的循环问题了!!!
具体实例分析过程: (还是上图的例子)
先看代码再看分析过程:
void PrevOrder2()//前序遍历非递归 { stack<Node> s;//栈 Node cur = _root;//根结点 while(cur || !s.empty ()) { while(cur) { cout<<cur->_data <<" "; s.push (cur ); cur = cur->_left ; } Node tmp = s.top (); s.pop(); cur = tmp->_right; } cout<<endl; }
**首先我们有了一个栈:s
接着我们需要得到根节点cur = _root(根节点);**
正式开始:
如果当前的结点cur不为NULL或者栈不为NULL(需要用循环控制)我们就可以继续接下来的过程了;
为什么?
如果当前结点为NULL并且栈也为NULL的话,我们好像没有什么可以继续访问的不是吗?
如果 cur不为NULL的话(又是一个内循环),我们就先访问cur->_data( 1 )(先序遍历),然后指向 1 的节点指针入栈,然后让当前结点指向它的左孩子(2);cur = cur->_left;这就是前面设置循环条件为当前结点左孩子不为null的原因;
接下来直接用节点数据代表cur; 2不为NULL,2入栈,cur指向3;
3不为NULL,3入栈,cur指向3的左孩子即NULL;
cur指向NULL,可以访问栈顶元素的右孩子了,即3的右孩子,记得pop()掉栈顶元素,因为它的自身,左右孩子都访问过了;
cur = s.top()->_right;因为3的右孩子也可能有节点;
比如下图:
所以把3的右结点又当成是一个根节点来循环;
还是以第一个二叉树继续,cur为NULL,直接跳过内层循环,取栈顶元素2(3已经在上次循环时Pop()掉了);访问2的右孩子,pop栈顶元素,即又将2的右孩子当作根节点,cur = cur->_right;
cur指向4,进入内层循环,访问4;4入栈,cur = cur->_left;
cur为NULL,跳出内层循环,取栈顶元素4,pop栈顶元素,访问4的右孩子,cur = cur->_right;
cur为NULL,跳过内层循环,取栈顶元素1,cur指向它的右孩子,cur = cur->_right;
cur指向5,进入内层循环,访问5,5入栈,cur = cur->_left;
cur指向6,访问6,6入栈,出内层循环,取栈顶元素6,pop栈顶元素,cur = cur->_right;
cur为NULL, 跳过内层循环,取栈顶元素5,pop栈顶元素,cur = cur->_right;
cur为NULL并且栈为NULL, 结束!
以上就是走了一次前序遍历的非递归,中序遍历以此类推;
//中序遍历的非递归实现 void Inorder2() { stack<Node> s; Node cur = _root; while(cur || !s.empty ()) { while(cur) { s.push (cur ); cur = cur->_left ; } Node tmp = s.top (); cout<<tmp->_data <<" "; s.pop(); cur = tmp->_right; } cout<<endl; }
中序遍历可以以此类推,而后序遍历没有以此类推的原因是,后序遍历略微有坑;
下面我们就详细讲述一下后序遍历;
结合代码看解释更好理解!!!
//后序遍历非递归 void _PostOrder3(Node root) { stack<Node> s; Node cur = _root; //保存当前结点 Node prev = NULL; //保存访问的前一个结点 while(cur || !s.empty ()) { while(cur) { s.push (cur); cur = cur->_left ; } Node top = s.top (); //判断当前结点是否可以访问的两个限定条件 if(top->_right == NULL || top->_right == prev) { cout<<top->_data <<" "; prev = top; //注意更新前一个访问的结点; s.pop (); } //否则说明当前结点的右孩子还没有访问过; else cur = top->_right ; } }
后序遍历的规则在于,先左后右,最后当前结点;那么我们用栈存储结点访问时,就会出现一种情况,比如不知道当前结点是否可以访问,因为有两种情况可以退回当前结点,比如刚访问过它的左孩子,退回到当前结点,然后去访问它的右孩子,又会退回到当前结点,所有就会出现这个
矛盾;
具体点的比如,下图:
当访问过3后,退回到2,2不能访问,因为要访问2的右孩子,那么去访问4,又退回2,那么问题来了,编译器可不知道你刚才访问的是你的左孩子还是右孩子;
这就需要我们想一种方法让程序知道当前结点是否可以访问了,还是上图为例,如果我们知道当前结点访问的上一个结点是什么,再与要访问的下一个结点比较,如果和下一个结点相同的话,就代表当前结点可以访问了,当然,还有还得判断一种为NULL的情况;
比如:后序遍历访问到4的时候,前一个访问的是3,而4的右孩子为NULL,岂不是不能访问4了,所以,我们还得考虑右孩子为NULL的情况;
后序遍历的演示过程就省略了,压栈过程借鉴前序遍历;
- 二叉树遍历(递归,非递归)
- 二叉树遍历(递归、非递归、Morris遍历)
- 二叉树遍历(递归、非递归、Morris遍历)
- 二叉树的遍历(递归+非递归+层次遍历)
- 二叉树遍历(非递归)
- 二叉树的遍历(非递归)
- 二叉树遍历(非递归版)
- 二叉树遍历(非递归算法)
- 二叉树遍历(前中后层序/非递归)
- 二叉树遍历算法(非递归)
- 二叉树的遍历(非递归)
- 二叉树的遍历(非递归)
- 二叉树遍历(前中后层序/非递归)
- 遍历二叉树(非递归)
- 二叉树遍历(非递归)
- 二叉树遍历(非递归版)
- 二叉树遍历-前序中序(非递归)
- 二叉树非递归遍历(C++)
- 无监督特征学习——Unsupervised feature learning and deep learning
- 小议使用trigger()的主动触发模拟点击的使用
- POJ做题顺序
- 从上到下打印二叉树——层序遍历二叉树
- 76. Minimum Window Substring(贪心,滑动窗口实现,hard)
- 遍历二叉树(非递归)
- 监督式和非监督式机器学习算法
- python基础
- 做个JDBC访问MySQL的通用BaseDao
- [POJ 1625] Censored! (AC自动机+DP+高精度)
- 47. Permutations II
- CSDN-markdown编辑器语法总结
- http://www.cnblogs.com/exmyth/p/4555814.html
- XML基本语法