C++二叉树(一)

分享目标

  1. 二叉树、完全二叉树的定义
  2. 二叉树、完全二叉树的表示和存储
  3. 二叉树的层序遍历

二ヌ树的定义

二叉树(Binary tree)是每个结点最多只有两个子结点的树结构。

● 树中结点的度都不大于2

● 两个子结点分别称为树的左子结点和右子结点

● 两个子结点是有左右之分的

例如,图的树就是一棵二叉树。

2是1的左子结点,3是1的右子结点。

2、3的左右子结点都是空。

1、下图的树是二叉树吗?NO

二叉树所有结点的度都不大于2。而本题结点3的度是3,所有这不是二叉树。

2、下面两颗二叉树相同吗?不同

二叉树的子结点是有左右之分的。子结点换位置后得到的是一棵新的二叉树。

二叉树的基本形态

二叉树的基本性质(1)

二叉树的基本性质(2)

树的深度有两种不同的定义(一般题目中会说明)。

不同的深度定义,性质2的公式不相同。

如果树的深度的定义是从根结点到最远叶结点所经过的边的数量。(结点的数量。)

性质2:深度为h的二叉树最多有2h+1-1个结点(根结点深度为0)

根结点深度为0。那么深度为4的二叉树,最多有31(2h+1-1 = 24+1-1 = 31)个结点。其中第5层

最多有_16(2i-1 = 25-1=16) 个结点。

可以套用公式,也可以用画图的方式,从根结点开始一层一层添加结点,最后数结点总数。

二叉树的基本性质(3)

性质3:对于任何一棵二叉树,如果它的叶子结点个数为n0,度为2的结点数为n2,

那么一定满足:n0=n2+1

例如图,叶子结点有6个。两个分支的结点有5个:a、b、d、c、g。6=5+1,满足性质3的描述!

(这个公式的证明过程不要求掌握)

二ヌ树的表示和存储

二叉树一般使用孩子表示法

1、结点定义为结构体:(每个结点都包含:结点的值、左孩子和右孩子)
cpp 复制代码
struct tree node {
	//结点的值
	int leftchild,rightchild;//左右子结点在数组中的下标
	
	int data;

};
2、结点数组用来保存二叉树:
cpp 复制代码
tree node tree[maxn];//树
3、结点编码,并保存到数组:

· 结点的编码从1开始,编码i的结点保存在数组下标i。

· leftchild、rightchild保存左右子结点的下标(编号),如果没有子结点,赋值0或-1。

例如,图所示的二叉树,保存到数组之后:

满二叉树

所有叶结点的深度均相同,且所有非叶结点的子结点数量均为2的二叉树称为满二叉树。简而言之,满二叉树的每一层都完全填满。

一棵深度为h的满二叉树一定有2h+1-1个结点(根结点深度为0时)。

满二叉树也叫完美二叉树。

完全二ヌ树

对于一棵二叉树,除了最后一层外,每一层都被完全填满,并且最后一层所有结点都靠左且连续,这样的二叉树称为完全二叉树。

完全二叉树如果去掉最后一层的所有结点,得到的一定是一棵满二叉树。

注意,满二叉树也是一种完全二叉树。

完全二叉树的基本性质

完全二叉树的数组表示法

首先,完全二叉树可以使用二叉树的孩子表示法(结构体)。

其次,基于完全二叉树的基本性质,还有更高效简洁的表示法:数组表示法。

完全二叉树的数组表示法,就是使用数组表示和存储一个完全二叉树。

具体步骤:

第一步编号:根结点编号为1,其他结点从上到下、从左到右依次编号。

第二步保存:以编号为下标,依次把结点数据存放到一维数组中。

例如,使用数组表示法存储右图的完全二叉树:

第一步编号:根结点编号为1,其他结点从上到下、从左到右依次编号

第二步保存:以编号为下标,依次把结点数据存放到数组中

【注意】如果不是完全二叉树,不推荐使用数组表示法。

数组表示法的优点:可以方便地访问指定结点的父结点和子结点。

完全二叉树的基本性质

(1)编号×大于1的结点,它的父结点编号为x/2。

(2)结点x的左孩子是2x(2x <= n时),右孩子是2x+1(2x+1 <= n时)。

二叉树的深度

给定一棵二叉树,求该二叉树的深度。

二叉树深度的定义:从根结点到叶结点依次经过的结点形成树的一条路径。最长路径的结点个数(含根、叶结点)为树的深度。

【输入描述】第一行是一个整数n,表示二叉树的结点个数。二叉树结点编号从1到n,

根结点为1,n <= 10。接下来有n行,依次对应二叉树的n个结点。每行有两个整数,

