——本节内容为Bilibili王道考研《数据结构》P38~P42视频内容笔记。
目录
一、树的基本概念
1.相关术语
(1)空树:结点数为0的数。
(2)非空树:
①有且只有一个根结点;
②没有后继的结点称为“叶子结点”(终端结点);
③有后继的结点称为“分支结点”(非终端结点);
④除了根结点以外,任何一个结点都有且仅有一个前驱;
⑤每个结点可以有0个或多个后继。
(3)子树:每个结点可分为多个互不相交的有限集合,其中每个集合本身又是一棵树,并且称为该结点的子树(结点A有三棵子树,结点B有两棵子树,结点F没有子树)。
(4)树是一种递归定义的数据结构。
2.结点之间的关系描述
(1)祖先结点:从自身开始向上追溯直到根结点,途径的所有结点都称为祖先结点(图示“你”的祖先结点有:“父亲”、“爷爷”)。
(2)子孙节点:由自身所产生的所有结点都称为子孙节点(图示除了“爷爷”以外所有的结点都称为“爷爷”的子孙结点)。
(3)双亲结点(父结点):自身的前驱称为双亲结点(图示“你”的双亲结点为“父亲”)。
(4)孩子结点:自身的所有后继都称为孩子结点(图示“父亲”的孩子结点为“你”和“F”)。
(5)兄弟结点:自身的前驱所产生的其他结点称为兄弟结点(图示“F”为“你”的兄弟结点)。
(6)堂兄弟结点:除了“兄弟结点”,处于一层中的所有结点都称为堂兄弟结点(图示“G”、“H”、“I”、“J”都称为“你”的堂兄弟结点)。
(7)路径:两个结点之间的路径只能是从上往下的单向方向。
(8)路径长度:两个结点的路径长度指它们之间的边的个数。
3.结点、树的属性描述
(1)结点的层次(深度):即该结点处于这棵树从上往下数的第几层(图示“E”结点的层次为3)。
(2)结点的高度:即该结点处于这棵树从下往上数的第几层(图示“E”结点的高度为2)。
(3)树的高度(深度):即该树一共有多少层(图示的树的高度为4)。
(4)结点的度:即该结点的“孩子结点”的个数(图示“B”结点的度为2,“D”结点的度为3)。
①非叶子结点的度>0;
②叶子结点的度=0。
(5)树的度:即各结点的度的最大值(图示的树的度为3)。
4.有序树和无序树
(1)有序树:从逻辑上看,树中结点的各子树从左至右是有次序的,不能互换。
(2)无序树:从逻辑上看,树中结点的各子树从左至右是无次序的,可以互换。
(3)具体要看你用树存什么,是否需要用结点的左右位置来反应某些逻辑关系。
5.树和森林
(1)森林:森林是m(m>=0)棵互不相交的树的集合。
(2)m可以为0,即代表空森林。
二、树的性质
1.结点数=总度数+1(总度数+根结点)。
2.度为m的树、m叉树
(1)树的度:各结点的度的最大值;所以度为m的树应:
①任意结点的度<=m(最多有m个孩子);
②至少有一个结点度=m;
③一定是非空树,至少有m+1个结点。
(2)m叉树:每个结点最多只能有m个孩子的树;所以m叉树应:
①任意结点的度<=m(最多有m个孩子);
②允许所有结点的度都<m;
③可以是空树;
3.树的最多结点
(1)度为m的树第i层至多有个结点(i>=1);
(2)m叉树第i层至多有个结点(i>=1)。
(3)高度为h的m叉树最多有个结点。
(等比数列求和:)
4.树的最少结点
(1)高度为h的m叉树至少有h个结点。
(2)高度为h、度为m的树至少有h+m-1个结点。
5.m叉树最小高度
(1)具有n个结点的m叉树的最小高度为。
(2)高度最小的情况:所有结点都有m个孩子(即往宽了长)。
(3)分析过程如下:
三、二叉树的定义和基本术语
1.基本概念
(1)二叉树是n(n>=0)个结点的有限集合:
①空二叉树,即n=0;
②由一个根结点和两个互不相交的被称为根的左子树和右子树组成,左子树和右子树又分别是一棵二叉树。
(2)特点:
①每个结点至多只有两棵子树;
②左右子树不能颠倒(二叉树是有序树)。
2.二叉树的五种状态
3.特殊二叉树
(1)满二叉树
①定义:一棵高度为h,且含有个结点的二叉树。
②图像:
③特点:
Ⅰ只有最后一层有叶子结点;
Ⅱ不存在度为1的结点(要么为2,要么为0);
Ⅲ按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父节点为(如果有的话)。
(2)完全二叉树
①定义:当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树。
②图像:
③通俗解释:就是把一个满二叉树里较大的连续编号删掉,比如上面图示中删掉了最后一层中后三个结点,其余编号和满二叉树的编号相对应。
④特点:
Ⅰ只有最后两层可能有叶子结点;
Ⅱ最多只有一个度为1的结点;
Ⅲ按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1;结点i的父节点为(如果有的话);
Ⅳ为分支结点,
为叶子结点;
Ⅴ如果一个完全二叉树的某个结点只有一个孩子的话,那么这个孩子一定是左孩子。
(3)二叉排序树
①定义:一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
Ⅰ左子树上所有结点的关键字均小于根结点的关键字;
Ⅱ右子树上所有结点的关键字均大于根结点的关键字。
②左子树和右子树又各是一棵二叉排序树。
③图像:
(4)平衡二叉树
①定义:树上任一结点的左子树和右子树的深度之差不超过1。
②图像:
③平衡二叉树有更高的搜索效率。
四、二叉树的性质
1.二叉树性质1
(1)设非空二叉树中度为0、1、2的结点的个数分别为n0、n1、n2,则n0=n2+1(叶子结点比二分支结点多一个);算法如下:
(2)假设树中结点总数为n,则:
①n=n0+n1+n2;
②n=n1+2n2+1(树的结点数=总度数+1);
③用②-①得到:n0=n2+1。
2.二叉树性质2
二叉树第i层最多有个结点(i>=1)。
3.二叉树性质3
高度为h的二叉树最多有个结点(满二叉树)。
4.完全二叉树性质1
(1)具有n个(n>0)结点的完全二叉树的高度h为或
。 算法如下:
(2)第n个结点所在层次为或
。
5.完全二叉树性质2
(1)对于完全二叉树,可以由总的结点数n推出度为0、1、2的结点个数为n0、n1、n2。
(2)算法:
①完全二叉树最多只有一个度为1的结点,即n1=0或1;
②由n0=n2+1得n0+n2=2n2+1一定是一个奇数;
③若完全二叉树有2k(偶数)个结点,则必有n1=1,n0=k,n2=k-1;
④若完全二叉树有2k-1(奇数)个结点,则必有n1=0,n0=k,n2=k-1。
五、二叉树的存储结构
1.顺序存储
(1)代码实现:
#define MaxSize 100
struct TreeNode {
int value; //结点中的数据元素
bool isEmpty; //结点是否为空
};
TreeNode t[MaxSize];
(2)定义长度为MaxSize的数组t,按照从上到下、从左到右的顺序依次存储完全二叉树中的各个结点,如下图所示:
①图示,让第一个位置t[0]空缺,可保证数组下标和结点编号一致。
②几个重要的基本操作:
Ⅰi结点的左孩子:2i;
Ⅱi结点的右孩子:2i+1;
Ⅲi结点的父结点:;
Ⅳi结点所在的层次: 或
。
③若完全二叉树中共有n个结点,则:
Ⅰ判断i是否有左孩子:2i<=n?
Ⅱ判断i是否有右孩子:2i+1<=n?
Ⅲ判断i是否是叶子/分支结点:?
(3)如果不是完全二叉树,依然按层序将各个结点顺序存储:
①情况如图:
②这些结点的编号将无法反应结点之间的逻辑关系。
(4)为解决(3)的问题,在二叉树的顺序存储中,一定要把二叉树的结点编号与完全二叉树对应起来,如下图所示:
①可以反应结点之间的逻辑关系:
Ⅰi结点的左孩子:2i;
Ⅱi结点的右孩子:2i+1;
Ⅲi结点的父结点:;
Ⅳi结点所在的层次: 或
。
②但无法直接判断左孩子右孩子或i结点是否是叶子/分支结点,可以:
Ⅰ判断i是否有左孩子:首先我们知道2i是i的左孩子结点的位置,然后用isEmpty来判断这个位置空不空,不空则有左孩子;
Ⅱ判断i是否有右孩子: 首先我们知道2i+1是i的右孩子结点的位置,然后用isEmpty来判断这个位置空不空,不空则有右孩子;
Ⅲ判断i是否是叶子/分支结点:根据ⅠⅡ判断i有没有左右孩子,无则叶子,有则分支。
(5)二叉树的顺序存储的弊端:需要开辟大量的存储单元,如果不是完全二叉树的话,会造成很多存储空间的浪费,所以顺序存储结构只适合存储完全二叉树。
2.链式存储
(1)代码实现:
typedef struct BiTNode {
int data; //数据域
struct BiTNode* lchild, * rchild; //左右孩子指针
}BiTNode,*BiTree;
(2)如图所示:
(3)由于每个结点都有两个指针域,如果一个二叉树有n个结点的话,那么它一共应该有2n个指针域。除了根结点,每个结点头上都会连一个指针,也就是有n-1个结点头上都会连一个指针,则剩下n+1个指针指向NULL,即n个结点的二叉链表共有n+1个空链域(可用于构造线索二叉树)。
(4)构造二叉链表:
struct ElemType {
int value;
};
typedef struct BiTNode {
int data;
struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;
int main()
{
//定义一棵空树
BiTree root = NULL;
//插入根结点
root = (BiTree)malloc(sizeof(BiTNode));
root->data = { 1 };
root->lchild = NULL;
root->rchild = NULL;
//插入新结点
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));
p->data = { 2 };
p->lchild = NULL;
p->rchild = NULL;
root->lchild = p; //作为根结点的左孩子
}
(5)三叉链表
①对于二叉链表来说,想要找到某个结点的父结点是很困难的,只能从根结点开始遍历寻找,如果结点足够多的情况下,这将是一个巨大的工程。所以引出方便寻找父结点的三叉链表:
②代码实现:
struct ElemType {
int value;
};
typedef struct BiTNode {
ElemType data;
struct BiTNode* lchild, * rchild;
struct BiTNode* parent;
}BiTNode,*BiTree;