- 树: 树的结点包含一个数据元素及若干指向其子树的分支;
- 结点的度:结点拥有的子树的个数称为结点的度;
- 叶子或终端结点:度为0的结点被称为叶子或终端结点;
- 分支结点或非终端结点: 度不为0的结点称为非终端结点或分支结点;
- 内部结点: 除根结点之外,分支结点也被称为内部结点;
- 树的度: 树的度是树内各结点的度的最大值;
- 孩子结点和双亲结点: 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲;
- 兄弟结点: 同一个双亲结点的孩子称为兄弟结点;
- 祖先: 从根到该节点所经分支上的所有节点被称为结点的祖先;
- 子孙: 以某节点为根的子树中任一节点都称为该节点的子孙;
- 森林: 由m(m>0)棵互不相交的多颗树的集合称为森林;
树的表示
🎤树结构相比于线性结构来说,是极为复杂的,存储起来也更加的麻烦,实际中树有很多种表示方式,如:双亲表示法,左右孩子表示法、左孩子右兄弟表示法等等。我们这里就简单的了解其中最常用的左孩子右兄弟表示法。
左孩子右兄弟表示法, 顾名思义,也就是说有两个指针,一个指针指向左边第一个孩子,另外一个指针指向右边第一个兄弟。
typedef int DataType;
typedef struct TreeNode
{
struct TreeNode\* FirstChild;//指向左边第一个孩子,也叫左孩子
struct TreeNode\* NextBrother;//指向右边第一个兄弟结点,也叫右结点
DataType data;
}TNode;
这里只对树结构有一个简单的了解,重点在后面的二叉树。
二叉树的概念
概念
🎤二叉树是树结构的其中之一,它的特点是每个结点最多只有两颗子树(即二叉树中不存在度大于2的结点)
二叉树的模型
特殊的二叉树
- 🎤满二叉树: 一个二叉树,如果每一个层的结点数都达到2,则这个二叉树就是满二叉
树。那么满二叉树的结点个数就是2*K-1个(K为二叉树的层数)
- 🎤完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对
于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号
从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉
树。
存储结构
🎤二叉树的存储结构分为两种:一种是顺序存储结构;另一种是链式存储结构。
- 二叉树的顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树
会有空间的浪费。
- 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的
方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩
子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都
是二叉链,三叉链等后续会详细介绍。
二叉树链式结构的实现
🎤在下面,大家一定要自己动手去画递归图,这样能更加直观更加立体的了解二叉树是怎样递归的。
二叉链的表示
🎤二叉链也叫左右孩子表示法。 其中一个指针指向左孩子,另一个指针指向右孩子。
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode\* left;//左孩子
struct BinaryTreeNode\* right;//右孩子
BTDataType data;
}BTNode;
二叉树的遍历(前中后序)
🎤遍历二叉树,即按照某条搜索路径巡防树中的每个结点,使每个结点有且仅被访问一次。 二叉树的链式结构一般都是用左右孩子表示法来实现,再由二叉树的定义可得,二叉树是由三个基本单元组成的,分别为根节点,左子树和右子树。
若限定先左和先右的话,就有三种情况:前序(先根)遍历;中序(中根)遍历;后序(后根)遍历。 由递归的思想可以,前中后序的操作定义也基本有了一定的思路:
- 前序遍历的思路:(1)先访问根结点;(2)前序遍历左子树;(3)前序遍历右子树。
void PrevOrder(BTNode\* root)//前序
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
- 中序遍历的思路:(1)先中序遍历左子树;(2)访问根结点;(3)中序遍历右子树。
void InOrder(BTNode\* root)//中序
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
- 后序遍历的思路:(1)先后序遍历左子树;(2)后序遍历右子树;(3)访问根结点。
void PostOrder(BTNode\* root)//后序
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
树结点的个数
🎤分治思想,将大问题分成小问题解决,在二叉树里就是递归的思想
int TreeSize(BTNode\* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
叶子结点的个数
🎤度为0的结点被称为叶子或终端结点
int TreeLeafSize(BTNode\* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
层序遍历
🎤这里需要用到队列,最后我会把所有代码放在下面
🎤层序遍历的核心思想就是上一层出队的时候带着下一层所有的结点进队
//层序遍历,核心思想:上一层出队的时候带下一层的结点进队
void LevelOrder(BTNode\* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode\* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (root->left)
{
QueuePush(&q, front->left);
}
if (root->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
二叉树的销毁
🎤如果我们用前序或者中序来遍历销毁的话,会发现,有的结点找不到了,而后续遍历销毁却避开这个问题。
void TreeDestory(BTNode\* root)
{
if (root == NULL)
{
return;
}
TreeDestory(root->left);
TreeDestory(root->right);
free(root);
root = NULL;
}
代码
因为我们层序遍历中,用到了队,我就直接把队列的定义和实现的代码放了进来。 到这里,我相信大家对二叉树已经掌握了🎉🎉🎉,有错误大家可以指出哦,有疑问也可以问我,大家共同进步,后续会持续更新《数据结构》的相关内容,大家喜欢的话可以关注一下,😚😚😚下面是项目代码,大家参考一下。****
test.c
#include<stdio.h>
#include<stdlib.h>
#include"Queue.h"
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode\* left;//左孩子
struct BinaryTreeNode\* right;//右孩子
BTDataType data;
}BTNode;
void PrevOrder(BTNode\* root)//前序
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode\* root)//中序
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
void PostOrder(BTNode\* root)//后序
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
//分治思想--大问题分成小问题来解决,也就是递归
int TreeSize(BTNode\* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求叶子结点的个数
int TreeLeafSize(BTNode\* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
//层序遍历,核心思想:上一层出队的时候带下一层的结点进队
void LevelOrder(BTNode\* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode\* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (root->left)
{
QueuePush(&q, front->left);
}
if (root->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
//树的销毁--用后序的思想,一步一步销毁就行了
void TreeDestory(BTNode\* root)
{
if (root == NULL)
{
return;
}
TreeDestory(root->left);
TreeDestory(root->right);
free(root);
root = NULL;
}
int main()
{
BTNode\* A = (BTNode\*)malloc(sizeof(BTNode));
A->data = 'A';
![img](https://img-blog.csdnimg.cn/img_convert/39b8252fe94a86bc01134be32bc81732.png)
![img](https://img-blog.csdnimg.cn/img_convert/4e38cdd3ec57901aac0a6d8be97712ce.png)
![img](https://img-blog.csdnimg.cn/img_convert/84bd06f8cc9d86361555413833097447.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
y(root->right);
free(root);
root = NULL;
}
int main()
{
BTNode\* A = (BTNode\*)malloc(sizeof(BTNode));
A->data = 'A';
[外链图片转存中...(img-9ABZ3wWA-1714253179291)]
[外链图片转存中...(img-26qnCjhM-1714253179291)]
[外链图片转存中...(img-3R9BQANu-1714253179291)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**