二叉树的遍历——递归和非递归
二 叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有前序、中序以及后序三种遍历方法。因为树的定义本身就是 递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现。在三种遍历中, 前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。
一.前序遍历
前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。
1.递归实现
void pre_order(BTree *root)
{
if(root != NULL)//必不可少的条件,递归的出口
{
printf("%2c",root->key); //访问根结点
pre_order(root->lchild); //前序遍历左子树
pre_order(root->rchild); //前序遍历右子树
}
}
2.非递归实现
根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为NULL并且栈为空,则遍历结束。
//非递归前序遍历
void pre_order(BTree *root)
{
stack<BTree*> s;
BTree *p = root; //定义指针p并使树根指针为它的初值
//当栈非空或p指针非空时执行循环
while (p != NULL || !s.empty()) {
while(p != NULL) {
cout<<p->data<<" ";
s.push(p);
p = p->lchild;
}
if (!s.empty()) {
p = s.top();
s.pop();
p = p->rchild;
}
}
}
二.中序遍历
中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。
1.递归实现
void in_order(BTree* root)
{
//必不可少的条件,递归的出口
if(root != NULL)
{
in_order(root->lchild);
printf("%2c",root->data);
in_order(root->rchild);
}
}
2.非递归实现
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为NULL并且栈为空则遍历结束
//非递归中序遍历
void in_order(BTree *root)
{
stack<BTree*> s;
BTree *p = root;
while (p != NULL || !s.empty()) {
while(p != NULL) {
s.push(p);
p = p->lchild;
}
if (!s.empty()) {
p = s.top();
cout<<p->data<<" ";
s.pop();
p = p->rchild;
}
}
}
三.后序遍历
后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。
1.递归实现
void post_order(BTree* root)
{
//必不可少的条件,递归的出口
if(root != NULL)
{
post_order(root->lchild);
post_order(root->rchild);
printf("%2c",root->data);
}
}
2.非递归实现
后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。
第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问, 因此其右孩子还未被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就 保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是 否是第一次出现在栈顶。
方 法:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子 或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈 顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
//非递归后序遍历
void post_order(BTree* root)
{
stack<BTree*> s;
//当前结点
BTree *cur = NULL;
//前一次访问的结点
BTree *pre = NULL;
s.push(root);
while(!s.empty()) {
cur = s.top();
if( (cur->lchild == NULL && cur->rchild == NULL) ||
(pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
{
//如果当前结点没有孩子结点或者孩子节点都已被访问过
cout<<cur->data<<" ";
s.pop();
pre = cur;
} else {
if(cur->rchild != NULL)
s.push(cur->rchild);
if(cur->lchild!=NULL)
s.push(cur->lchild);
}
}
}
四、层次遍历
//采用STL中的queue处理
#include <queue>
void layerOrder(BTree *tree)
{
if (tree == NULL)
return;
queue<BTree *> q;
q.push(tree);
BTree *p = NULL;
while (!q.empty())
{
p = q.front();
visit(p);
q.pop();
if (p->lchild != NULL)
q.push(p->lchild);
if (p->rchild != NULL)
q.push(p->rchild);
}
}
五.二叉树的其他一些应用
1.求二叉树的深度
若一棵二叉树为空,则它的深度为0,否则它的深度等于左子树和右子树中的最大深度加1. 设nLeft为左子树的深度,nRight为右子树的深度,
则二叉树的深度为:max(nLeft , nRight)+1.
//树的深度
int TreeDepth(BTree* root)
{
int nLeft, nRight;
if(root == NULL)//必不可少的条件,递归的出口
return 0;
nLeft = TreeDepth(root->lchild);
nRight = TreeDepth(root->rchild);
return (nLeft > nRight) ? (nLeft + 1):(nRight + 1);
}
2.从二叉树中查找值为x的结点。若存在,则由x带回完整值并返回真,否则返回假
该算法类似于前序遍历,若树为空则返回false结束递归,若树根结点的值就等于x的值,则把结点值赋给x后返回true结束递归,否则先向左子树查找,若找到则返回true结束递归,否则再向右子树查找,若找到则返回true结束递归,若左,右子树均未找到则返回false结束递归。
struct BTreeNode
{
ElemType data; //结点值域
BTreeNode *left; //指向左孩子结点的值域
BTreeNode *right; //指向右孩子结点的值域
}
bool FindBTree(BTreeNode *BT , ElemType &x)
{
if(BT == NULL) //树为空返回假
return false;
if(BT->data == x) //树根结点的值等于x则由x带回结点值并返回真
{
x = BT->data;
return true;
}
else
{
//向左子树查找,若成功则继续返回真
if(FindBTree(BT->left , x))
return true;
//向右子树查找,若成功则继续返回真
if(FindBTree(BT->right , x))
return true;
//左,右子树查找均失败则返回假
return false;
}
}
3.统计出二叉树中等于给定值x的结点个数,结果由函数返回。
此算法也是一个递归过程,若树为空则返回0结束递归,若树根结点的值等于x的值则返回左、右两棵子树中等于x结点的个数加1,否则只应返回左、右两棵子树中等于x结点的个数。
int CountX(BTreeNode *BT , ElemType &x)
{
if(BT == NULL) //空树返回0
return 0;
if(BT->data == x)
return CountX(BT->left , x)+CountX(BT->right , x) + 1; //返回1加上两子树中的x结点数
else
return CountX(BT->left , x)+CountX(BT->right , x); //返回两子树中的x结点数
}
4.返回x结点所处的层号,若不存在值为x的结点则返回0.
int NodeLevel(BTreeNode *BT , ElemType &x)
{
//空树的层号为0
if(BT == NULL)
return 0;
//根结点的层号为1
if(BT->data == x)
return 1;
else
{
//求出x在左子树中的层号,返回该层号加1
int c1 = NodeLevel(BT->left , x);
if(c1 >= 1)
return c1+1;
//求出x在右子树中的层号,返回该层号加1
int c2 = NodeLevel(BT->right , x);
if(c2 >= 1)
return c2+1;
//在左、右子树中都不存在x结点则返回0
else
return 0;
}
}
5.从二叉树中找出所有结点的最大值并返回,若为空树则返回0.
ElemType MaxValue(BTreeNode *BT)
{
if(BT == NULL)
return 0; //空树返回0
ElemType k1 , k2;
k1 = MaxValue(BT->left); //求出左子树中的最大值
k2 = MaxValue(BT->right); //求出右子树中的最大值
if(k1 < k2)
k1 = k2; //两子树的最大值赋给k1
if(k1 > BT->data)
return k1;
else
return BT->data;
}
6.求二叉树中所有结点数。
int BTreeCount(BTreeNode *BT)
{
if(BT == NULL)
return 0;
else
return BTreeCount(BT->left) + BTreeCount(BT->right) + 1;
}
7.求二叉树中所有叶子结点数
int BTreeLeafCount(BTreeNode *BT)
{
if(BT == NULL)
return 0;
if(BT->left == NULL && BT->right == NULL)
return 1;
else
return BTreeLeafCount(BT->left) + BTreeLeafCount(BT->right);
}
- 【Golang语言社区】前端编程- 从零开始开发一款H5小游戏(一) 重温canvas的基础用法
- 今天聊聊分布式锁 No.86
- 【JS游戏编程基础】关于js里的this关键字的理解
- 【算法】随机森林算法
- 【Golang语言社区--H5编程】smoke.js
- GO语言标准库概览
- Golang测试技术
- 安装Python时遇到如下问题,解决方案
- spring使用Email邮件系统
- discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现
- mybatis 对于基本类型数据传值的问题
- Guava------------Cache使用方法
- hadoop安装及配置入门篇
- 剖析Go编写的Socket服务器模块解耦及基础模块的设计
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 常用的ClassLoader分析
- 如何实现自己的ClassLoader
- Hacking with iOS: SwiftUI Edition - 潜力客户名单项目(三)
- 大型项目技术栈第四讲 SQL语句构建器
- redis 入门(一)——Linux环境安装测试以及基本命令演示
- 大型项目技术栈第五讲 富文本编辑器
- weblogic 11g StuckThreadMaxTime 问题解决 以及 线程池、数据库连接池参数调优
- 大型项目技术栈第九讲 kaptcha的使用
- 大型项目技术栈第十讲 日志与性能监控
- Mybatis系列第三讲 Mybatis使用详解(1)
- Maven系列第二讲 安装、配置、mvn运行过程详解
- Maven第六讲 生命周期详解 高手必备!
- 鸿蒙 Ability 讲解(页面生命周期、后台服务、数据访问)
- Maven 项目第七讲 Maven插件
- weblogic Schema validation schemaValidationEnabled=false 启动报错解决