数据结构——二叉树

目录

引言

二叉树

1.二叉树的定义

2.特殊的二叉树

[(1) 满二叉树](#(1) 满二叉树)

(2)完全二叉树

3.二叉树的性质

4.二叉树的存储方法

(1)顺序存储

(2)链式存储

5.二叉树的构建与销毁

(1)构建二叉树

(2)销毁二叉树

6.二叉树的遍历

(1)前序遍历

(2)中序遍历

(3)后序遍历

(4)层序遍历

7.二叉树的扩展功能

[(1) 求节点个数](#(1) 求节点个数)

(2)求叶子节点个数

(3)求第k层节点个数

(4)查找值为x的节点

(5)求二叉树高度

(6)判断是否为完全二叉树

结束语


引言

在我的博客 数据结构------树的三种表示方法 中,我们学习了一些有关树的知识,今天我们来学习一种特殊的树形结构------二叉树

求点赞收藏评论关注!!!十分感谢!!!

二叉树

1.二叉树的定义

二叉树是由节点组成的树形结构,其中每个节点最多有两个子节点,分别称为"左子节点"和"右子节点"。在二叉树中,根节点没有父节点,而每个非根节点都有一个父节点。叶子节点是没有子节点的节点。

1.二叉树不存在度大于2的结点。

2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

如下图所示:

2.特殊的二叉树

(1) 满二叉树

一棵高度为h,且含有2^h-1个结点的二叉树称为满二叉树。即树中的每层都含有最多的结点

满二叉树有如下几个特点:

叶子只能出现在最下一层。

非叶子结点的度一定是2。

在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

如下图所示:

(2)完全二叉树

完全二叉树是由满二叉树而引出来的

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

满二叉树是一种特殊的完全二叉树。

完全二叉树有如下特点:

叶子结点只能出现在最下两层。

最下层的叶子一定集中在左部连续位置。

倒数第二层,若有叶子结点,一定都在右部连续位置。

如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。

同样结点数的二叉树,完全二叉树的深度最小。

如下图所示:

3.二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第 i 层上最多有 2^i -1个结点。

2.若规定根节点的层数为1,则深度为 h 的二叉树的最大结点数:

N=1+2+4+...+2^i -1=2^h -1

3.对任何一棵二叉树, 如果度为 0 其叶结点个数为 N0 , 度为 2 的分支结点个数为 N2 , 则有 N0=N2+1。

4.若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1)(ps: log2(n+1) 是log以2 为底,n+1为对数)。

5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对 于序号为i的结点有:

1). 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点。

2). 若2i+1,左孩子序号:2i+1,2i+1>=n否则无左孩子。

3). 若2i+2,右孩子序号:2i+2,2i+2>=n否则无右孩子。

第三点证明过程如下:

1.假设节点总数为 N ,如果度为0其叶结点个数为 N0 , 度为1的分支结点个数为 N1 ,度为2的分支结点个数为 N2 。

2.结点的总数N可以表示为:N=N0​+N1​+N2 ​。

3.在二叉树中,每个度为1的结点有1个分支,每个度为2的结点有2个分支。因此,二叉树的分支总数B可以表示为:B=N1​+2N2​ 。

4.在二叉树中,除了根结点外,每个结点都有一个分支进入。因此,分支的总数也可以表示为结点总数减去1,即:B=N−1 。

5.公式代入我们可以得到:N1​+2N2​=N0​+N1​+N2​−1 。

6.整理式子可以得出:N0​=N2​+1 。

4.二叉树的存储方法

(1)顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空 间的浪费。而现实中使用中只有堆才会使用数组来存储,堆我们会在后面学习到。

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

(2)链式存储

大多数情况下我们都采用链式存储,此处我们采用链式存储进行数据存储。

typedef struct BinaryTreeNode
{
	DataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

5.二叉树的构建与销毁

(1)构建二叉树
// 构建二叉树
BTNode* BinaryTreeCreate(DataType* a, int n, int* pi)
{
	// 如果当前元素是'#',表示该位置是空的,递增索引并返回NULL
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("malloc fail:");
		return NULL;
	}
	root->data = a[(*pi)++];
	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);
	return root;
}

这段代码的目的是根据一个给定的数组 a(其中包含二叉树节点的值,其中 '#' 字符表示空节点)和一个索引指针 pi(指向当前正在处理的数组元素的位置)来递归地构建一棵二叉树。

