数据结构与算法之《二叉树》详解(1)
二、二叉树的概念及结构
2、1 二叉树的概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
2、2 二叉树的特点
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
2、3 二叉树的结构(图片)
2、4 特殊的二叉树
- **满二叉树:**一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉 树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- **完全二叉树:**完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对 于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号 从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
三、二叉树的代码及思路实现
3、1 二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
3、1、1 二叉树的顺序存储结构
顺序结构存储就是使用
数组来存储
,一般使用数组
只适合表示完全二叉树
,因为不是完全二叉树 会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
3、1、2 二叉树的链式存储结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链。
3、2 二叉树链式结构的实现
二叉树的链式结构实现都有哪些模块呢?接下来我简单的给大家总结一下:
- 定义结构体;
- 自定义一个二叉树;
- 前序遍历;
- 中序遍历;
- 后序遍历;
- 求树中节点的个数;
- 求叶节点的个数。
接下来我们来看一下各个模块实现的细节以及详解。
3、2、1 定义结构体
定义结构体时,由上面的链式存储结构我们直到该结构体应该包含一个存储数据的变量,和指向左右分支节点的指针;我们看代码实现。
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
3、2、2 自定义一个二叉树
首先我们自己要有一个二叉树,简单的二叉树即可。因为下面的操作都是在二叉树上进行的。这里给出一个简单的二叉树,如下图及代码实现:
BTNode* A = (BTNode*)malloc(sizeof(BTNode));
A->data = 'A';
A->left = NULL;
A->right = NULL;
BTNode* B = (BTNode*)malloc(sizeof(BTNode));
B->data = 'B';
B->left = NULL;
B->right = NULL;
BTNode* C = (BTNode*)malloc(sizeof(BTNode));
C->data = 'C';
C->left = NULL;
C->right = NULL;
BTNode* D = (BTNode*)malloc(sizeof(BTNode));
D->data = 'D';
D->left = NULL;
D->right = NULL;
BTNode* E = (BTNode*)malloc(sizeof(BTNode));
E->data = 'E';
E->left = NULL;
E->right = NULL;
A->left = B;
A->right = C;
B->left = D;
B->right = E;
3、2、3 前序遍历
什么是前序遍历呢?我们先来看一下比较官方的解释。
NLR
:前序遍历(Preorder Traversal 亦称先序遍历
)——
访问根结点的操作发生在遍历其左右子树之前。我在稍微解释一下前序遍历的概念:其实就是遍历树时,先访问根,再访问左子树,最后访问右子树。这里要注意的是,当我们访问到左子树时,我们把左子树当成一个新树,同时也应该满足先访问根,在访问左子树,最后访问右子树。我们发现前序遍历先访问了整个树的跟后,再把整个树左子树访问完后,再从下往上依次访问整个数的右子树。
其实我们不难发现,当一个子树的节点为空时,我们就不再往下访问了,开始从下往上访问右子树。这好像与递归有点类似哦!其实就是用递归实现的遍历。我们结合着下图理解一下:
注:
- 往下的箭头表示递归调用;
- 往上的箭头表示返回,也就是归。
下面我们看代码的实现。
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
3、2、4 中序遍历
什么是中序遍历呢?同样,我们先来看一下比较官方的解释。
LNR:中序遍历
(Inorder Traversal)——
访问根结点的操作发生在遍历其左右子树的中
间。通俗来讲,其实就是遍历树时,
**先访问左子树,再访问根,最后访问右子树。**对比前序遍历,中序遍历与前序遍历大同小异,只不过是访问顺序发生了变化。我们结合这下图理解一下:
注:
- 往下的箭头表示递归调用;
- 往上的箭头表示返回,也就是归。
下面我们看代码的实现。
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
3、2、5 后序遍历
当我们了解完前序遍历和中序遍历后,我们理解后序遍历接很简单了。 我们先看比较官方的解释。
LRN
:后序遍历
(Postorder Traversal)——
访问根结点的操作发生在遍历其左右子树之后。通俗来讲,其实就是遍历树时,
**先访问左子树,再右子树,最后访问根。**我们直接看图:
注:
- 往下的箭头表示递归调用;
- 往上的箭头表示返回,也就是归。
下面我们看代码的实现。
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
3、2、6 求树中节点的个数
我们在统计树中节点的个数时,需要遍历整个树才行。当然,遍历整个树也是需要用递归的。当我们遇到某个节点为空时,我们就返回0,不是空时我们就返回1。我们结合着代码一起理解一下。
int TreeNodeSize(BTNode* root)
{
return root == NULL ? 0 : TreeNodeSize(root->left) + TreeNodeSize(root->right) + 1;
}
3、2、7 求树中叶节点的个数
我们知道,叶节点的度为0,也就是叶节点的左子树和右子树均为空。同样,我们使用递归遍历整个树,遇到节点为空时返回零,遇到节点的左子树和右子树均为空返回1。我们看代码的实现。
int TreeLeafSize(BTNode* root)
{
if (root == 0)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
3、3 二叉树的性质
通过上面对二叉树的理解,我给大家总结出了二叉树的一些性质:
- 若规定根节点的层数为
1
,则一棵非空二叉树的
第
i
层上最多有



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
若规定根节点的层数为
1
,则一棵非空二叉树的
第
i
层上最多有
[外链图片转存中…(img-K80VDLED-1714520074365)]
[外链图片转存中…(img-0PSxxg9b-1714520074366)]
[外链图片转存中…(img-HNoJJ99J-1714520074366)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
更多推荐





所有评论(0)