分别表示该结点的左儿子和右儿子的结点编号。如果第一个(第二个)数为-1则表示没

有左(右)儿子。

【输出描述】输出一个整型数,表示树的深度。

本题要求根据题目给出的每个结点的左右子结点,存储这棵二叉树,然后求出树的深度。

树的深度可以使用深度优先搜索(DFS)遍历树来找到最长路径,就是树的深度。

· 从根结点出发

· 每走一层深度+1

· 所有结点深度的最大值就是树的深度

深搜找二叉树深度的具体步骤:
  1. 从根结点出发开始搜索(深度为1)

  2. 如果访问的是空节点(编号为-1),进行回溯。

  3. 如果访问的是一个非空结点:

    1)先记录并更新深度的最大值。

    2)然后递归搜索左右孩子(深度+1)。

    搜索完成后保存的深度的最大值就是树的深度。

1、定义结点(结构体)和数组

由于不需要考虑每个结点的值,因此结构体只需要存储每个结点的左右孩子即可。

cpp 复制代码
struct tree node {
	int ls,rs;//ls:左子结点编号,rs:右子结点编号

};

tree node tree [11] ;

数组的大小为n+1(结点编号从1开始)

2、实现dfs递归函数
cpp 复制代码
int ans;//结点深度的最大值

// id:当前结点的编号,depth:这个结点的深度
void dfs(int id, int depth) {
	if(id==1) return;//是空结点,回溯,试其他路径
	ans=max(ans,depth);//更新最大值
	// 分别对左右子结点进行dfs,传入子结点的编号和深度
	// 子结点的深度=当前结点深度+1
	dfs(tree[id].ls,depth+1);
	dfs(tree[id].rs,depth+1);
}
3、main函数

1)读入输入并保存到树(数组)

2)从根结点开始深搜

3)输出深度值

题目按顺序给出每个结点的左右孩子,因此读入和建树也非常方便,直接按顺序读入即可。

cpp 复制代码
int n;

// 读入和保存树
cin >> n;
for(int i=1;i <= n;i++)
	cin >> tree[i] .ls >> tree[i].rs;

// 从根结点开始深搜
//根结点编号1,深度1

dfs(1,1);

// 输出深度
cout << ans;
完整代码
cpp 复制代码
#include<iostream>
using namespace std;

struct tree node{
	int ls,rs;//ls:左孩子结点编号,rs:右孩子结点编号
};
tree_node tree[11];//数组长度n+1,节点编号从1 开始
int n,ans; // ans:结点深度的最大值
// dfs
// id:当前结点的编号,depth:这个结点的深度
void dfs(int id,int depth) {
	if(id ==- 1) return;//是空结点,回溯,试其他路径
	ans= max(ans,depth);//更新最大值
	// 分别对左右子结点进行dfs,传入子节点的编号和深度
	// 子节点的深度=当前结点深度+1
	dfs(tree[id] .1s,depth+1) ;
	dfs(tree[id].rs,depth+1) ;
}
int main(){
	// 读入和保存树
	cin >> n;
	for(int i=1;i <= n;i++)
		cin >> tree[i] .ls >> tree[i].rs;
	// 从根结点开始深搜
	dfs(1,1);//根结点编号1,深度1
	// 输出深度
	cout << ans;
	return 0;
}

二ヌ树的遍历

二叉树常用的遍历方式包括:

● 层序遍历

● 前序(先序)遍历

● 中序遍历

● 后序遍历

层序遍历:从二叉树根结点开始,从上至下逐层遍历,在同一层中,则按照从左到右的顺序访问结点。

层序遍历的实现

层序遍历的核心在于按照树的层次来访问结点。

与广搜(BFS)的原理相同,因此二叉树的层序遍历一般通过BFS实现。

1)使用队列(Queue)数据结构

2)按层处理结点:

  1. 根结点第一个入队。
  2. 每次从队列中取出一个结点,访问该结点的值。
  3. 将该结点的左、右子结点"先左后右"入队。
    重复步骤23,直到队列为空,即所有结点都被访问过。

二叉树的层序遍历,因此也被称为二叉树的广度优先遍历。

cpp 复制代码
#include<queue>
struct tree node {
	int ls,rs;
	int value;

};
tree node tree [MAXN];
queue<int> q;

本示例代码:
· 队列只保存结点的编号
· 编号0表示空结点
· 空结点不入队
cpp 复制代码
void bfs() {
	// 根结点,入队
	q.push (1) ;
	while(! q. empty () ) {
		// 队首出队
		int id = q.front ();
		q.pop () ;
		cout << tree[id].value << " ";
		//子结点(非空)先左后右,入队
		if(tree[id] .ls != 0)
			q.push (tree[id] .ls) ;
		if(tree[id].rs != 0)
			q.push (tree [id].rs) ;
	}

}
层序遍历的应用(一)

