1
0
mirror of https://github.com/Didnelpsun/CS408.git synced 2026-06-18 01:19:14 +08:00

更新图

This commit is contained in:
Didnelpsun
2021-09-27 23:22:07 +08:00
parent 25423916c0
commit 7456b03e9a
2 changed files with 134 additions and 51 deletions

View File

@@ -84,6 +84,18 @@ $D.1$
解:$C$。$n$个结点的树有$n-1$条边,假设森林中有$x$棵树,将每棵树的根连到一个添加的结点,则成为一棵树,结点数是$n+1$,边数是$e+x=n$,从而可知$x=n-e$。另解设森林中有$x$棵树,则再用$x-1$条边就能把所有的树连接成一棵树,此时,边数$+1=$顶点数,即$e+(x-1)+1=n$,故$x=n-e$。
**例题** 设无向图$G=(V,E)$和$G'=(V',E')$,若$G'$是$G$的生成树,则下列说法中错误的是()。
$A.G'$为$G$的子图
$B.G'$为$G$的连通分量
$C.G'$为$G$的极小连通子图且$V=V'$
$D.G'$是$G$的一个无环子图
解:$B$。连通分量是无向图的极大连通子图,其中极大的含义是将依附于连通分量中顶点的所有边都加上,所以连通分量中可能存在回路,这样就不是生成树了。
## 图的存储结构
### 邻接表
@@ -111,3 +123,39 @@ $C.O(n+e)$
$D.O(ne)$
解:$C$。删除与某顶点$v$相关的所有边的过程如下:先删除下标为$v$的顶点表结点的单链表,出边数最多为$n-1$,对应时间复杂度为$O(n)$;再扫描所有边表结点,删除所有的顶点$v$的入边,对应时间复杂度为$O(e)$。故总的时间复杂度为$O(n+e)$。
## 图的基本操作
### 图遍历
<!-- #### 广度优先遍历 -->
#### 深度优先遍历
**例题** 无向图$G=(V,E)$,其中$V=\{a,b,c,d,e,f\}$$E=\{(a,b),(a,e),(a,c),(b,e),(c,f),(f,d),(e,d)\}$,对该图从$a$开始进行深度优先遍历,得到的顶点序列正确的是()。
$A.a,b,e,c,d,f$
$B.a,c,f,e,b,d$
$C.a,e,b,c,f,d$
$D.a,e,d,f,c,b$
解:$D$。可以画出$G$结构。$A$当遍历到$e$时应该遍历相邻且没有访问的$d$,而不是$c$。$B$访问到$f$应该访问$d$而不是$e$。$C$当访问完$a,e,b$这个序列后结束形成环,下一个访问的应该是与序列尾部$b$的父结点$e$相邻的没有访问的$d$而不是$c$。
**例题** 使用$DFS$算法递归地遍历一个无环有向图,并在退出递归时输出相应顶点,这样得到的顶点序列是()。
$A.$逆拓扑有序
$B.$拓扑有序
$C.$无序的
$D.$都不是
解:$A$。对一个有向图做深度优先遍历,并未专门判断有向图是否有环(有向回路)存在,无论图中是否有环,都得到一个顶点序列。若无环,在退出递归过程中输出的应是逆拓扑有序序列。(深度优先使用栈,先进后出)
## 图的应用
### 最小生成树

View File

