数据结构图论部分知识点总结:
第9章 图论
一、AOV网
AOV网:一个有向无环图。含义是用AOV网表示各个事件的先后关系,对于每一条边,终点(弧尾)以始点(弧尾)为先决条件,而一个点不可能以自己为先决条件。
1.拓扑排序
拓扑排序
生成的拓扑序列
可以用来检测一个有向图是不是AOV网,即判断该有向图有没有回路。
(1)内容:
-
对于一个n个点,e条边的图,任选入度为0的点输出,
-
然后删除该点即所有出边。
-
重复直至没有入度为0的边
① 输出的顶点数 < 总顶点数:存在有向回路,不是AOV网。
② 输出的定点数 > 总顶点数:不存在有向回路,是AOV网,此时输出的序列为拓扑序列。
(2)算法设计:
- 定义 邻 接 表 \textcolor{red}{邻接表} 邻接表作为图的存储结构。
- 定义
inDegree数组
存储所有顶点的入度。
初始化操作可以循环遍历邻接表统计所有顶点的入度。
- 定义
topo数组
存储输出的拓扑序列。 - 定义一个
栈
存储所有顶点入度为0的顶点,包括新出现的入度为0的顶点。
每删除一个点和边(对该点邻接到的点入度减一),然后检测删除点的邻接点的入度,入度为0就入栈。
- 对入度为0的顶点的选择可以通过出栈操作实现。出栈前先检测栈是否为空,即当前有无入度为0的顶点,没有就结束拓扑排序。
(3)时间复杂度
O ( n + e ) O(n+e) O(n+e)
二、AOE网及关键路径
AOE网:把一个工程看作一个AOE网。一个AOE网是一种特殊的AOV网。
- 有且仅有一个入度为0的点,称为源点(工程的开始)。
- 有且仅有一个出度为0的点,称为汇点(工程的结束)。
- 不存在回路。(无环)
- 且每条边都带权的有向图。
关键路径:从源点到汇点的最长路径。代表一个工程的最短完成时间。
1.关键路径的求解步骤
(1)对AOE网进行拓扑排序
(2)用下列公式按拓扑序列求出每个
事
件
\textcolor{red}{事件}
事件可能的最早发生时间
E
e
a
r
l
y
(
v
i
)
E_{early}(v_i)
Eearly(vi)
x = { E e a r l y ( v 0 ) = 0 E e a r l y ( v j ) = m a x { E e a r l y ( v i ) + w ( v i , v j ) } 0 < j < n x = \begin{cases}E_{early}(v_0)=0\\E_{early}(v_j)=max\text\{E_{early}(v_{i})+w(v_i,v_j)\}&0<j<n\end{cases} x={Eearly(v0)=0Eearly(vj)=max{Eearly(vi)+w(vi,vj)}0<j<n
(3)用下列公式按逆拓扑序列(即从后往前)和
E
e
a
r
l
y
(
汇
点
)
E_{early}(汇点)
Eearly(汇点)求出每个
事
件
\textcolor{red}{事件}
事件允许的最迟发生时间
E
l
a
t
e
(
v
i
)
E_{late}(v_i)
Elate(vi)
x = { E l a t e ( v n − 1 ) = E e a r l y ( v n − 1 ) E l a t e ( v i ) = m i n { E l a t e ( v j ) − w ( v i , v j ) } 0 ≤ i < n − 1 } x = \begin{cases}E_{late}(v_{n-1})=E_{early}(v_{n-1})\\E_{late}(v_i)=min\text\{E_{late}(v_{j})-w(v_i,v_j)\}&0\leq i<n-1\}\end{cases} x={Elate(vn−1)=Eearly(vn−1)Elate(vi)=min{Elate(vj)−w(vi,vj)}0≤i<n−1}
(4)求出每个 活 动 \textcolor{red}{活动} 活动可能的最早开始时间 A e a r l y ( a k ) A_{early}(a_k) Aearly(ak)
A e a r l y ( a k ) = E e a r l y ( v i ) A_{early}(a_{k})=E_{early}(v_i) Aearly(ak)=Eearly(vi),其中ak关联vi和vj。
(5)求出每个 活 动 \textcolor{red}{活动} 活动允许的最迟开始时间 A l a t e ( a k ) A_{late}(a_k) Alate(ak)
A l a t e ( a k ) = E l a t e ( v j ) − w ( v i , v j ) A_{late}(a_{k})=E_{late}(v_j)-w(v_{i},v_{j}) Alate(ak)=Elate(vj)−w(vi,vj),其中ak关联vi和vj。
(6)找出 A e a r l y ( a k ) = A l a t e ( a k ) A_{early}(a_k)=A_{late}(a_k) Aearly(ak)=Alate(ak)的活动ak即为关键活动,由关键活动形成的由源点到汇点的路径即为关键路径。
2.关键路径算法
(1)算法实现
- 用一个数组存储可能最早发生时间,遍历拓扑序列,每次更新数组元素值
- 另一个数组存储允许最迟发生时间,遍历逆拓扑序列,每次更新数组元素值
(2)时间复杂度
O ( n + e ) O(n+e) O(n+e)
二、图的最小代价生成树
- n个顶点的无向连通图最少有n-1条边。
- 生成树:n个顶点的无向连通图的生成树是一棵极小连通子图,包括该图中的所有顶点,有足以构成一棵树的n-1条边。
一个带权图的各生成树中,具有最小代价的生成树(各边上的代价之和)称为该网络的最小代价生成树。关于如何构造最小代价生成树有普利姆算法
和布鲁斯卡尔算法
。
1.普利姆算法
(1)内容:
G为含n个顶点的带权连通网,T是正在构造中的生成树(初始状态下T只有一个任选的起始顶点,且无边),从初始状态开始,重复执行下列运算:
①从T的入边中找一条代价最小的边e(出现多条时任选其一)
②将e及其不属于T的那个顶点加入T;
③重复(1)(2)直至所有顶点加入T。
(2)算法设计:
所需数据结构如下:
- 图采用 邻 接 表 \textcolor{red}{邻接表} 邻接表存储
- 定义一维数组
nearest[]
,lowcost[]
和mark[]
,并初始化,
其中
nearest[i]
存放当前生成树中与顶点i
距离最近的顶点,lowcost[i]
存放边(i,nearest[i])
的权值,mark[i]
标记顶点i是否已经在生成树上。
初始状态下nearest数组中的元素置为-1,lowcost数组中的元素置为INFTY(一个极大值),mark数组所有元素置TRUE。
-
初始顶点加入生成树T。
-
对于尚未在生成树中的顶点v,如果(u,v)是v与生成树T中若干顶点构成的边中权值最小的,则设置nearest[v]=u,lowcost[v]=w(u,v)。
生成树每新加入一个点就要对新加入的点遍历其边链表,将其和树外点间的权值与对应的lowcost值作比较,更新nearest[v]和lowcost[v]
mark[v]=True
代表v
加入了生成树。
(3)时间复杂度:
O ( n 2 ) O(n^{2}) O(n2)
2.克鲁斯卡尔算法
(1)内容:
T:正在构造中的生成树。
G:图
E:图的边集合
E’:生成树的边集合
从初始状态(此时T中只包含G中所有顶点,无边)开始,重复执行下列运算:
①在E中选择一条代价最小的边(u,v), 并将其从E中删除,加入T;
②若在生成树的边集合E’中加入边(u,v)以后未形成回路,则将其加进E’中,否则继续从E中选择下一条边;
③重复执行步骤② ,直至E’中包含n-1条边时为止。
(2)算法设计:
所需数据结构如下:
- 图采用 邻 接 矩 阵 \textcolor{red}{邻接矩阵} 邻接矩阵存储
- 一维结构体数组
edgeSet[]
用于存储图中所有边的信息,包括边的两个顶点和边的权值,edgeSet[i]存放图中的一条边。 - 一维数组
vexSet[]
用于标识各顶点所属的连通分量,其中vexSet[i]
表示顶点i
所属连通分量,初始时vexSet[i]=i
,表示自身构成一个连通分量。
关键步骤:
- 从邻接矩阵获取所有边存入edgeSet
- 用排序算法对edgeSet中的边按权值从小到大排列选出
- 用数组vexSet[]存储各点所属联通分量,选择权值最小的边插入T,并判断两个边的顶点
u
和v
是否在一个连通分量即vexSet[u]和vexSet[v]是否相等,vexSet[u]!=vexSet[v]
,不会形成回路,然后合并这两个连通分量vexSet[u]==vexSet[v]
,形成回路,选择次小继续判断。
(3)时间复杂度:
O ( e ∗ l o g 2 e ) O(e*log_2e) O(e∗log2e)
三、图的单源最短路径
1.迪杰斯特拉算法
(1)算法设计
所需数据结构如下:
- 采用邻接矩阵存储
- s[]记录对应下标的顶点是否在S集合中,即是否确定最短路径。
- d[]存放v0到下标对应点的当前最短路径的长度(注意:这里的d[]存放的最短路径值跟最后的不一定一致)
- path[]存储下标对应顶点的前驱结点。
主要步骤:
-
先初始化数组值。
s[0]置1,其余为0。
d[i]如果v0到顶点vi存在边则置权值,不存在边则置无穷大的数。
path[i]如果v0到顶点vi存在边则置0,不存在边则置-1。
-
循环选出最小的d[k],得到对应k,置s[k]=1,进入下一轮选择前要先更新d[w](w是V-S集合中的某个点)的值,循环遍历当前V-S集合中的各个点,比较d[k]+w[k][w]与d[w]的大小,小的那个录入d[w],还要更新path[w]。
(2)时间复杂度
-
O ( n 2 ) O(n^2) O(n2)
-
求源点到某一顶点间的最短路径: O ( n 2 ) O(n^2) O(n2)
-
求任意两对顶点间的最短路径(每次选一个顶点为源点重复n次): O ( n 3 ) O(n^3) O(n3)
w[k][w]与d[w]的大小,小的那个录入d[w],还要更新path[w]。
四、总结:
拓扑排序 | 求关键路径 | 普利姆算法 | 克鲁斯卡尔算法 | 迪杰斯特拉算法 | |
---|---|---|---|---|---|
图的存储结构 | 邻接表 | 邻接表 | 邻接表 | 邻 接 矩 阵 \textcolor{red}{邻接矩阵} 邻接矩阵 | 邻 接 矩 阵 \textcolor{red}{邻接矩阵} 邻接矩阵 |
算法用到的数据结构 | 数组、 栈 \textcolor{red}{栈} 栈 | 数组 | 数组 | 结构体数组 | 数组 |
时间复杂度 | O ( n + e ) O(n+e) O(n+e) | O ( n + e ) O(n+e) O(n+e) | O ( n 2 ) O(n^2) O(n2) | O ( e l o g 2 e ) O(elog_2e) O(elog2e) | O ( n 2 ) O(n^2) O(n2) |