层序遍历求二叉树的深度

cpp 复制代码
struct tree node{

	int ls,rs;
	int value;
	int depth;//结点的深度
};

1、定义结点结构体 增加一个成员:结点的深度值

2、层序遍历:

  1. 根结点第一个入队。根结点深度为1。
  2. 每次从队列中取出一个结点。用这个结点的深度值更新深度的最大值。
  3. 将该结点的左、右子结点"先左后右"入队。并设置子节点深度=当前节点深度+1。
    重复步骤2、3,直到队列为空,即所有结点都被访问过。深度的最大值就是二叉树的深度。

简而言之,在遍历过程中为访问到的结点更新深度值,同时记录最大值。

层序遍历的应用(二)

层序遍历判断是否完全二叉树

根据完全二叉树的定义:除了最后一层外,每一层都被完全填满(每个结点都有左右子结点),并且

所有结点都尽可能地向左对齐。

在层序遍历中,遇到一个空结点,并且之后的所有结点都是空结点,那么这是完全二叉树。

反之,如果在遇到第一个空结点后,又出现了一个非空结点,那么这棵树就不是完全二叉树。

判断完全二叉树

给定一棵二叉树,请编程判断是否是完全二叉树。

完全二叉树的定义:对于一棵二叉树,除了最后一层外,每一层都被完

全填满,并且所有结点都尽可能地向左对齐。

【输入描述】第一行是一个整数n,表示二叉树的结点个数。二叉树结

点编号从1到n(1 <= n <= 10),根结点为1。接下来有n行,依次对应

二叉树的n个结点。每行有两个整数,分别表示该结点的左儿子和右儿

子的结点编号。如果第一个(第二个)数为-1则表示没有左(右)儿子。

【输出描述】是完全二叉树输出"YES",否则输出"NO"。

本题首先要求保存指定的二叉树,并判断是否完全二叉树。

应用层序遍历进行判断:遇到一个空结点,之后不再遇到非空结点,就是完全二叉树。

反之,在遇到一个空结点后,又出现了非空结点,那么就不是完全二叉树。

● 本题中空结点编号为-1。

● 实现时,层序遍历(广搜)过程中需要把输入的空结点也入队。

层序遍历判断完全二叉树的步骤:
cpp 复制代码
//是否完全二叉树
bool isComplettree = true;
//是否遇到空结点
bool metNull = false;
  1. 根结点第一个入队。
  2. 每次从队列中取出一个结点进行处理直到队列为空:
    1)如果这个结点是空节点,设标记metNull为true。
    2)否则不是空节点,先查看标记:
    ①如果metNull为true,则说明不是完全二叉树,设isComplettree为false。
    ②否则将该结点的左、右子结点"先左后右"入队。
实现层序遍历
cpp 复制代码
struct tree node {
	int ls,rs;

};
tree node tree [11] ;
queue<int> q;
int n;

//是否完全二叉树
bool isComplettree = true;
//是否遇到过空结点
bool metNull = false;
cpp 复制代码
void bfs () {
	// 根结点入队
	q.push (1) ;
	while(! q. empty () ) {
		int id = q.front ();
		q.pop () ;
		if(id ==- 1)//是空节点
			metNull = true;
		else {//不是空节点
					if(metNull){//并且之前已经遇到过空节点
					//这不是完全二叉树
						isComplettree = false;
						return;//层序遍历可以终止
					}
				// 先左后右,子结点(编号)入队
					q.push (tree [id] . ls) ;
					q.push (tree [id] .rs) ;
			}
	}
}

【注意】判断完全二叉树时,子节点不管是否为空都入队
	
	
突现主函数

main函数:

1)读入输入并保存到树

2)从根结点开始层序遍历

3)输出结果

cpp 复制代码
int main() {
	// 读入和保存树
	cin >> n;
	for(int i=1;i <= n;i++)
		cin >> tree[i].ls >> tree[i].rs;
	// 从根结点开始层序遍历
	bfs();
	// 输出结果
	if (isComplettree)
		cout << "YES";
	else
	
		cout << "NO";
	return 0;

}
完整代码
cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

struct tree node{
	int ls,rs;//ls:左孩子结点编号,rs:右孩子结点编号
}
tree_node tree [11];
queue<int>q;//为了节约空间,队列中只保存树的编号
int n;
bool isComplettree= true;//是否完全二叉树
bool metNull = false;//是否遇到过空结点