@@ -158,15 +158,15 @@
#### 查找边
使用邻接矩阵只用根据对应行列的元素是否为1或某值就可以了,如果是0或无穷,就代表没有该邻边。时间复杂度为$O(1)$。
使用邻接矩阵只用根据对应行列的元素是否为$1$或某值就可以了,如果是$0$或无穷,就代表没有该邻边。时间复杂度为$O(1)$。
而使用邻接矩阵需要从一端点出发遍历对应的结点链表,如果能在链表中找到另一端点的索引,就代表有边。时间复杂度为$O(1)$到$O(\vert V\vert)$。
#### 查找点邻边
对于无向图,邻接矩阵需要遍历对应结点的那一行,所有数值为1或某数值的列就是对应的有边的另一个端点。时间复杂度为$O(\vert V\vert)$。
对于无向图,邻接矩阵需要遍历对应结点的那一行,所有数值为$1$或某数值的列就是对应的有边的另一个端点。时间复杂度为$O(\vert V\vert)$。
对于有向图,邻接矩阵需要遍历对应结点的那一行得到出边以及那一列代表入边,所有数值为1或某数值的列就是对应的有边的另一个端点。时间复杂度为$O(\vert V\vert)$。
对于有向图,邻接矩阵需要遍历对应结点的那一行得到出边以及那一列代表入边,所有数值为$1$或某数值的列就是对应的有边的另一个端点。时间复杂度为$O(\vert V\vert)$。
对于无向图,邻接表只用遍历对应结点的结点链表就可以。时间复杂度为$O(1)$到$O(\vert V\vert)$。
@@ -190,7 +190,7 @@
邻接矩阵只用在最后增加一行一列。时间复杂度是$O(1)$。
邻接表只用在存储结点的数组的末尾添加一个结点指针设置为NULL。时间复杂度是$O(1)$。
邻接表只用在存储结点的数组的末尾添加一个结点,指针设置为$NULL$。时间复杂度是$O(1)$。
### 图删除
@@ -202,7 +202,11 @@
### 图遍历
分为广度优先BFS与深度优先DFS
指从图某一顶点出发按照某种搜索方法沿着图中的边对图中所有顶点访问一次且仅访问一次
树的遍历也可以看作一种特殊图的遍历。
分为广度优先$BFS$与深度优先$DFS$。
#### 广度优先遍历
@@ -212,24 +216,27 @@
广度优先遍历过程:
1. 访问顶点v
2. 访问v的所有未被访问的邻接点。
3. 依次从这些邻接点(在步骤二中访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问。
1. 访问顶点$v$
2. 访问$v$的所有未被访问的邻接点。
3. 依次从这些邻接点(在步骤二中访问的顶点)出发,访问它们的所有未被访问的邻接点依此类推,直到图中所有访问过的顶点的邻接点都被访问。
因为广度优先算法不需要回退,所以不是一个递归算法。
邻接矩阵实现时的时间复杂度为$O(\vert V\vert^2)$,邻接表实现时的时间复杂度为$O(\vert V\vert+\vert E\vert)$;空间复杂度为$O(\vert V\vert)$。
广度优先生成树:根据广度优先遍历可以将所有第一次访问结点时的路径组合生成一个广度优先生成树,若图结点为n则生成树边一共有n-1条。因为保存图的数据结构若是不唯一则其广度优先生成树也是不唯一的。
广度优先生成树:根据广度优先遍历可以将所有第一次访问结点时的路径组合生成一个广度优先生成树,若图结点为$n$个,则生成树边一共有$n-1$条。因为保存图的数据结构若是不唯一,则其广度优先生成树也是不唯一的。若邻接矩阵存储则唯一,若邻接表存储则不唯一。
广度优先生成森林:若图是不连通的,那会生成连通分量个广度优先生成树,就构成了广度优先生成森林。
#### 深度优先遍历
类似于树的先根遍历。
类似于树的先根遍历。是一个递归算法,所以需要一个工作栈。
深度优先遍历过程:
1. 访问顶点v
2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历。
3. 直至图中和v有路径相通的顶点都被访问。
1. 访问顶点$v$
2. 依次从$v$的未被访问的邻接点出发,对图进行深度优先遍历。
3. 直至图中和$v$有路径相通的顶点都被访问。
4. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
邻接矩阵实现时的时间复杂度为$O(\vert V\vert^2)$,邻接表实现时的时间复杂度为$O(\vert V\vert+\vert E\vert)$;空间复杂度为$O(\vert V\vert)$。
@@ -238,7 +245,10 @@
邻接矩阵方式唯一所以深度优先序列唯一,而邻接表方式不唯一,所以深度优先序列不唯一。
深度优先生成树根据深度优先遍历可以将所有第一次访问结点时的路径组合生成一个深度优先生成树若图结点为n个则生成树边一共有n-1条。因为保存图的数据结构若是不唯一则其深度优先生成树也是不唯一的
使用$BFS$可以解决非带权图的单源最短路径问题,因为广度优先搜索按照距离有近到远
深度优先生成树:根据深度优先遍历可以将所有第一次访问结点时的路径组合生成一个深度优先生成树,若图结点为$n$个,则生成树边一共有$n-1$条。因为保存图的数据结构若是不唯一,则其深度优先生成树也是不唯一的。如果无向图非连通,则一个顶点出发只能一次性遍历到该顶点所在连通分量的所有顶点。
深度优先生成森林:若图是不连通的,那会生成连通分量个深度优先生成树,就构成了深度优先生成森林。
#### 图遍历与图连通性
@@ -251,65 +261,81 @@
### 最小生成树
一个连通图的生成树包含图的所有顶点,并且只含尽可能少的边。对于生成树来说,若砍去它的一条边,则会使生成树变成非连通图;若给它增加一条边,则会形成图中的一条回路。
#### 最小生成树定义
最小生成树MST也是最小代价树。已知生成树就是最小边的能到任意结点的树这种树只关心边数所以有多个不同的生成树。
最小生成树$MST$也是最小代价树。已知生成树就是最小边的能到任意结点的树,这种树只关心边数,所以有多个不同的生成树。
而最小生成树就是带权生成树的最小权值和的情况。
R为图G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称G的最小生成树。
$R$为图$G$的所有生成树的集合,若$T$为$R$中边的权值之和最小的生成树,则$T$称$G$的最小生成树。
+ 最小生成树可能有多个,但是边的权值总是唯一且最小的。
+ 最小生成树的边数=顶点数-1。减去一条则不连通增加一条则会出现回路。
+ 最小生成树的边数=顶点数$-1$。减去一条则不连通,增加一条则会出现回路。
+ 若一个连通图本身就是一棵树,则其最小生成树就是其本身。
+ 只用连通图才有生成树,非连通图只有生成森林。
获取最小生成树的$Prim$算法和$Kruskal$算法都是基于贪心算法的策略。每次都加入一条边逐渐生成一棵生成树。
#### Prim算法
普里姆算法:从某个顶点开始构建生成树,每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。
+ 假设$G=\{V,E\}$是连通图,其最小生成树$T=(U,E_T)$$E_T$是最小生成树中边的集合。
+ 初始化:向空树$T=(U,E_T)$中添加图$G=(V,E)$的任一顶点$u_0$,使$U=\{u_0\}$$E_T=\varnothing$。
+ 循环(重复下列操作直至$U=V$):从图$G$中选择满足$\{(u,v)|u\in U,v\in V-U\}$且具有最小权值的边$(u,v)$,加入树$T$,置$U=U\cup\{v\}$$E_T=E_T\cup\{(u,v)\}$。
时间复杂度为$O(\vert V\vert^2)$,适用于边稠密图。
#### Kruskal算法
克鲁斯卡尔算法:每次选择一条权值最小的边,使这条边的两头连通,若本就连通的就不选,直到所有的结点都连通。
时间复杂度为$O(\vert E\vert\log_2\vert E\vert)$,适用于边稀疏图
+ 假设$G=(V,E)$是连通图,其最小生成树$T=(U, E_T)$
+ 初始化:$U=V,E_T=\varnothing$。即每个顶点构成一棵独立的树,$T$此时是一个仅含$\vert V\vert$个顶点的森林。
+ 循环(重复下列操作直至$T$是一棵树):按$G$的边的权值递增顺序依次从$E-E_T$中选择一条边,若这条边加入$T$后不构成回路,则将其加入$E_T$,否则舍弃,直到$E_T$中含有$n-1$条边。
使用堆来存放边,所以每次旋转最小权值的边只需要$O(\log\vert E\vert)$的时间。
时间复杂度为$O(\vert E\vert\log_2\vert E\vert)$,适用于边稀疏顶点多的图。
### 最短路径
+ 单源最短路径:单个结点到图的其他结点的最短路径。
+ BFS算法无权图
+ Dijkstra算法带权图、无权图
+ $BFS$算法(无权图)。
+ $Dijkstra$算法(带权图、无权图)。
+ 每对顶点间最短路径:每个结点之间的最短路径。
+ Floyd算法带权图、无权图
+ $Floyd$算法(带权图、无权图)。
#### BFS算法
广度优先算法可以计算无权图的单源最短路径。
实际上无权图可以视为一种特殊的带权图,只是每条边的权值全部为1
实际上无权图可以视为一种特殊的带权图,只是每条边的权值全部为$1$
广度优先算法基本上就是对广度优先遍历的改进。定义两个数组,索引号就代表元素的序号,一个数组表示从起点开始到该点的最短路径长度,另一个数组表示从起点开始到该点的最短路径的上一个结点的索引值。
#### Dijkstra算法
1. 从$v_0$开始,初始化三个数组:标记各顶点是否已找到最短路径;最短路径长度;最短路径上的前驱
2. 遍历所有结点找到还没确定最短路径且最短路径长度值最小的的一个顶点这就确定了下一个最短路径的结点令其各顶点是否已找到最短路径的值为true。
即迪杰斯特拉算法
1. 从$v_0$开始,初始化三个数组:标记各顶点是否已找到最短路径$final$;最短路径长度$dist$;最短路径上的前驱$path$。从$v_0$开始,所以将$final[0]=true$$dist[0]=0$$path[0]=-1$,然后将$v_0$直连的点的$dist$初始化为直连路径长度,对应的$path=0$,但是不要将对应的$final=true$,因为还没有确定对应的直连路径就是最短路径。其他顶点的$dist=\infty$。
2. 遍历所有结点,找到还没确定最短路径,且最短路径长度值最小的的一个顶点,这就确定了下一个最短路径的结点,令其各顶点是否已找到最短路径的值为$true$。
3. 检查所有邻接这个结点的其他结点,若其点还没有找到最短路径,则更新最短路径长度值与最短路径上前驱的值。
4. 重复步骤二再次循环遍历所有结点并找到没确定最短路径则最短路径长度最小的顶点。
Dijkstra算法与Prim算法类似都是优先与最短的路径结合。
$Dijkstra$算法与$Prim$算法类似,都是优先与最短的路径结合。
时间复杂度为$O(\vert V\vert^2)$。
当权值中含有负权值的时候可能Dijkstra算法会失效。
当权值中含有负权值的时候可能$Dijkstra$算法会失效。
#### Floyd算法
是一种动态规划算法,将问题的求解分为多个阶段。
对于n个结点的图G,求任意一对顶点$v_i$到$v_j$之间的最短路径可分为如下阶段:
对于$n$个结点的图$G$,求任意一对顶点$v_i$到$v_j$之间的最短路径可分为如下阶段:
1. 初始化:不允许在其他顶点中转,求最短路径。
2. 若允许在$v_0$中转,求最短路径。
@@ -317,56 +343,65 @@ Dijkstra算法与Prim算法类似都是优先与最短的路径结合。
4. ...
5. 若允许在$v_0$、$v_1\cdots v_{n-1}$中转,求最短路径。
算法需要遍历n次,每次遍历都需要查看n×n的矩阵中是否有更优的中转点。
算法需要遍历$n$次,每次遍历都需要查看$n\times n$的矩阵中是否有更优的中转点。
即判断若$A^{(k-1)}[i][j]>A^{(k-1)}[i][k]+A^{(k-1)}[k][j]$是否成立,若成立则$A^{(k)}[i][j]=A^{(k-1)}[i][k]+A^{(k-1)}[k][j]$$path^{(k)}[i][j]=k$,否则$A^{(k)}$和$path^{(k)}$保持原样。其中$A^{(k)}$表示的是允许$v_0\cdots v_k$个点中转后各顶点的最短路径长度,$path^{(k)}$表示允许$v_0\cdots v_k$个点中转后两个点之间的中转点。
时间复杂度为$O(\vert V\vert^3)$,空间复杂度为$O(\vert V\vert^2)$。
Floyd算法复杂度高所以基本上都是四个结点以下的图能解决带负权值的问题但是不能解决带有负权回路的图即有负权值的边组成回路这种图可能没有最短路径。
$Floyd$算法复杂度高,所以基本上都是四个结点以下的图,能解决带负权值的问题,但是不能解决带有负权回路的图,即有负权值的边组成回路,这种图可能没有最短路径。
### 有向无环图
若一个有向图中不存在环则是有向无环图简称DAG图。
若一个有向图中不存在环,则是有向无环图,简称$DAG$图。
有向无环图可以运用到表达式的表达上,将操作数共同的结点部分删除并将边合并到一起,这就形成了图。
#### 表达式应用
顶点中不可能出现重复的操作数
有向无环图可以运用到表达式的表达上,用树表示表达式,将操作数共同的结点部分删除并将边合并到一起,这就形成了图,从而能精简表达式
顶点中不可能出现重复的操作数。表达式树不唯一。
1. 把各个单个的操作数不重复的排成一排。
2. 标出各个运算符的生效顺序。
3. 按顺序加入运算符,并注意对运算符的优先级进行分层。
4. 当构建完成后从底向上逐层检查同层的运算符是否可以合并。
### 拓扑排序
#### 拓扑排序
AOV网用DAG图表示一个工程顶点表示活动有向边$<v_i,v_j>$表示活动$v_i$必须先于活动$v_j$进行。
$AOV$网:用$DAG$图表示一个工程,顶点表示活动,有向边$<v_i,v_j>$表示活动$v_i$必须先于活动$v_j$进行。
拓扑排序:在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条;件时,称为该图的一个拓扑排序:
拓扑排序在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序:
1. 每个顶点出现且只出现一次。
2. 若顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。
2. 若顶点$A$在序列中排在顶点$B$的前面,则在图中不存在从顶点$B$到顶点$A$的路径。
或定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV网都有一个或多个拓扑排序序列。
或定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点$A$到顶点$B$的路径,则在排序中顶点$B$出现在顶点$A$的后面。每个$AOV$网都有一个或多个拓扑排序序列。
简单来说就是找到工程执行的先后顺序。
拓扑排序的实现:
1. 从AOV网中选择一个没有前驱的入度为0的顶点并输出。
1.$AOV$网中选择一个没有前驱的入度为$0$的顶点并输出。
2. 从网中删除该顶点和所有以它为起点的有向边。
3. 重复步骤一和二直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。
3. 重复步骤一和二直到当前的$AOV$网为空或当前网中不存在无前驱的顶点(存在环路所以不能拓扑排序)为止。
逆拓扑排序的实现:
1. 从AOV网中选择一个没有后继的出度为0的顶点并输出。
1.$AOV$网中选择一个没有后继的出度为$0$的顶点并输出。
2. 从网中删除该顶点和所有以它为起点的有向边。
3. 重复步骤一和二直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。
3. 重复步骤一和二直到当前的$AOV$网为空或当前网中不存在无前驱的顶点为止。
### 关键路径
时间复杂度若使用邻接表为$O(\vert V\vert+\vert E\vert)$,若使用邻接矩阵则是$O(\vert V\vert^2))$。
在带权有向图中以顶点表示时间以有向边表示活动以边上的权值表示完成该活动的开销称之为用边表示活动的网络简称AOE网。
#### 关键路径
在带权有向图中,以顶点表示时间,以有向边表示活动,以边上的权值表示完成该活动的开销,称之为用边表示活动的网络,简称$AOE$网。
+ 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
+ 只有在进入某顶点的各有向边所代表的活动都已经结束时,该顶点所代表的事件才能发生,活动也可以并行进行。
+ 只有一个入度为0的顶点,即开始顶点(源点),表示整个工程的开始。
+ 只有一个出度为0的顶点,称为结束顶点(汇点),表示整个工程的结束。
+ 只有一个入度为$0$的顶点,即开始顶点(源点),表示整个工程的开始。
+ 只有一个出度为$0$的顶点,称为结束顶点(汇点),表示整个工程的结束。
+ 从源点到汇点的有向路径可能有多条,所有路径中,具有最大路径长度的路径称为关键路径(即决定完成整个工程所需的最小时间),而关键路径上的活动称为关键活动。
+ 事件的最早发生时间:决定了所有从该事件开始的活动能够开工的最早时间。
+ 活动的最早开始时间:指该活动弧的起点所表示的事件最早发生时间。
@@ -376,13 +411,13 @@ AOV网用DAG图表示一个工程顶点表示活动有向边$<v_i,v_j>$
求关键路径的步骤:
1. 求所有事件的最早发生时间ve。根据拓扑排序序列依次按照所有路径的最大值求出各个顶点的最早发生时间。
2. 求所有事件的最迟发生时间vl。根据逆拓扑排序序列回退依次将每个结点按第一步计算的整个工程的时间减去本结点需要处理的时间得到每个活动都最晚应该发生的时间交叉的结点取最小值。
3. 求所有活动的最早发生时间e。根据第二步可以得到每个活动发生的最早时间。
4. 求所有活动的最迟发生时间l。根据vl求每个结点不影响整个工程的情况下最晚可以什么时候开始。
5. 求所有活动的时间余量d。将l-e得到d,余量为0的活动就是关键活动,表示如果该活动拖延就会影响整个工程的进度。
1. 求所有事件的最早发生时间$ve$。根据拓扑排序序列,依次按照所有路径的最大值求出各个顶点的最早发生时间。
2. 求所有事件的最迟发生时间$vl$。根据逆拓扑排序序列,回退依次将每个结点按第一步计算的整个工程的时间减去本结点需要处理的时间,得到每个活动都最晚应该发生的时间,交叉的结点取最小值。
3. 求所有活动的最早发生时间$e$。根据第二步可以得到每个活动发生的最早时间。
4. 求所有活动的最迟发生时间$l$。根据$vl$求每个结点不影响整个工程的情况下最晚可以什么时候开始。
5. 求所有活动的时间余量$d$。将$l-e$得到$d$,余量为$0$的活动就是关键活动,表示如果该活动拖延就会影响整个工程的进度。
+ 关键活动时间增加,整个工程工期延长。
+ 关键活动时间减少,整个工程工期缩短。
+ 关键活动时间减少,可能变为非关键活动。
+ 若有多条关键路径,则必须提高所有关键路径关键活动才能缩短工期。
+ 若有多条关键路径,则必须提高所有关键路径关键活动才能缩短工期。