第七章 树与二叉树
7.1树的定义
树是n(n≥0)个结点的有限集,当n=0时,称为空树。在任意一棵非空树中应满足:有且仅有一个特定的称为根的结点;当n>1时,其余结点可分为m(m>0)个互不相交的有限集,其中每个集合本身又是一棵树,并且称为根的子树。
附历年考研真题,“关注”可免费获取!
7.2树的基本术语
①结点的度:树中一个结点的孩子个数,最大度数称为树的度。
②分支结点:度大于0的结点。
③叶子结点:度为0的结点。
④结点的层次:从树的根开始,根结点为第一层,它的子结点为第二层,以此类推。
⑤结点的深度:从根结点开始自顶向下逐层累加。
⑥结点的高度:从叶子结点开始自底向上逐层累加。
⑦树的高度(深度):树中结点的最大层数。
⑧路径与路径长度:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
7.3树的性质
①树中结点个数=所有结点的度数+1
②度为m的树中第i层上至多有mi-1个结点。
③高度为h的m叉树至少有h个结点。
④高度为h的m叉树至多(mh-1)/(m-1) 等比数列求和m0 + m1 + m2 +…+ mh-1,等比求和公式a0(1-qn)/(1-q)。
⑤高度为h、度为m的树至少有h+m-1个结点。
⑥具有n个结点的m叉树的最小高度为┌logm[n(m-1)+1]┑,向上取整。
求解思路,n应该大于高度为h-1最多结点数,且少于高度为h最多结点数,即(mh-1-1)/(m-1)≤n≤(mh-1)/(m-1)。
⑦m叉树:每个结点最多只能有m个孩子的树,任意结点的度≤m、允许所有结点的度都<m、可以是空树。
⑧度为m的树:任意结点的度≤m、至少有一个结点的度=m、一定是非空树,至少有m+1个结点
7.4特殊二叉树
①满二叉树:一棵高度为h,且含有2h-1个结点的二叉树,即树中的每层都含有最多的结点。
②完全二叉树:高度为h、有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应。
③二叉排序树:左子树上所有结点的关键字均小于根结点的关键字;右子树上所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一棵二叉排序树。
④平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1。
7.5二叉树性质
①非空二叉树上的叶子结点数等于度为2的节点数加1,即n0= n2+1
推导:结点总数n= n0+n1+n2=0* n0+n1+2* n2+1(所有结点度数+1)。
②非空二叉树上第k层至多有2k-1个结点。
③高度为h的二叉树至多2h-1个结点。
④对完全二叉树按从上至下,从左至右的顺序依次编号1,2,3…n,则有以下关系:
当i>1时,结点i的双亲结点编号为└i/2┘;
当2i≤n时,结点i的左孩子编号为2i,否则无左孩子;
当2i+1≤n时,结点i的右孩子编号为2i+1,否则无右孩子;
结点i所在层次(深度)为└Log2i┘+1。
⑤具有n个结点的完全二叉树的高度为┌log2[n+1]┑,同m叉树推导过程。
7.6二叉树存储结构
①顺序存储结构:二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左向右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在一维数组下标为i-1的分量中,建议从数组下标1开始存储树中的结点。完全二叉树和满二叉树适用采用顺序存储。
二叉树顺序存储几个重要特点:
②链式存储结构:二叉树一般采用链式存储结构,包含数据域data,左指针域lchild,右指针域rchild。
在含有n个结点的二叉链表中,含有n+1个空链域。
7.7二叉树遍历
7.7.1先序遍历(根左右)
7.7.2中序遍历(左根右)
7.7.3后续遍历(左右根)
7.7.4层次遍历
按照箭头所指方向,按1,2,3,4的层次顺序,对二叉树中的各个结点进行访问。进行层次遍历,需要借助一个队列,先将根结点入队,然后出队,访问出队结点,若它有左子树,则将左子树根结点入队;若它有右子树,则将右子树根结点入队。然后出队,访问出队结点,如此往复。
7.8由遍历序列构造二叉树
7.8.1前序+中序遍历序列
7.8.2后序+中序遍历序列
7.8.3层序+中序遍历序列
7.9线索二叉树
7.9.1线索二叉树定义
使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。传统的二叉链表存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱或后继。引入线索二叉树正是为了加快查找结点前驱和后继的速度。
7.9.2线索二叉树的存储结构描述
规定:若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。还需增加两个标志域标识指针域是指向左(右)孩子还是指向前驱(后继)。标志域含义如下:
7.9.3中序线索二叉树的构造
7.9.4中序线索二叉树的遍历
7.10树、森林的存储结构
7.10.1双亲表示法
采用一组连续的存储空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲结点在数组中的位置。根结点下标为0,其伪指针域为-1。
7.10.2孩子表示法
将每个结点的孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表(叶子结点的孩子链表为空)。
7.10.3孩子兄弟表示法
以二叉链表作为树的存储结构,每个结点包括三部分:结点值、指向结点第一个孩子结点的指针,及指向结点下一个兄弟结点的指针(沿此域可以找到结点的所有兄弟结点)。
7.11树、森林与二叉树的转换
树转换为二叉树规则:每个结点左指针指向它的第一个孩子,右指针指向它在树中的相邻右兄弟。①在兄弟结点之间加一根线;②对每个结点,只保留它与第一个孩子的连线,而与其他孩子的连线全部抹掉;③以树根为轴心,顺时针旋转45度。
森林转换为二叉树规则:①将森林中的每棵树转换成相应的二叉树;②每棵树的根也视为兄弟关系,在每棵树根之间加一条线;③以第一棵树为轴心,顺时针旋转45度。
二叉树转换为森林的规则:若二叉树非空,则二叉树的根及其左子树为第一棵树,故将根的右链断开。二叉树根的右子树又可视为除第一棵树外的森林转换后的二叉树,应用同样的方法,直到最后只剩最后一棵没有右子树的二叉树为止,最后将每棵二叉树依次转换为树即可。
7.12树和森林遍历
求树、森林遍历时,可先对应到二叉树,再来求解。
7.13哈夫曼树和哈夫曼编码
7.13.1哈夫曼树定义
也称最优二叉树,在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树。
结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积。
树的带权路径长度:树中所有叶结点的带权路径长度之和(WPL)
7.13.2哈夫曼树的构造
7.13.3哈夫曼编码
固定长度编码:对每一个字符采用相等长度的二进制位表示。
可变长度编码:对不同的字符采用不等长的二进制表示,特点是对高频率字符赋短码,对低频率字符赋长码,从而使平均编码长度减短,起到压缩数据效果。
前缀编码:没有一个编码是另一个编码的前缀。
构造哈夫曼编码:将每个出现的字符当做一个独立的结点,其权值为它出现的频度,构造出对应的哈夫曼树;从根至该字符的路径上标记序列,其中标记为0表示“转向左孩子”,标记为1表示“转向右孩子”。
第八章 图
8.1图的基本概念
(1)有向图
(2)无向图
(3)完全图
(4)子图
(5)连通、连通图和连通分量
(6)强连通图、强连通分量
有向图强连通情况下边最少的情况,至少需要n条边,构成一个环路。
(7)顶点的度、入度和出度
度:以该顶点为一个端点的边的数目;
无向图顶点的度依附于该顶点的边的条数,全部顶点度的和等于边数的2倍。有向图全部顶点的入度之和与出度之和相等,并且等于边数。
8.2图的存储及基本操作
8.2.1邻接矩阵法(重点)
指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即个顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
邻接矩阵特点:
①无向图,邻接矩阵一定是一个对称矩阵,只需存储上(或下)三角矩阵的元素;
②用邻接矩阵存储图,要确定图中有多少条边,必须按行、按列对每个元素进行检测;
③设图G的邻接矩阵为A,An的元素An[i][j]等于由顶点i到顶点j的长度为n的路径的数目。
8.2.2邻接表法(重点)
指对图G中的每个顶点vi建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图则是以顶点vi为尾的弧),这个单链表称为顶点vi边表(对于有向图则称为出边表)。顺序+链式存储。
邻接表 | 邻接矩阵 | |
---|---|---|
空间复杂度 | 无向图:O(|V|+2|E|)有向图:O(|V|+|E|) | O(|V|2) |
适用于 | 稀疏图 | 稠密图 |
表示方式 | 不唯一(每个顶点对应的单链表中,各边结点的链接次序可以是任意的) | 唯一 |
度 | 顶点i的度=出度+入度 | 无向图:第i行(第i列)非零元素的个数等于顶点i的度有向图:第i行非零元素的个数是顶点i的出度,第i列非零元素的个数是顶点i的入度 |
出度 | 只需读取该顶点的邻接表中结点的个数 | |
入度 | 需遍历全部邻接表 | |
找相邻边 | 无向图: 只需读取该顶点的邻接表有向图:找出边只需读取该顶点的邻接表,找入边需遍历整个邻接表 | 遍历对应行或列,时间复杂度为O(|V|) |
8.2.3十字链表
是有向图的一种链式存储结构,只存有向图。
8.2.4邻接多重表
是无向图的一种链式存储结构,只存无向图
8.2.5图的基本操作
8.3图的遍历
8.3.1广度优先搜索(BFS)
类似于二叉树的层序遍历算法,基本思想:①首先访问起始顶点v,接着由v出发,依次访问v的各个未访问过的邻接顶点w1,w2…wi;②然后依次访问w1,w2…wi的所有未被访问过的邻接顶点;③再从这些访问过的顶点出发,访问他们所有未被访问过的邻接顶点,直至图中所有顶点都被访问过为止。
BFS算法是一种分层的查找过程,算法需借助一个辅助队列。广度优先搜索过程与二叉树的层序遍历是完全一致的。
广度优先遍历序列如下:(遍历过程中,通常属于同一梯队的序号较小的排在前面)
广度优先生成树:给定图的邻接矩阵存储表示是唯一的,故其广度优先生成树也是唯一的;而邻接表存储表示不唯一,故其广度优先生成树也不是唯一的。
8.3.2深度优先搜索(DFS)
类似于二叉树的先序遍历算法,基本思想:①首先访问起始顶点v,然后由v出发,访问与v邻接且未被访问的任一顶点w1;②再访问与w1邻接且未被访问的任一顶点w2…重复上述过程;③当不能再继续向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,直至图中所有顶点都被访问过为止。
DFS算法是一个递归算法,需要借助一个递归工作栈。
深度优先遍历序列如下:(遍历过程中,通常属于同一梯队的序号较小的排在前面)
深度优先生成树:与广度优先生成树一样。
广度优先搜索 | 深度优先搜索 | |
---|---|---|
空间复杂度 | O(|V|) | O(|V|) |
时间复杂度 | 邻接表:O(|V|+|E|)邻接矩阵:O(|V|2) | 邻接表:O(|V|+|E|)邻接矩阵:O(|V|2) |
算法 | 类似于二叉树的层序遍历算法 | 类似于二叉树的先序遍历算法 |
辅助 | 辅助队列 | 递归工作栈 |
生成树/遍历序列 | 邻接矩阵:唯一的邻接表:不唯一 | 邻接矩阵:唯一的邻接表:不唯一 |
8.4图的应用(最小生成树、最短路径、拓扑排序、拓扑排序)
(1)最小生成树
性质:最小生成树的树形不唯一;其对应的边的权值之和总是唯一的,而且是最小的;最小生成树的边数为顶点数减1。
8.4.1最小生成树算法(Prim算法)
基本思想:从某一顶点开始构建,每次将代价最小的新顶点纳入生成树,直至所有顶点都纳入为止。时间复杂度为O(|V|2),适用于稠密图。
8.4.2最小生成树算法(Kruskal算法)
基本思想:每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选)。时间复杂度为O(|E|Log2|E|),适用于稀疏图。
(2)最短路径
当图是带权图时,把从一个顶点v0到图中其余任意一个顶点vi的一条路径(可能不止一条)所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径长度最短的那条路径称为最短路径。
8.4.3 Dijkstra算法求单源最短路径(不适用于带负权值的有向图)
单源最短路径:求图中某一顶点到其他各顶点的最短路径,
基本思想:设置一个集合S记录已求得的最短路径的顶点,初始时把源点v0放入S,每并入一个新顶点vi,都要修改源点v0到集合V-S中顶点当前的最短路径长度值。时间复杂度为O(|V|2)。
构造过程中,3个辅助函数:
dist[]:记录源点v0到其他各顶点当前的最短路径长度。初始值为,若v0到vi有弧,则dist[i]为弧上的权值,否则为∞;
path[]:path[i]表示从源点到顶点i之间的最短路径的前驱结点;
final[]:标记各顶点是否已找到最短路径,已找到为TRUE,未找到为False。
Dijkstra算法求单源最短路径步骤如下:
初始 | V0 | V1 | V2 | V3 | V4 |
---|---|---|---|---|---|
final[5] | √ | × | × | × | × |
dist[5] | 0 | 10 | ∞ | ∞ | 5 |
path[5] | -1 | 0 | -1 | -1 | 0 |
第1轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi(此处为V4),令final=True。检查所有邻接V4的顶点,若其final为false,则更新dist和path信息。
第1轮 | V0 | V1 | V2 | V3 | V4 |
---|---|---|---|---|---|
final[5] | √ | × | × | × | √ |
dist[5] | 0 | 8 | 14 | 7 | 5 |
path[5] | -1 | 4 | 4 | 4 | 0 |
第2轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi(此处为V3),令final=True。检查所有邻接V3的顶点,若其final为false,则更新dist和path信息。
第2轮 | V0 | V1 | V2 | V3 | V4 |
---|---|---|---|---|---|
final[5] | √ | × | × | √ | √ |
dist[5] | 0 | 8 | 13 | 7 | 5 |
path[5] | -1 | 4 | 3 | 4 | 0 |
第3轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi(此处为V1),令final=True。检查所有邻接V3的顶点,若其final为false,则更新dist和path信息。
第3轮 | V0 | V1 | V2 | V3 | V4 |
---|---|---|---|---|---|
final[5] | √ | √ | × | √ | √ |
dist[5] | 0 | 8 | 9 | 7 | 5 |
path[5] | -1 | 4 | 1 | 4 | 0 |
第4轮:将最后一个顶点直接纳入。
第4轮 | V0 | V1 | V2 | V3 | V4 |
---|---|---|---|---|---|
final[5] | √ | √ | √ | √ | √ |
dist[5] | 0 | 8 | 9 | 7 | 5 |
path[5] | -1 | 4 | 1 | 4 | 0 |
综上,根据path值,此图最短路径为V2<- V1<-V4<-V0
②Floyd算法求各顶点之间最短路径(略)
时间复杂度为O(|V|2),同时适用于带负权值的有向图。
8.4.4拓扑排序
有向无环图(DAG图):一个有向图中不存在环。
AOV网(用顶点表示活动的网络):用DAG图表示一个工程,其顶点表示活动,用有向边﹤Vi, Vj﹥表示活动Vi必须先于活动Vj进行的这样一种关系,记为AOV网。
一个有向无环图满足拓扑排序的2个条件:①每个顶点在图中只出现1次;②若顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径(即图中无环)。
拓扑排序算法步骤:
①从AOV网中选择一个没有前驱(入度为0)的顶点输出;
②从网中删除该顶点和所有以它为起点的有向边;
③重复①和②直到当前的AOV网为空。
具体操作示例如下,输出序列为12435。
注意:若一个顶点有多个直接后继或有多个入度为0的顶点,则拓扑排序的结果通常不唯一
时间复杂度:采用邻接表存储的为O(|V|+|E|),采用邻接矩阵存储的为O(|V|2)。
逆拓扑排序算法步骤:
①从AOV网中选择一个没有后继(出度为0)的顶点输出;
②从网中删除该顶点和所有以它为终点的有向边;
③重复①和②直到当前的AOV网为空。
上图逆拓扑排序输出序列为:53421
8.4.5关键路径
AOE网(用边表示活动的网络):用DAG图表示一个工程,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销,简称AOE网。AOE网和AOV网都是有向无环图,AOE网边有权值,AOV网边无权值。在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点);也仅有一个出度为0的顶点,称为结束顶点(汇点)。而AOV网则可以有多个入度为0和出度为0的顶点。
关键路径:从源点到汇点的所有路径中,路径长度最大的路径称为关键路径,而关键路径上的活动称为关键活动。
求关键路径的4个参量:
①事件vk的最早发生时间ve(k):指从源点v1到顶点vk的最长路径长度,决定了从vk开始的活动能够开工的最早时间;
②事件vk的最迟发生时间vl(k):指在不推迟整个工程完成的前提下,该事件最迟必须发生的时间;
③活动ai的最早开始时间e(i):指该活动弧的起点所表示的事件的最早发生时间,若边﹤Vk, Vj﹥表示活动ai,则有e(i)= ve(k);
④活动ai的最迟开始时间l(i):指该活动弧的终点所表示事件的最迟发生时间与该活动所需时间之差,若边﹤Vk, Vj﹥表示活动ai,则有l(i)= vl(j)-Weight(Vk, Vj);
⑤活动ai可拖延时间d(i): d(i)= l(i)- e(i),若一个活动的时间余量为零,则说明该活动必须要如期完成,否则就会拖延整个工程的进度,所以d(i)=0的活动是关键活动。
求关键路径的具体步骤:
①从源点出发,令ve(源点)=0,按拓扑有序求其余顶点的最早发生时间ve();
②从汇点出发,令vl(汇点)=ve(汇点),按逆拓扑有序求其余顶点的最迟发生时间vl();
③根据各顶点的ve()值求所有弧的最早开始时间e();
④根据各顶点的vl()值求所有弧的最迟开始时间l();
⑤求AOE网中所有活动的差额d(),找出所有d()=0的活动构成关键路径。
综上,关键活动为:a2, a5, a7;关键路径为v1-﹥v3-﹥v4-﹥v6