void bfs() {
	// 根结点,入队
	q.push (1) ;
	while(!q. empty () ) {
		int id = q.front ();
		q.pop() ;
		if(id ==- 1)//是空节点
			metNull = true;
		else{//不是空节点
			if(metNull){//并且之前已经遇到过空节点
				isComplettree = false;//判断出这不是完全二叉树
				return;//层序遍历可以终止
			}
			// 先左后右,子结点(编号)入队
			q.push (tree [id] . ls) ;
			q.push (tree [id] .rs) ;
		}
	}
}
int main() {
	// 读入和保存树
	cin >> n;
	for(int i=1;i <= n;i++)
		cin >> tree[i] .ls >> tree[i].rs;
	// 从根结点开始层序遍历
	bfs();
	// 输出结果
	if(isComplettree) cout << "YES";
	else cout << "NO";
	return 0;
}

本次分享的知识点

  1. 二叉树的定义和基本性质
  2. 二叉树的表示和存储
  3. 完全二叉树的定义与基本性质
  4. 完全二叉树的数组表示法
  5. 二叉树的层序遍历
    1、以下关于满二叉树的描述,哪一个是准确的?C

A、满二叉树的所有节点都有两个子节点

B、满二叉树的所有叶子节点都不在同一层

C、满二叉树的每一层都完全填满

D、满二叉树的节点数必须是2的幂

2、根结点的深度为1,具有61个结点的完全二叉树的深度为?D

A、7

B、8

C、5

D、6

【提示】可以用画图法,从根结点一层一层添加结点,累加结点数。

也可以直接用公式:深度为h的二叉树最多有2h-1个结点(根结点深度为1)。

二叉树的深度(层序遍历法)

给定一棵二叉树,要求用层序遍历的方式求该二叉树的深度。

二叉树深度的定义:从根结点到最远叶结点依次经过的结点个数(含根、叶结点)。

【输入描述】第一行是一个整数n,表示二叉树的结点个数。二叉树结点编号从1到

n,根结点为1,n <= 10。接下来有n行,依次对应二叉树的n个结点。每行有两

个整数,分别表示该结点的左儿子和右儿子的结点编号。如果第一个(第二个)数

为-1则表示没有左(右)儿子。

【输出描述】输出一个整型数,表示树的深度。

【输出样例】

3

2 3

-1-1

-1-1

【输入样例】

2

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

struct tree node{
	int ls,rs;//ls:左孩子结点编号,rs:右孩子结点编号
	int depth;//深度
};
tree node tree [11] ;
queue<int>q;//为了节约空间,队列中只保存树的编号
int n;
int ans;//深度最大值
void bfs() {
	// 根结点编号1,入队
	tree [1] .depth = 1;
	q. push (1) ;
	while(!q.empty () ) {
		int id = q.front () ;
		q.pop () ;
		
		int depth = tree[id] . depth;
		ans= max(ans, depth);//更新深度最大值
	
		// 先左后右,子结点(编号)入队
		if(tree[id] .1s != -1) {
			tree[tree[id].ls].depth=depth +1;//设置左子节点的深度
			q.push (tree [id] . 1s) ;
		}
		if(tree[id] .rs != -1) {
			tree[tree[id].rs].depth= depth+1;//设置右子节点的深度
			q.push (tree[id] . rs) ;
		}
	}
}
int main(){
	// 读入和保存树
	cin >> n;
	for(int i=1;i <= n;i++)
		cin >> tree[i] .ls >> tree[i].rs;
	// 从根结点开始层序遍历
	bfs();
	// 输出
	cout << ans << endl;
	return 0;
}
相关推荐
民乐团扒谱机1 小时前
【微实验】谐波乘积谱(HPS)算法深度解析:原理、数学与代码实现
开发语言·人工智能·python·算法·语音识别·音乐
报错小能手1 小时前
Go 语言结构 基础语法
开发语言·后端·golang
Lazionr1 小时前
stack与queue:底层实现与容器适配器
开发语言·数据结构·c++
Hello_Damon_Nikola2 小时前
CH573从入门到精通
c语言·开发语言·单片机·嵌入式硬件
Logintern092 小时前
Langgraph使用MemorySaver建立有记忆的图
开发语言·windows·python
豆沙沙包?2 小时前
C++~~~vector容器(p31-P40)
开发语言·c++
小小de风呀2 小时前
de风——【从零开始学习C++】(十八):AVL树——让二叉搜索树自己“站起来“的自平衡黑科技
c++·科技·学习
原野池予2 小时前
深入Java集合框架:数组与List互转底层原理剖析(JDK 8)
java·数据结构·list
必须会一定会2 小时前
GLM-5.3 迁移实战:强制思考、1M 上下文与官方编程基准
android·开发语言·kotlin