递归思路:

1.检查当前索引 *pi 所指向的数组元素是否为 '#'。如果是,说明该位置应该是一个空节点,函数递增索引 pi 并返回 NULL,表示当前子树的根节点为空。

2.如果当前位置不是空节点,代码将尝试为新的树节点分配内存。

3.在成功分配内存后,代码将当前索引 *pi 所指向的数组元素的值赋给新节点的 data 字段,并递增索引 pi 以准备处理下一个元素。

4.代码递归地调用 BinaryTreeCreate 函数来构建当前节点的左子树和右子树。

(2)销毁二叉树
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

6.二叉树的遍历

二叉树的遍历是二叉树的基本操作之一,它指的是按照某种规则访问二叉树中的所有节点,并且每个节点只被访问一次。

二叉树的遍历方式有多种,其中最常见的有四种:前序遍历(Pre-order Traversal)、中序遍历(In-order Traversal)、后序遍历(Post-order Traversal)和层序遍历(Level-order Traversal)。

(1)前序遍历

前序遍历的访问顺序为:根 左 右

递归思路:

1.访问根节点

2.前序遍历左子树

3.前序遍历右子树

代码如下:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N");
		return;
	}
	printf("%c", root->data);
	BinaryTreePrevOrder(root->left); 
	BinaryTreePrevOrder(root->right);
}
(2)中序遍历

中序遍历的访问顺序:左 根 右

递归思路:

1.中序遍历左子树

2.访问根节点

3.中序遍历右子树

代码如下:

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N");
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%c", root->data);
	BinaryTreeInOrder(root->right);
}
(3)后序遍历

后序遍历的访问顺序:左 右 根

递归思路:

1.后序遍历左子树

2.后序遍历右子树

3.访问根节点

代码如下:

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N");
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c", root->data);
}
(4)层序遍历

层序遍历的访问顺序:逐层访问

  • 从根节点开始,按层次从上到下、从左到右遍历二叉树

层序遍历通常使用队列来实现:

在二叉树的层序遍历中,我们需要按照层次逐层访问节点,并且保证每一层的节点都按照从左到右的顺序被访问。队列是一种先进先出(FIFO, First In First Out)的数据结构,它允许我们在一端添加元素(队尾),在另一端移除元素(队首)。这种特性与层序遍历的需求非常吻合。

代码如下:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue queue;
	QueueInit(&queue);
	// 如果根节点不为空,则将其加入队列
	if (root)
	{
		QueuePush(&queue, root);
	}
	// 当队列不为空时,继续遍历
	while (!QueueEmpty(&queue))
	{
		BTNode* tmp = QueueFront(&queue);
		printf("%c", tmp->data);
		// 将该节点从队列中移除
		QueuePop(&queue);

		// 如果该节点有左子节点,则将左子节点加入队列  
		if (tmp->left)
		{
			QueuePush(&queue, tmp->left);
		}
		// 如果该节点有右子节点,则将右子节点加入队列  
		if (tmp->right)
		{
			QueuePush(&queue, tmp->right);
		}
	}
	printf("\n");
	QueueDestroy(&queue);
}

层序遍历需要使用我们之前写过的队列,详情可以看看这篇文章:数据结构------链式队列和循环队列

7.二叉树的扩展功能

(1) 求节点个数

在这里我们使用递归调用来实现求节点个数的功能:

递归调用相加对于给定的二叉树节点,

如果节点为空(NULL),则返回0(表示没有节点)。

否则,返回1(表示当前节点本身)加上左子树和右子树的节点数(递归调用)

代码如下:

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) +
		BinaryTreeSize(root->right);
}
(2)求叶子节点个数

求叶子节点个数同样使用递归实现:

1.对于给定的二叉树节点,如果节点为空(NULL),则返回0(表示没有节点)。

2.如果节点左右孩子都为空,则返回1(表示当前节点为叶子节点)。

3.如果上面的两个return都没有执行,说明该节点既不为空也不是叶子节点,则返回左子树+右子树的叶子节点数(递归调用)。

代码如下:

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) +
		BinaryTreeLeafSize(root->right);
}
(3)求第k层节点个数

同样是使用递归的思路:

假设根结点为第一层,那么对于第一层,要求的是第k层;对于第二层,需要求的是第k-1层......逐层分解,对于第k层,需求的就是第一层。

1.如果 root 为 NULL,表示当前子树为空,无法再往下遍历,因此返回 0。

2.如果 k 等于 1,这意味着我们要找的是根节点所在的层,也就是第一层。由于根节点一定存在于第一层(如果树不为空),因此返回 1。

3.如果上述都没有执行,说明节点既不为空,也不是第k层,此时递归调用函数,返回该节点的左孩子的k-1层的节点数+右孩子的k-1层的节点数。

代码如下:

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1)
		+ BinaryTreeLevelKSize(root->right, k - 1);
}
(4)查找值为x的节点

遍历二叉树,寻找值为x的节点,思路如下:

1.检查传入的根节点 root 是否为 NULL。如果是,说明已经遍历到了树的底部或者树本身就是空的,此时肯定找不到值为 x 的节点,因此直接返回 NULL。

2.如果根节点不为空,则检查根节点的数据 root->data 是否等于要查找的值 x。如果相等,说明找到了目标节点,直接返回当前节点的指针 root。

3.调用函数获取左子树返回的值,如果该值不为空,说明获得了值为x的节点的地址,将该值返回给上一层 如果调用左子树未返回值,再调用函数获取右子树返回的值,如果改值不为空,说明获得了值为x的节点的地址,将该值返回给上一层。

4.若都没找到,则返回NULL。

代码如下:

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, DataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* n1 = BinaryTreeFind(root->left, x); 
	BTNode* n2 = BinaryTreeFind(root->right, x);
	if (n1)
	{
		return n1;
	}
	if (n2)
	{
		return n2;
	}
	return NULL;
}
(5)求二叉树高度

求二叉树的高度思路如下:

1.检查传入的根节点root是否为NULL。如果是,则意味着当前子树为空,其高度自然为0。因此,函数返回0。

2.递归计算左子树和右子树的高度。注意:求得的左右子树高度要记录下来,否则会出现重复计算的情况。

代码如下:

// 求树的高度
int BinaryHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	int leftHeight = BinaryHeight(root->left);
	int rightHeight = BinaryHeight(root->right);
	return leftHeight > rightHeight ? 
		leftHeight + 1 : rightHeight + 1;
}
(6)判断是否为完全二叉树

判断该二叉树是否为完全二叉树同样需要用到队列。思路如下:

通过层序遍历(使用队列)来判断一棵二叉树是否是完全二叉树。完全二叉树的定义是:除了最后一层外,每一层都是完全填满的,并且所有节点都尽可能地向左对齐。在层序遍历的过程中,如果遇到空节点,则后续的所有节点都应该是空节点,否则就不是完全二叉树。

代码如下:

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue queue;
	QueueInit(&queue);
	// 如果根节点存在,则将其加入队列
	if (root)
	{
		QueuePush(&queue, root);
	}
	while (!QueueEmpty(&queue))
	{
		BTNode* tmp = QueueFront(&queue);
		QueuePop(&queue);

		// 遇到空节点,就退出循环 
		if (tmp==NULL)
		{
			break;
		}
		QueuePush(&queue, tmp->left);
		QueuePush(&queue, tmp->right);
	}
	// 检查队列中是否还有非空节点  
	// 如果队列中还有节点,并且这些节点不是空节点,
	// 则不是完全二叉树
	while (!QueueEmpty(&queue))
	{
		BTNode* tmp = QueueFront(&queue);
		QueuePop(&queue);
		if (tmp)
		{
			QueueDestroy(&queue);
			return false;
		}
	}
	QueueDestroy(&queue);
	return true;
}

结束语

磨磨蹭蹭写了很久,终于是把二叉树写完了。

树的介绍在这里:数据结构------树的三种表示方法

求点赞收藏评论关注!!!

感谢各位大佬的支持!!

相关推荐
VertexGeek30 分钟前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
石小石Orz30 分钟前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
jiao_mrswang1 小时前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca2 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱2 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子2 小时前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
是阿建吖!2 小时前
【优选算法】二分查找
c++·算法
王燕龙(大卫)2 小时前
leetcode 数组中第k个最大元素
算法·leetcode
不去幼儿园3 小时前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh3 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法