本期带大家一起用C语言实现二叉树🌈🌈🌈
1、二叉树的定义
二叉树是一种特殊的树状数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点
二叉树的链式存储结构是指用 链表 来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三
个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址
2、二叉树 的结构
3、二叉树的实现
3.1 结构设计
typedef int BTreeDataType;
typedef struct BTreeNode
{
BTreeDataType val;
struct BTreeNode* right;
struct BTreeNode* left;
}BTNode;
3.2 手动构建二叉树
BTNode* CreatNode(BTreeDataType val)
{
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("root malloc fail");
return;
}
root->val = val;
root->left = NULL;
root->right = NULL;
return root;
}
BTNode* CreatBinaryTree()
{
BTNode* root1 = CreatNode(1);
BTNode* root2 = CreatNode(2);
BTNode* root3 = CreatNode(3);
BTNode* root4 = CreatNode(4);
BTNode* root5 = CreatNode(5);
BTNode* root6 = CreatNode(6);
BTNode* root7 = CreatNode(7);
root1->left = root2;
root1->right = root3;
root2->left = root4;
root2->right = root5;
root3->left = root6;
root3->right = root7;
return root1;
}
3.3 前序遍历
-
首先进行条件判断,如果当前节点
root
为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在先序遍历中,空节点也需要被遍历到。 -
若当前节点
root
不为空,则输出当前节点的值root->val
-
继续递归遍历当前节点的左子树,即调用
PrevOrder(root->left)
。 -
最后,递归遍历当前节点的右子树,即调用
PrevOrder(root->right)
。
通过递归的方式,先序遍历会按照先根节点、左子树、右子树的顺序遍历整个二叉树。在代码中,先打印当前节点的值,然后分别遍历左子树和右子树。
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
else
printf("%d ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
3.4 中序遍历
-
首先进行条件判断,如果当前节点
root
为空,即遍历到了空节点,输出"N "(表示Null)之后返回。这是因为在中序遍历中,空节点也需要被遍历到。 -
若当前节点
root
不为空,则递归遍历当前节点的左子树,即调用InOrder(root->left)
。 -
输出当前节点的值
root->val
-
最后,递归遍历当前节点的右子树,即调用
InOrder(root->right)
。
通过递归的方式,中序遍历会按照左子树、根节点、右子树的顺序遍历整个二叉树。在代码中,先遍历左子树,然后打印当前节点的值,最后遍历右子树。这样可以保证中序遍历的顺序性。
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}
3.5 后序遍历
后序遍历的访问次序是 先访问左子树,再访问右子树,再访问根节点
逻辑同前序遍历和中序遍历差不多
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->val);
}
3.4 层序遍历
层序遍历就是从第一层开始从左向右逐个遍历
那么当前结果就是1 2 3 4 5 6 7
层序遍历的话,需要一个队列来进行辅助
首先我们将root根节点放进去,然后判断队列当中是否为空,为空就跳出循环
不为空的话就将节点不为NULL的放进去,直到队列为空
void LevalOrder(BTNode* root)
{
if (root == NULL)
return;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->val);
if(front->left)
QueuePush(&q, front->left);
if(front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
核心思想就是出上一层带下一层
3.5 计算二叉树的节点数
这里我们可以有两种思路:
1、给定一个 size ,遍历二叉树,分别遍历左右子树,遍历过程中遍历到非空节点 size++,遍历到空,则返回 0。
2、二叉树的大小 = 根节点 + 左子树节点 + 右子树节点,将其分成多个子问题,递归求解。
思路1 size计数
使用局部变量size
来记录节点数量是不可行的。因为在递归调用的过程中,每一次递归都会创建一个新的函数栈帧,这意味着每个递归调用都有自己独立的size
变量,并且初始值都是0,无法准确计算出二叉树的大小。
为了解决这个问题,可以使用全局变量size
来记录节点数量。全局变量位于全局数据区,在整个工程内都有效。但需要注意的是,由于全局变量不会被销毁,如果多次调用计算节点数量的函数,size
会累加,可能导致错误的结果。因此,在每次调用之前,需要将size
重置为0,以确保准确计算二叉树的大小。
总结来说,局部变量不能满足统计二叉树节点数量的在这里插入代码片
需求,需要使用全局变量,并在每次调用之前将其重置为0。
int size = 0;
int BTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
size++;
BTreeSize(root->left);
BTreeSize(root->right);
return size;
}
思路2 递归实现
计算二叉树的节点数
如果root==NULL的话,那我们就返回0
不然的话我们就返回1+左子树的节点树+右子树的节点数
int BTreeSize(BTNode* root)
{
//return root == NULL ? 0 : 1 + BTreeSize(root->left) + BTreeSize(root->right);
if (root == NULL)
return 0;
return 1 + BTreeSize(root->left) + BTreeSize(root->right);
}
3.6 计算二叉树的高度
计算二叉树的高度
如果我们的root==NULL的话,那我们返回0
不然的话计算左子树的高度和右子树的高度
让最高的子树高度+1就是我们的二叉树高度
不过需要注意的是需要拿left_height和right_height
来计数,防止重复递归调用
不然到时候还得重复计算好多次,尤其是会重复计算下面的高度
可不是简单的二倍关系
nt BTreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int left_hight = BTreeHeight(root->left);
int right_hight = BTreeHeight(root->right);
return left_hight > right_hight ? 1 + left_hight : 1 + right_hight;
}
3.7 计算叶子节点的个数
计算叶子节点的个数
如果root==NULL,返回0
如果root->left == NULL && root->right == NULL
那就返回1
如果还不满足以上的条件话,那就返回左子树的叶子节点个数+右子树叶子节点个数
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
3.8 计算第K层节点数
计算第K层节点数
对于第一层,需要计算的是 第 k 层的节点个数;
对于第二层,需要计算的是 第 k - 1 层的节点个数;
对于第 k 层,计算的就是 第 1 层( 当前层数 ) 的节点个数。
如果我的root==NULL的话,那就返回0
如果我的root!=NULL而且k==1的话那就返回1
不然的话就返回左子树第k-1层节点的数目+右子树第k-1层节点的数目
int BTreeLevelSize(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BTreeLevelSize(root->left, k - 1) + BTreeLevelSize(root->right, k - 1);
}
3.9 查找某个值对应的节点
查找某个值对应的节点
如果root==NULL,返回NULL
如果root->val==val,返回root
既然root->val!=val,那么就去左右子树分别查找
如果在左子树查走到了,那么就返回左子树当中找到的那个节点
如果在右子树查走到了,那么就返回右子树当中找到的那个节点
至于为什么要判断左右子树查找到结果不为NULL呢
因为左子树如果结果是NULL,不然直接返回NULL
还有右子树没有查找
右子树的结果的话可以不用判NULL,因为这个时候根节点和左子树当中都没有找到val,那么就剩下右子树了
如果右子树还没有的话,那就没有,返回NULL,有的话就返回节点
BTNode* BTreeFind(BTNode* root, BTreeDataType val)
{
if (root == NULL)
return NULL;
if (root->val == val)
return root;
BTNode* leftNode = BTreeFind(root->left,val);
if (leftNode != NULL)
return leftNode;
BTNode* rightNode = BTreeFind(root->right, val);
if (rightNode != NULL)
return rightNode;
return NULL;
}
3.10 判断是否为满二叉树
和层序遍历的思想一样,需要一个队列来进行辅助
首先将根节点root放到队列当中
如果在中途遇到了NULL的话,那就跳出循环
如果没遇到NULL的话,那就带下一层子树进来
遇到NULL结束入队列,并且检查队列当中是否还有有效元素(NULL除外)
如果全是NULL,那就代表这个二叉树完全二叉树
不然的话就不是
bool IsComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if(root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
break;
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
3.11 二叉树的销毁
二叉树的销毁逻辑是和后序遍历一样的
void BTreeDestroy(BTNode* root)
{
if (root == NULL)
return;
BTreeDestroy(root->left);
BTreeDestroy(root->right);
free(root);
}
4、感谢与交流✅
🌹🌹🌹如果大家通过本篇博客收获了,对二叉树有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