数据结构——5.图
考纲
- 图的基本概念和术语
- 图的存储结构:邻接矩阵、邻接表、逆邻接表
- 遍历算法:深度优先搜索算法和广度优先搜索算法
- 应用:最小生成树;最短路径,拓扑排序和关键路径
无算法实现,主要选填+简答(手推)
一、图的基本概念和术语
1、图的定义
- 图G由顶点集V和边集E组成,记为G = (V,E)。
- V = {v1,v2,…,vn}。|V|表示G中顶点的个数、图G的阶。
- E = {(u,v) | u∈V, v∈V}。|E|表示图G中边的条数。
- 图不可为空,V一定是非空集。
2、基本概念和术语
- 有向图
(1)E是有向边/弧的有限集合。
(2)弧是顶点的有序对<v,w>(<弧尾,弧头>),<v,w> ≠ <w,v>。 - 无向图
(1)E是无向边/边的有限集合。
(2)边是顶点的无序对(v,w)/(w,v),(v,w) = (w,v)。顶点v和w互为邻接点。称边依附于点/相关联。 - 简单图、多重图
(1)简单图:①不存在重复边;②不存在顶点到自身的边
(2)多重图:图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联。 - 完全图/简单完全图
(1)无向完全图:任意两个顶点之间都存在边。若无向图顶点数|V|=n,则 ∣ E ∣ ∈ [ 0 , C n 2 ] = [ 0 , n ( n − 1 ) / 2 ] |E|\in [0,C_{n}^{2}] = [0,n(n-1)/2] ∣E∣∈[0,Cn2]=[0,n(n−1)/2] 。
(2)有向完全图:任意两个顶点之间都存在方向相反的两条弧。若有向图顶点数|V|=n,则 ∣ E ∣ ∈ [ 0 , 2 C n 2 ] = [ 0 , n ( n − 1 ) ] |E|\in [0,2C_{n}^{2}] = [0,n(n-1)] ∣E∣∈[0,2Cn2]=[0,n(n−1)] 。 - 子图
(1)子图:G1(V1,E1),V1是V子集,E1是E子集。(需要满足E1关联的顶点都在V1中,并非V和E的任何子集都能构成G的子图)。
(2)生成子图:子图G1满足V(G1) = V(G)
(3)对于有向图,生成子图和子图概念一样。
- 连通、连通图和连通分量(选填)
- 无向图中顶点间有路径存在——连通
- 连通图:图中任意两个顶点都连通。否则非连通图。
- 若G是连通图,则最少有n-1条边
- 若G是非连通图,则最多边数 C n − 1 2 C_{n-1}^{2} Cn−12
- 连通分量:无向图的极大连通子图。极大连通子图:子图必须连通且包含尽可能多的顶点和边。
- 强连通图、强连通分量
- 有向图中双向路径存在——强连通
- 强连通图:任何一对顶点都强连通
- 若G是强连通图,则最少有n条边(圆环)
- 强连通分量:有向图中的极大强连通子图。极大强连通子图:子图必须强连通,同时保留尽可能多的边。
- 生成树、生成森林
(1)连通图的生成树是包含图中全部顶点的一个极小连通子图。极小连通子图:边尽可能的少,但要保持连通。
(2)在非连通图中,连通分量的生成树构成了非连通图的生成森林。
- 顶点的度、入度和出度
(1)无向图:顶点v的度TD(v):依附于该顶点的边的条数。∑TD(Vi) = 2|E|
(2)有向图:- 入度ID(v):以v为终点;
- 出度OD(v):以v为起点。
- TD(v) = ID(v) + OD(v)。∑ID(Vi) = ∑OD(Vi) = |E|
- 边的权和网
(1)边的权:边上标某种含义的数值——权值。
(2)带权图/网:边上带有权值的图。
(3)带权路径长度:一条路径上所有边的权值之和。 - 稠密图、稀疏图
(1)边数很少的图称为稀疏图,反之稠密图。
(2)一般 ∣ E ∣ < ∣ V ∣ l o g ∣ V ∣ |E| < |V|log|V| ∣E∣<∣V∣log∣V∣时,将G视为稀疏图。 - 路径、路径长度和回路
(1)路径:顶点序列。
(2)路径长度:路径上边的数目。
(3)回路/环:第一个和最后一个顶点相同的路径。 - 简单路径和简单回路
(1)简单路径:顶点不重复出现的路径。
(2)简单回路:除第一个和最后一个,其余顶点不重复出现的回路。 - 距离
点到点的距离:最短路径(if 存在)、无穷(if 不存在)。 - 有向树
(1)树:不存在回路且连通的无向图。n个顶点的树必有n-1条边。n个顶点的图,若 ∣ E ∣ > n − 1 |E| > n-1 ∣E∣>n−1,则一定有回路。
(2)有向树:一个顶点的入度为0、其余顶点的入度均为1的有向图。
3、常见考点总结
- 对于n个顶点的无向图G
- 所有顶点度之和=2|E|
- 若G是连通图,则最少有n-1条边(树),若 ∣ E ∣ > n − 1 |E|>n-1 ∣E∣>n−1,则一定有回路
- 若G是非连通图,则最多可能有 C n − 1 2 C_{n-1}^{2} Cn−12条边
- 无向完全图共有 C n 2 C_{n}^{2} Cn2条边
- 对于n个顶点的有向图G
- 所有顶点的出度之和=入度之和=|E|
- 所有顶点的度之和=2|E|
- 若G是强连通图,则最少有n条边(形成回路)
- 有向完全图共有 2 C n 2 2C_{n}^{2} 2Cn2条边
二、图的存储结构
四种存储结构,各自手动实现、画图、性质、适用场景。物理存储,掌握如何画图,分步实现。
(一)邻接矩阵(顺序)
1、无向图、有向图及其邻接矩阵
- 当A[i][j]=1是E(G)中的边;=0则不是。
- 无向图:
(1)第i个结点的度 = 第i行/第i列的非零元素个数。
(2)时间复杂度O(n)/O(|V|)。 - 有向图:
(1)出度 = 行的非零元素个数;入度 = 列;度 = 行+列
(2)时间复杂度O(|V|)。
2、网及其邻接矩阵
- 有边相连:A[i][j]=wij;无边:A[i][j]=0或
∞
\infty
∞。
注:默认不相连为 ∞ \infty ∞,部分会将对角线设置为0。
3、邻接矩阵法性能分析
- 空间复杂度: O ( n ) + O ( n 2 ) = O ( ∣ V ∣ 2 ) O(n)+O(n^{2})=O(|V|^{2}) O(n)+O(n2)=O(∣V∣2)。只与顶点数相关,和实际边数无关。适合存储稠密图。、
- 无向图的邻接矩阵是对称矩阵,可以压缩存储上三角或下三角区。
- 设图邻接矩阵为A(元素为0/1),则 A n [ i ] [ j ] A^{n}[i][j] An[i][j]等于由定点i到顶点j的长度为n的路径树数目。
- 只要确定了顶点编号,邻接矩阵唯一。
4、邻接存储结构定义(了解)
#define MaxVertexNum 100 //顶点数目的最大值
#define INFINITY 最大的int值 //宏常量无穷
typedef struct{
char Ver[MaxVertextNum]; //顶点表
int Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表;0/1时可以考虑用bool或枚举类型
int vexnum,arcnum; //图的当前顶点数和边数/弧数
}MGraph;
(二)邻接表(顺序+链式)
1、无向图、有向图及其邻接表
- 无向图:边结点的数量是2|E|,整体空间复杂度为 O ( ∣ V ∣ + 2 ∣ E ∣ ) O(|V|+2|E|) O(∣V∣+2∣E∣)
- 有向图:边结点的数量是|E|,整体空间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
- 有向图中求一个顶点的出度只需要计算其邻接表中的结点个数;但求其顶点的入度需要遍历全部的邻接表。因此有人采用逆邻接表来加速求解给定顶点的入度。
2、邻接表法性能分析
- 图的邻接表表示方法并不唯一(链接次序任意)。
- 对于稀疏图,采用邻接表将极大地节省存储空间。
邻接表 | 邻接矩阵 | |
---|---|---|
空间复杂度 | O(|V|+2|E|)或O(|V|+|E|) | O(|V|^2) |
适合用于 | 存储稀疏图 | 存储稠密图 |
表示方式 | 不唯一 | 唯一 |
计算度/出度/入度 | 计算有向图的度、入度不方便,其余都方便 | 必须遍历对应行或列 |
找相邻的边 | 找有向图的入边不方便,其余很方便 | 必须遍历对应行或列 |
3、邻接表存储结构定义(了解)
#define MaxVertexNum 100
//“边/弧”
typedef struct ArcNode{
int adjvex; //边/弧指向哪个结点
struct ArcNode *next; //指向下一条弧的指针
//InfoType info; //边权值
}ArcNode;
//“顶点”
typedef struct VNode{
VertexType data; //顶点信息
ArcNode *firsr; //第一条边/弧
}VNode, AdjList[MaxVertexNum];
//用邻接表存储的图
typedef struct{
AdjList vertices;
int vexnum,arcnum;
}ALGraph;
(三)逆邻接表
- 存储方式和邻接表一样,看入边
(四)十字链表(链式、有向)
- 空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
- 顺着绿色线路找到指定顶点的所有出边;橙色找到入边。
- 十字链表只用于存储有向图。
(五)邻接多重表(链式、无向)
- 用以解决邻接表(每条边对应两份冗余信息、删除操作时间复杂度高)、邻接矩阵(空间复杂度高)。
- 空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。
- 每条边只对应一份数据,删除边/结点等操作很方便。
- 邻接多重表只适用于存储无向图。
三、遍历算法
图的遍历算法中使用较多的、图的基本操作:
FirstNeighbor(G,X)
:求G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。
NextNeighbor(G,x,y)
:假设G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。
1、广度优先搜索(BFS)
(1)实现过程
- 概念:类似于二叉树的层序遍历算法。
- 要点:
- 找到与一个顶点相邻的所有顶点
- 标记哪些顶点被访问过
- 需要一个辅助队列
- 手推过程示例:
- 对于同一个图,基于邻接矩阵的遍历所得到的DFS序列和BFS序列是唯一的,基于邻接表的遍历多得到的DFS序列和BFS序列不唯一。(eg.顶点3:467/674…)
- 如果G是非连通图,则在当前遍历结束后,检查初始标记数组中是否还有标记为false的顶点,若有则对其进行下一轮遍历。改进版代码→添加函数对标记数组进行循环判断。结论:对于无向图,调用BFS函数的次数=连通分量数
(2)BFS算法的性能分析(选填)
- 空间复杂度:最坏情况,辅助队列大小为
O
(
∣
V
∣
)
O(|V|)
O(∣V∣)。
- 时间复杂度:
- 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。访问|V|个结点 O ( ∣ V ∣ ) O(|V|) O(∣V∣) + 查找|V|个邻接点 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) = O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
- 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。访问|V|个结点 O ( ∣ V ∣ ) O(|V|) O(∣V∣) + 查找共需要 O ( ∣ E ∣ ) O(|E|) O(∣E∣) = O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
(3)广度优先生成树(性质 选填)
- 广度优先生成树由广度优先遍历过程确定。由于邻接表表示方式不唯一,因此基于邻接表的广度优先生成树也不唯一。
- 对非连通图的广度优先遍历可得到广度优先生成森林。
(4)BFS求解单源最短路径问题(用途)
无权图可以视为一种特殊的带权图,只是每条边的权值都为1
- 对BFS的小修改,在visit一个顶点时,修改其最短路径长度d[ ]并在path[ ]记录前驱结点。(可以结合生成树理解)
- BFS求单源最短路径只适用于无权图,或所有边的权值都相同的图。
- 时间复杂度:邻接矩阵 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2);邻接表 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
2、深度优先搜索(DFS)
(1)实现过程
- 概念:类似树的先根遍历
- 手推过程示例(w为尚未访问的邻接顶点)
- 若G是非连通图,无法遍历完所有结点。处理方式和BFS一致,循环标记数组进行判断。
(2)DFS性能分析(选填)
- 空间复杂度:
- 最坏情况:
O
(
∣
V
∣
)
O(|V|)
O(∣V∣)(函数调用栈递归深度)
- 最好情况:
O
(
1
)
O(1)
O(1)
- 最坏情况:
O
(
∣
V
∣
)
O(|V|)
O(∣V∣)(函数调用栈递归深度)
- 时间复杂度(访问+探索)(和BFS一样)
- 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
- 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。
(3)深度优先生成树
- 基于邻接表的深度优先生成树不唯一。
3、图的遍历和图的连通性(用途 选填)
- 对无向图进行BFS/DFS遍历,调用BFS/DFS函数的次数=连通分量数。
- 对于连通图,只需调用1次BFS/DFS。
- 对有向图进行BFS/DFS遍历,调用BFS/DFS次数要具体分析。
- 若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数.
- 对于强连通图,从任一结点出发都只需调用1次BFS/DFS。
四、图的应用
最小生成树;最短路径,拓扑排序和关键路径
六大算法:
手推(简答):步骤严格按顺序;表图按规范。
性质(选填):时间/空间复杂度;适用场景(稠/稀、有向/无向)
(一)最小生成树/最小代价树
- 连通图的生成树是包含图中全部顶点的一个极小连通子图。
- 带权连通无向图中,边的权值之和最小的生成树为最小生成树MST。(性质选填)
- 最小生成树不唯一,但边的权值之和总是唯一且最小。
- 最小生成树的边数 = 顶点数 - 1。砍掉一条则不连通,增加一条则会出现回路。
- 如果一个连通图本身就是一棵树,则其最小生成树就是它本身。
- 只有连通图才有生成树,非连通图只有生成森林。
1、① Prim算法(普里姆)
- 算法:从某一个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。
- 性质:
- 时间复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
- 适用边稠密图
- 基于贪心算法
2、② Kruskal算法(克鲁斯卡尔)
- 算法:每次选择权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通。
- 性质:
- 时间复杂度
O
(
∣
E
∣
l
o
g
2
∣
E
∣
)
O(|E|log_2|E|)
O(∣E∣log2∣E∣)
- 通常采用堆来存放边的集合,因此 每次选择最小权值的边只需 O ( l o g 2 ∣ E ∣ ) O(log_2|E|) O(log2∣E∣)
- 适用边稀疏图
- 基于贪心算法
- 时间复杂度
O
(
∣
E
∣
l
o
g
2
∣
E
∣
)
O(|E|log_2|E|)
O(∣E∣log2∣E∣)
(二)最短路径
- 最短路径:带权图中,把从一个顶点到图中任意一个顶点的一条路径所经过边上的权值之和,定义为该路径的带权路径长度。把带权路径长度最短的那条路径成为最短路径。
- 依赖性质:两点之间的最短路径也包含了路径上其他顶点间的最短路径。
- 最短路径问题
- 单源最短路径:
- BFS(无权图)(见前文)
- Dijkstra(带权图、无权图)
- 各顶点间的最短路径
- Floyd(带权图、无权图)
- 单源最短路径:
1、③ Dijkstra算法
- 算法:
- 初始化:(从V0开始)
- 第1轮:循环遍历所有结点,找到还没确定最短路径且dist最小的顶点Vi,令final[i]=ture;检查所有邻接自Vi的顶点,若其final值为false,则更dist和path消息
- 第2轮:
- 第3轮:
- 第4轮:(找不到其余false值,算法结束)
- 初始化:(从V0开始)
- 使用数组信息:
- V0到V2的最短带权路径长度为dist[2]=9
- path[ ]可知,V0到V2的最短带权路径V2←V1←V4←V0
- 求解过程(手推表格,重要)
- 性质
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)即
O
(
∣
V
∣
2
)
O(|V|^2)
O(∣V∣2)。
- n-1轮处理,每轮O(n)+O(n)。
- 使用邻接矩阵表示时,时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。使用带权的邻接表表示时,虽然修改dist[ ]的时间可以减少,但由于在dist[ ]中选择最小分量的时间不变,时间复杂度仍为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
- 和Prim算法实现思想部分类似。
- 基于贪心算法。
- Dijkstra算法不适用于有负权值的带权图。
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)即
O
(
∣
V
∣
2
)
O(|V|^2)
O(∣V∣2)。
2、④ Floyd算法
- 算法
- 初始化:不允许在其他顶点中转时,最短路径-1。
- 第0轮:若允许在
V
0
V_0
V0中转,最短路径?——求
A
(
0
)
A^{(0)}
A(0)和
p
a
t
h
(
0
)
path^{(0)}
path(0)
- 通用算法:依次扫描所有元素。
- 若
A
(
K
−
1
)
[
i
]
[
j
]
>
A
(
K
−
1
)
[
i
]
[
k
]
+
A
(
K
−
1
)
[
k
]
[
j
]
A^{(K-1)}[i][j]>A^{(K-1)}[i][k]+A^{(K-1)}[k][j]
A(K−1)[i][j]>A(K−1)[i][k]+A(K−1)[k][j]
- 则 A ( K ) [ i ] [ j ] = A ( K ) [ i ] [ k ] + A ( K ) [ k ] [ j ] A^{(K)}[i][j]=A^{(K)}[i][k]+A^{(K)}[k][j] A(K)[i][j]=A(K)[i][k]+A(K)[k][j]; p a t h ( K ) [ i ] [ j ] = k path^{(K)}[i][j]=k path(K)[i][j]=k
- 否则 A ( k ) A^{(k)} A(k)和 p a t h ( k ) path^{(k)} path(k)保持原值
- 若
A
(
K
−
1
)
[
i
]
[
j
]
>
A
(
K
−
1
)
[
i
]
[
k
]
+
A
(
K
−
1
)
[
k
]
[
j
]
A^{(K-1)}[i][j]>A^{(K-1)}[i][k]+A^{(K-1)}[k][j]
A(K−1)[i][j]>A(K−1)[i][k]+A(K−1)[k][j]
- 此处仅有
A
(
−
1
)
[
2
]
[
1
]
>
A
(
−
1
)
[
2
]
[
0
]
+
A
(
−
1
)
[
0
]
[
1
]
=
11
A^{(-1)}[2][1]>A^{(-1)}[2][0]+A^{(-1)}[0][1]=11
A(−1)[2][1]>A(−1)[2][0]+A(−1)[0][1]=11;
- 则
A
(
0
)
[
2
]
[
1
]
=
11
A^{(0)}[2][1]=11
A(0)[2][1]=11、
p
a
t
h
(
0
)
[
2
]
[
1
]
=
0
path^{(0)}[2][1]=0
path(0)[2][1]=0
- 则
A
(
0
)
[
2
]
[
1
]
=
11
A^{(0)}[2][1]=11
A(0)[2][1]=11、
p
a
t
h
(
0
)
[
2
]
[
1
]
=
0
path^{(0)}[2][1]=0
path(0)[2][1]=0
- 通用算法:依次扫描所有元素。
- 第1轮:若允许在
V
0
、
V
1
V_0、V_1
V0、V1中转,最短路径?——求
A
(
1
)
A^{(1)}
A(1)和
p
a
t
h
(
1
)
path^{(1)}
path(1)
- 此处仅有
A
(
0
)
[
0
]
[
2
]
>
A
(
0
)
[
0
]
[
1
]
+
A
(
0
)
[
1
]
[
2
]
=
10
A^{(0)}[0][2]>A^{(0)}[0][1]+A^{(0)}[1][2]=10
A(0)[0][2]>A(0)[0][1]+A(0)[1][2]=10;
- 则
A
(
1
)
[
0
]
[
2
]
=
10
A^{(1)}[0][2]=10
A(1)[0][2]=10、
p
a
t
h
(
1
)
[
0
]
[
2
]
=
1
path^{(1)}[0][2]=1
path(1)[0][2]=1
- 则
A
(
1
)
[
0
]
[
2
]
=
10
A^{(1)}[0][2]=10
A(1)[0][2]=10、
p
a
t
h
(
1
)
[
0
]
[
2
]
=
1
path^{(1)}[0][2]=1
path(1)[0][2]=1
- 此处仅有
A
(
0
)
[
0
]
[
2
]
>
A
(
0
)
[
0
]
[
1
]
+
A
(
0
)
[
1
]
[
2
]
=
10
A^{(0)}[0][2]>A^{(0)}[0][1]+A^{(0)}[1][2]=10
A(0)[0][2]>A(0)[0][1]+A(0)[1][2]=10;
- 第2轮:若允许在
V
0
、
V
1
、
V
2
V_0、V_1、V_2
V0、V1、V2中转,最短路径?——求
A
(
2
)
A^{(2)}
A(2)和
p
a
t
h
(
2
)
path^{(2)}
path(2)
- 此处仅有
A
(
1
)
[
1
]
[
0
]
>
A
(
1
)
[
1
]
[
2
]
+
A
(
1
)
[
2
]
[
0
]
=
9
A^{(1)}[1][0]>A^{(1)}[1][2]+A^{(1)}[2][0]=9
A(1)[1][0]>A(1)[1][2]+A(1)[2][0]=9;
- 则
A
(
2
)
[
1
]
[
0
]
=
9
A^{(2)}[1][0]=9
A(2)[1][0]=9、
p
a
t
h
(
2
)
[
1
]
[
0
]
=
2
path^{(2)}[1][0]=2
path(2)[1][0]=2
- 则
A
(
2
)
[
1
]
[
0
]
=
9
A^{(2)}[1][0]=9
A(2)[1][0]=9、
p
a
t
h
(
2
)
[
1
]
[
0
]
=
2
path^{(2)}[1][0]=2
path(2)[1][0]=2
- 此处仅有
A
(
1
)
[
1
]
[
0
]
>
A
(
1
)
[
1
]
[
2
]
+
A
(
1
)
[
2
]
[
0
]
=
9
A^{(1)}[1][0]>A^{(1)}[1][2]+A^{(1)}[2][0]=9
A(1)[1][0]>A(1)[1][2]+A(1)[2][0]=9;
- 从
A
(
−
1
)
A^{(-1)}
A(−1)和
p
a
t
h
(
−
1
)
path^{(-1)}
path(−1)开始,经过n轮递推,得到
A
(
n
−
1
)
A^{(n-1)}
A(n−1)和
p
a
t
h
(
n
−
1
)
path^{(n-1)}
path(n−1)。
- 根据 A ( 2 ) A^{(2)} A(2)可知, V 1 V_1 V1到 V 2 V_2 V2最短路径长度为4, V 0 V_0 V0到 V 2 V_2 V2最短路径长度为10。
- 根据 p a t h ( 2 ) path^{(2)} path(2)可知,完整路径信息分别为 V 1 _ V 2 V_1 \_ V_2 V1_V2和 V 0 _ V 1 _ V 2 V_0 \_ V_1 \_ V_2 V0_V1_V2
- 初始化:不允许在其他顶点中转时,最短路径-1。
- 手推过程,重要
- 性质
- 时间复杂度 O ( ∣ V ∣ 3 ) O(|V|^3) O(∣V∣3)。(循环嵌套三次)
- 空间复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
- 使用动态规划思想。
- Floyd算法可以用于负权值带权图。
- 不能解决带有“负权回路”的图,这种图可能没有最短路径。
- 也可以用Dijkstra算法求所有顶点间的最短路径,重复 ∣ V ∣ |V| ∣V∣次即可,总的时间复杂度 ∣ V ∣ O ( ∣ V ∣ 2 ) = O ( ∣ V ∣ 3 ) |V|O(|V|^2)=O(|V|^3) ∣V∣O(∣V∣2)=O(∣V∣3)
3、总结
(三)拓扑排序
1、DAG图
- 有向无环图(DAG):若一个有向图中不存在环。
- DAG描述表达式:
(
(
a
+
b
)
∗
(
b
∗
(
c
+
d
)
)
+
(
c
+
d
)
∗
e
)
∗
(
(
c
+
d
)
∗
e
)
((a+b)*(b*(c+d))+(c+d)*e)*((c+d)*e)
((a+b)∗(b∗(c+d))+(c+d)∗e)∗((c+d)∗e)
- 实现对相同子式的共享,从而节省存储空间。
- DAG顶点中不可能出现重复的操作数。求解方法:
- Step1~3
- Step4
- 最终结果可能不唯一。408只考过1次。
- DAG描述表达式:
(
(
a
+
b
)
∗
(
b
∗
(
c
+
d
)
)
+
(
c
+
d
)
∗
e
)
∗
(
(
c
+
d
)
∗
e
)
((a+b)*(b*(c+d))+(c+d)*e)*((c+d)*e)
((a+b)∗(b∗(c+d))+(c+d)∗e)∗((c+d)∗e)
2、AOV图
- AOV图(用顶点表示活动的网):用DAG图表示一个工程。顶点表示活动,有向边 < V i , V j > <V_i,V_j> <Vi,Vj>表示活动 V i V_i Vi必须先于活动 V j V_j Vj进行。
3、⑤ 拓扑排序
- 拓扑排序:找到做事的先后顺序。每个AVO网都有一个或多个拓扑排序序列。
- 拓扑排序的实现:
①从AOV网中选择一个没有前驱(入度为0)的顶点并输出。
②从网中删除该顶点和所有以它为起点的有向边。
③重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止(说明有回路)。 - 手推过程:
- 性质:
- 时间复杂度:
- 邻接表 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。输出每个顶点同时处理边。
- 邻接矩阵 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2).
- 空间复杂度: O ( ∣ V ∣ ) O(|V|) O(∣V∣)
- 若图中有环,则不存在拓扑排序序列/逆拓扑排序。
- 由于AOV网中各顶点的地位平等,每个顶点的编号是人为的,因此可以按拓扑排序的结果重新编号,生成AOV网的新的邻接存储矩阵,这种矩阵可以是三角矩阵。对于一般的图来说,若其邻接矩阵是三角矩阵,则存在拓扑序列。反之不一定。
- 时间复杂度:
- 拓扑排序的实现:
- 逆拓扑排序
- 实现过程
①从AOV网中选择一个没有后继(出度为0)的顶点并输出。
②从网中删除该顶点和所有以它为终点的有向边。
③重复①和②直到当前的AOV网为空。 - 可用DFS实现逆拓扑排序:在顶点退栈前输出。
- 实现过程
(四)关键路径
1、AOE网及相关定义
- AOE网(用边表示活动的网络):在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销。
- 性质:(选填)
- ①只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始;
- ②只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的时间才能发生。
- 有些活动是可以并行进行的。
- 开始顶点(源点):在AOE网中仅有一个入度为0的顶点,它表示整个工程的开始。
- 结束顶点(汇点):仅有一个出度为0的顶点,它表示整个工程的结束。
- 从源点到汇点的所有有向路径中,具有最大路径长度的称为关键路径,把关键路径上的活动称为关键活动
- 关键路径的长度:完成整个工程的最短时间。若关键活动不能按时完成,则整个工程的完成时间就会延长。
2、寻找关键活动
- 参量定义
- 事件 v k v_k vk的最早发生时间 v e ( k ) ve(k) ve(k):决定了所有从 v k v_k vk开始的活动能够开工的最早时间。
- 活动 a i a_i ai的最早开始时间 e ( i ) e(i) e(i):该活动弧的起点所表示的事件的最早发生时间。
- 事件 v k v_k vk的最迟发生时间 v l ( k ) vl(k) vl(k):在不推迟整个工程完成的前提下,该事件最迟必须发生的时间。
- 活动 a i a_i ai的最迟开始时间 l ( i ) l(i) l(i):该活动弧的重点所表示事件的最迟发生时间与该活动所需时间之差。
- 活动 a i a_i ai的时间余量 d ( i ) = l ( i ) − e ( i ) d(i)=l(i)-e(i) d(i)=l(i)−e(i):在不增加完成整个工程所需总时间的情况下,活动 a i a_i ai可以拖延的时间。
- 若一个活动的时间余量为0,则说明该活动必须要如期完成。 d ( i ) = 0 d(i)=0 d(i)=0即 l ( i ) = e ( i ) l(i)=e(i) l(i)=e(i)的活动 a i a_i ai是关键活动。
- 由关键活动组成的路径就是关键路径。
3、⑥ 求关键路径
- 步骤:
- Step1、求所有事件的最早发生时间
v
e
(
)
ve()
ve()
- 按拓扑排序序列,依次求各个顶点的
v
e
(
k
)
ve(k)
ve(k)
- 按拓扑排序序列,依次求各个顶点的
v
e
(
k
)
ve(k)
ve(k)
- Step2、求所有事件的最迟发生时间
v
l
(
)
vl()
vl()
- 按逆拓扑排序序列,依次求各个顶点的
v
l
(
k
)
vl(k)
vl(k);
- 按逆拓扑排序序列,依次求各个顶点的
v
l
(
k
)
vl(k)
vl(k);
- Step3、求所有活动的最早发生时间
e
(
)
e()
e()
- 若边
<
v
k
,
v
j
>
<v_k,v_j>
<vk,vj>表示活动
a
i
a_i
ai,则有
e
(
i
)
=
v
e
(
k
)
e(i)=ve(k)
e(i)=ve(k)
- 若边
<
v
k
,
v
j
>
<v_k,v_j>
<vk,vj>表示活动
a
i
a_i
ai,则有
e
(
i
)
=
v
e
(
k
)
e(i)=ve(k)
e(i)=ve(k)
- Step4、求所有活动的最迟发生时间
l
(
)
l()
l()
- 若边
<
v
k
,
v
j
>
<v_k,v_j>
<vk,vj>表示活动
a
i
a_i
ai,则有
l
(
i
)
=
v
l
(
j
)
−
W
e
i
g
h
t
(
v
k
,
v
j
)
l(i)=vl(j)-Weight(v_k,v_j)
l(i)=vl(j)−Weight(vk,vj)
- 若边
<
v
k
,
v
j
>
<v_k,v_j>
<vk,vj>表示活动
a
i
a_i
ai,则有
l
(
i
)
=
v
l
(
j
)
−
W
e
i
g
h
t
(
v
k
,
v
j
)
l(i)=vl(j)-Weight(v_k,v_j)
l(i)=vl(j)−Weight(vk,vj)
- Step5、求所有活动的时间余量
d
(
)
d()
d()
-
d
(
i
)
=
l
(
i
)
−
e
(
i
)
d(i)=l(i)-e(i)
d(i)=l(i)−e(i)
-
d
(
i
)
=
l
(
i
)
−
e
(
i
)
d(i)=l(i)-e(i)
d(i)=l(i)−e(i)
- 结论: d ( k ) = 0 d(k)=0 d(k)=0,关键活动: a 2 、 a 5 、 a 7 a_2、a_5、a_7 a2、a5、a7
- 关键路径: V 1 → V 3 → V 4 → V 6 V1→V3→V4→V6 V1→V3→V4→V6
- Step1、求所有事件的最早发生时间
v
e
(
)
ve()
ve()
- 手推过程:
- 关键活动、关键路径特性:
- 若关键活动耗时增加,则整个工程的工期将增长
- 缩短关键活动的时间,可以缩短整个工程的工期
- 当缩短到一定程度,关键活动可能变成非关键活动。
- 可能有多条关键路径,只提高一条关键路径上的关键活动速度并不能缩短整个工程的工期,只有加快那些包括在所有关键路径上的关键活动才能达到缩短工期的目的。