[算法] 二叉树的 先序遍历、中序遍历、后序遍历
2018-02-27 16:28
441 查看
本文根据清华大学邓俊辉老师课程《数据结构》总结,课程地址 。
二叉树本身并不具有天然的全局次序, 故为实现遍历,需通过在各节点与其孩子之间约定某种局部次序, 间接地定义某种全局次序。
按惯例左兄弟优先于右兄弟, 若记做节点
部访问的次序可有
可以根据节点
下面说一下各个遍历的迭代式实现。
根据上面的代码,举个例子。
上图所示的二叉树遍历,流程描述如下:
从节点
弹出栈顶空节点,再弹出
将
将
栈为空,遍历结束。(其实上述描述的每一次循环都会做一次栈是否为空的检查)
同样需注意下图是最终返回的结果展示顺序,实现方法及流程并非如此。
根据上面的代码,举个例子。
上图所示的二叉树遍历,流程描述如下:
从节点
直至所有左分支节点处理完毕。(此时
弹出栈
转向
但这里
所以
弹出
此时,没有新的节点入栈,栈中也没有其他节点,终止遍历操作。
根据上面的代码,举个例子。
找到最高左侧可见叶节点
到此为止,第一次入栈操作结束,此时栈中顶而下依次为
接下来弹出栈顶元素
接下来是
接下来判断是否需要执行入栈,并不断从栈中弹出节点,并访问之;
最后,栈为空,遍历结束。
遍历介绍
按照事先约定的某种规则或次序,对节点各访问一次而且仅一次。与向量和列表等线性结构一样,二叉树的这类访问也统称为遍历(traversal)。二叉树本身并不具有天然的全局次序, 故为实现遍历,需通过在各节点与其孩子之间约定某种局部次序, 间接地定义某种全局次序。
按惯例左兄弟优先于右兄弟, 若记做节点
V,及其左、右孩子
L和
R,则下图所示,局
部访问的次序可有
V L R、
L V R和
L R V三种选择。根据节点
V在其中的访问次序,三种策略也相应地分别称作
先序遍历、
中序遍历和
后序遍历。
可以根据节点
V次序位置进行记忆,先序遍历中
V位于前端,中序遍历中
V位于中间,后序遍历中
V位于后端。
下面说一下各个遍历的迭代式实现。
先序遍历
通过先序遍历操作后,返回结果的顺序如下图所示。 注意下图是最终返回的结果展示顺序,实现方法及流程并非如此。C++实现代码如下:
//从当前节点出发,沿左分支不断深入,直至没有左分支的节点,沿途节点遇到后立即访问 template <typename T, typename VST> //元素类型、操作器 static void visitAlongLeftBranch(BinNodePosi(T) x, VST& visit, Stack<BinNodePosi(T)>& S) { while (x) { visit(x->data); //访问当前节点 S.push(x->rChild); //右孩子入栈暂存(可优化:通过判断,避免空的右孩子入栈) x = x->lChild; //沿左分支深入一层 } } template <typename T, typename VST> //元素类型、操作器 void travPre_I2(BinNodePosi(T) x, VST& visit) { //二叉树先序遍历算法(迭代版) Stack<BinNodePosi(T)> S; //辅助栈 while (true) { visitAlongLeftBranch(x, visit, S); //从当前节点出发,逐批访问 if (S.empty()) break; //直到栈空 x = S.pop(); //弹出下一批的节点 } }
根据上面的代码,举个例子。
上图所示的二叉树遍历,流程描述如下:
从节点
a出发,沿左分支不断深入,直至没有左分支的节点,沿途节点遇到后立即访问。首先
a的右节点
c直接进栈,然后访问左节点
b;
b的右节点直接进栈,此时其为空节点,所以空节点进栈,访问
b的左节点,也为空,直接进行下一步;
弹出栈顶空节点,再弹出
c,将
c的右节点
f直接进栈,并访问左节点
d;
将
d的右节点
e直接进栈,并访问左节点 ;
d的左节点为空。接下来弹出栈顶的
e,并将
e的右节点(空节点)直接进栈,访问
e的左节点;
e的左节点为空。接下来弹出栈顶的
f,并将
f的右节点(空节点)直接进栈,访问
f的左节点
g;
将
g的右节点(空节点)直接进栈, 访问
g的左节点;
g的左节点为空。弹出
g的右节点(空节点),再弹出
f的右节点(空节点);
栈为空,遍历结束。(其实上述描述的每一次循环都会做一次栈是否为空的检查)
中序遍历
通过中序遍历操作后,返回结果的顺序如下图所示。同样需注意下图是最终返回的结果展示顺序,实现方法及流程并非如此。
C++实现代码如下:
template <typename T> //从当前节点出发,沿左分支不断深入,直至没有左分支的节点 static void goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& S) { while (x) { S.push(x); x = x->lChild; } //当前节点入栈后随即向左侧分支深入,迭代直到无左孩子 } template <typename T, typename VST> //元素类型、操作器 void travIn_I1(BinNodePosi(T) x, VST& visit) { //二叉树中序遍历算法(迭代版) Stack<BinNodePosi(T)> S; //辅助栈 while (true) { goAlongLeftBranch(x, S); //从当前节点出发,逐批入栈 if (S.empty()) break; //直至所有节点处理完毕 x = S.pop(); visit(x->data); //弹出栈顶节点并访问之 x = x->rChild; //转向右子树 } }
根据上面的代码,举个例子。
上图所示的二叉树遍历,流程描述如下:
从节点
b出发,
b进栈
S。沿左分支不断深入,遇到节点则入栈;
直至所有左分支节点处理完毕。(此时
S中从上往下为
a、b);
弹出栈
S顶节点
a并访问之;
转向
a右子树。到此处截止,为一个循环体操作。接下来对
a右子树,对其重复循环体类似操作;
但这里
a右子树为空,所以继续弹出
b。转向
b右子树,对其进行重复循环体类似操作;
所以
f、d、c依次入栈,
c在栈顶。弹出
c,转向
c右子树,重复循环体;
c右子树为空。弹出
d,转向
d右子树,重复循环体。
e入栈,弹出
e,转向
c右子树,重复循环体,
c右子树为空;
弹出
f,转向
f右子树,重复循环体。
g入栈,
g出栈,转向
g右子树,为空;
此时,没有新的节点入栈,栈中也没有其他节点,终止遍历操作。
后序遍历
通过后序遍历操作后,返回结果的顺序如下图所示。C++实现代码如下:
template <typename T> //在以S栈顶节点为根的子树中,找到最高左侧可见叶节点 static void gotoHLVFL(Stack<BinNodePosi(T)>& S) { //沿途所遇节点依次入栈 while (BinNodePosi(T) x = S.top()) //自顶而下,反复检查当前节点(即栈顶) if (HasLChild(*x)) { //尽可能向左 if (HasRChild(*x)) S.push(x->rChild); //若有右孩子,优先入栈 S.push(x->lChild); //然后才转至左孩子 } else //实不得已 S.push(x->rChild); //才向右 S.pop(); //返回之前,弹出栈顶的空节点 } template <typename T, typename VST> void travPost_I(BinNodePosi(T) x, VST& visit) { //二叉树的后序遍历(迭代版) Stack<BinNodePosi(T)> S; //辅助栈 if (x) S.push(x); //根节点入栈 while (!S.empty()) { if (S.top() != x->parent) //若栈顶非当前节点之父(则必为其右兄),此时需 gotoHLVFL(S); //在以其右兄为根之子树中,找到HLVFL(相当于递归深入其中) x = S.pop(); visit(x->data); //弹出栈顶(即前一节点之后继),并访问之 } }
根据上面的代码,举个例子。
找到最高左侧可见叶节点
k,若有右子树优先入栈(此处为
j),但优先往左子树方向走(
i入栈);
i的右子树
h入栈,
i无左子树,所以继续对右子树
h进行操作;
h的右子树
g入栈,方向到左子树(
b入栈);
b的右子树
a入栈,
b无左子树。继续对
a进行操作,
a无子节点;
到此为止,第一次入栈操作结束,此时栈中顶而下依次为
abghijk;
接下来弹出栈顶元素
a,访问之;
b是
a的父节点,不用进行 入栈操作。弹出栈顶元素
b,访问之;
接下来是
g,非
b的父节点,执行入栈操作,按照1~5步骤说的方法,依次将
fedc入栈;
接下来判断是否需要执行入栈,并不断从栈中弹出节点,并访问之;
最后,栈为空,遍历结束。
相关文章推荐
- 小小c#算法题 - 11 - 二叉树的构造及先序遍历、中序遍历、后序遍历
- 算法:二叉树的先序遍历、中序遍历、后序遍历(递归及非递归方式)的java代码实现
- 数据结构--二叉树的创建、先序遍历、中序遍历、后序遍历、深度、叶子结点数
- 二叉树先序遍历,中序遍历,后序遍历,层次遍历。
- 已知一棵二叉树的中序遍历和后序遍历,求二叉树的先序遍历
- 二叉树先序建立,先序遍历,中序遍历,后序遍历(递归)
- c++实现二叉树的先序遍历,中序遍历,后序遍历(递归方法)及运行实例结果
- 数据结构与算法——二叉树的前序遍历,中序遍历,后序遍历
- 算法学习 - 表达树的建立(后缀表达式法),树的先序遍历,中序遍历,后序遍历(非递归)
- 已知一棵二叉树的中序遍历和后序遍历,求二叉树的先序遍历
- 二叉树先序遍历,中序遍历和后序遍历的非递归实现
- [leetcode]_根据二叉树的先序遍历(后序遍历) + 中序遍历 重建二叉树
- c++实现二叉树的先序遍历,中序遍历,后序遍历(递归方法)及运行实例结果
- 二叉树中的先序遍历、中序遍历和后序遍历关系
- 已知二叉树先序遍历,中序遍历创建二叉树并输出后序遍历
- java实现二叉树的先序遍历,中序遍历,后序遍历
- c++实现二叉树的先序遍历,中序遍历,后序遍历(递归方法)及运行实例结果
- 创建一个二叉树(先序遍历、中序遍历、后序遍历)
- 二叉树BiTree的先序遍历.中序遍历.后序遍历.结点数.叶子结点数和数的高度
- 二叉树的遍历(2)--先序遍历,中序遍历,后序遍历(非递归)