目录
[(1) 满二叉树](#(1) 满二叉树)
[(1) 求节点个数](#(1) 求节点个数)
引言
在我的博客 数据结构------树的三种表示方法 中,我们学习了一些有关树的知识,今天我们来学习一种特殊的树形结构------二叉树。
求点赞收藏评论关注!!!十分感谢!!!
二叉树
1.二叉树的定义
二叉树是由节点组成的树形结构,其中每个节点最多有两个子节点,分别称为"左子节点"和"右子节点"。在二叉树中,根节点没有父节点,而每个非根节点都有一个父节点。叶子节点是没有子节点的节点。
1.二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
如下图所示:
2.特殊的二叉树
(1) 满二叉树
一棵高度为h,且含有2^h-1个结点的二叉树称为满二叉树。即树中的每层都含有最多的结点。
满二叉树有如下几个特点:
叶子只能出现在最下一层。
非叶子结点的度一定是2。
在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
如下图所示:
(2)完全二叉树
完全二叉树是由满二叉树而引出来的。
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
满二叉树是一种特殊的完全二叉树。
完全二叉树有如下特点:
叶子结点只能出现在最下两层。
最下层的叶子一定集中在左部连续位置。
倒数第二层,若有叶子结点,一定都在右部连续位置。
如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。
同样结点数的二叉树,完全二叉树的深度最小。
如下图所示:
3.二叉树的性质
- 若规定根结点的层数为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;
}
结束语
磨磨蹭蹭写了很久,终于是把二叉树写完了。
树的介绍在这里:数据结构------树的三种表示方法
求点赞收藏评论关注!!!
感谢各位大佬的支持!!