Upload
james-blankenship
View
44
Download
2
Embed Size (px)
DESCRIPTION
图论(二). 李子星. 内容提要. 最短路径问题 单源最短路径问题 Dijkstra 算法 Bellman-Ford 算法 SPFA 算法 任意点对最短路径问题 Floyd 算法 最小生成树问题 无向图的最小生成树问题 Prim 算法 Kruskal 算法 有向图的最小生成树问题. 单源最短路径问题. 给定一个有 n 个点的带权有向图,问从指定的起点开始,到其他所有点的最短路径。. 单源最短路径问题. 2. 2. 3. 8. 1. 1. 3. 10. 1. 4. 3. 5. 4. 4. 1. 6. 5. 3. - PowerPoint PPT Presentation
Citation preview
图论(二)
李子星
内容提要• 最短路径问题
– 单源最短路径问题• Dijkstra算法• Bellman-Ford算法• SPFA算法
– 任意点对最短路径问题• Floyd算法
• 最小生成树问题– 无向图的最小生成树问题
• Prim算法• Kruskal算法
– 有向图的最小生成树问题
单源最短路径问题
给定一个有 n个点的带权有向图,问从指定的起点开始,到其他所有点的最短路径。
单源最短路径问题
1
2
6 5
3
4
1
2
8
4
5
3
3
1
1
103
4
单源最短路径问题• 带权图中一个点到另一个点的最短路径一定唯一存在?–有路径通达且没有“负权回路”则一定存在–不一定唯一
• 单源最短路径是否一定能构成一棵树?–如果源点到其他所有点的最短路径都存在,则一定能找到一棵以指定的起点为根的树(边的方向总是从父亲指向儿子)
单源最短路径问题
1
2
6 5
3
4
1
2
8
4
5
-3
3
1
1
103
4
Dijkstra算法
如果边权没有负值,可以用 Dijkstra算法求解单源最短路径问题。
Dijkstra算法算法流程:1. 初始化所有点的 dist值为 +∞,起点的 di
st值为 0
2. 从所有点中找到未被标记的 dist值最小的点 x,将 x点标记
3. 检查所有 x引出的边,用 x点的 dist值与这条边的权值之和更新这条边指向的点的dist值(这个过程又被称为“松弛操作”)
4. 重复步骤 2和 3直到所有点都被标记
Dijkstra算法• 一个点的 dist值就是“已知的从起点到达它的最短路径长度”
• 如果记下每个点的 dist值最后一次更新是由哪条边的松弛操作完成的,这条边就是“已知的从起点到达它的最短路径的最后一条边”
• 一个点被标记也就意味着“这个点的 dist值已经不可能再被更新”,即“起点到它的最短路径已经求得”
Dijkstra算法
1
2
6 5
3
4
1
2
8
4
5
3
3
1
1
103
4
dist:0
dist:+∞
dist:+∞ dist:+∞
dist:+∞
dist:+∞
dist:1
dist:10
dist:3
dist:3
dist:5dist:2
dist:9
Dijkstra算法朴素的 Dijkstra算法的时间复杂度为 O(n^2)。
如果所有点的 dist值用一个堆来维护,“更新某个点的 dist值”和“寻找 dist值最小的点”就可以在 O(logn)的时间复杂度内完成。总时间复杂度就变成了 O(elogn),其中 e为图中的边数
实际上 Dijkstra算法就是 h函数为 0的 A*算法
Bellman-Ford算法
如果边权可以为负值,那么可以使用 Bellman-Ford算法求解单源最短路径问题。
Bellman-Ford算法算法流程:1. 初始化所有点的 dist值为 +∞,起点的 dist值为
0
2. 检查所有边 uv,用 u点的 dist值与这条边的权值之和更新 v点的 dist值。即对所有的边执行一次松弛操作。若此步骤中某个点的 dist值更新了,那么可以说此步骤是“有收获的”
3. 直到上一轮步骤 2没有收获,或步骤 2已经重复了 n轮,否则再重复一次步骤 2。若最后一轮步骤 2仍旧是有收获的,那么说明起点能够到达一个负权回路,否则目标就已经达成了。
Bellman-Ford算法证明:• 如果起点能够到达某个负权回路,那么显然,步骤 2总是会有收获。
• 如果起点无法到达一个负权回路,那么当所有点的 dist值到达最低值时,步骤 2就不再有收获了。
• 而一条最短路径一定没有环,即一条最短路径经过的边数一定不会超过 n-1(经过了所有其他的点),所以 n-1轮后,任何一个点作为终点的最短路径一定已经求得。
Bellman-Ford算法
时间复杂度显然是 O(ne)
SPFA算法
稍微改进一下 Bellman-Ford算法的过程,就得到 SPFA( Shortest Path Faster Algorithm)算法。
SPFA算法算法流程:1. 初始化所有点的 dist值为 +∞,起点的 di
st值为 0 “,将起点加入一个 待检查点”的队列 q
2. 从队列 q的队首中取出一个节点 x,对 x引出的所有边执行松弛操作,若某个点的dist值被更新了且这个点不在队列 q中,则将其加入队尾
3. 重复步骤 2直到某个点入队超过 n次或队列为空
SPFA算法
有理论能够证明, SPFA算法的平均时间复杂度为 O(e) 。但是有可能退化,最坏会到 O(en)。
比较适合于稀疏图中求解单源最短路径。
如果不用队列,改用栈可以么?
单源最短路径问题
算法 时间复杂度
Dijkstra O(n^2),或者用堆优化到 O(elogn)
Bellman-Ford O(en)
SPFAO(e),只是平均情况,最坏可能退化到 O(en),但一般来说效果很好
任意点对最短路径问题
给定一个有 n个点的带权有向图,问任意两个点之间的最短路径长度。
Floyd算法代码如下( w最初是图的邻接矩阵,算法结束后是任意点对间的最短路径长度):
for k=1 to n do
for i=1 to n do
for j=1 to n do
if (w[i,k]+w[k,j]<w[i,j])
w[i,j]=w[i,k]+w[k,j]
Floyd算法for k=1 to n do
// 此时的 w[x,y]为: x到 y的“中途只能// 经过 1..k-1这些点的”最短路径长度// 下面的二重循环则是求“中途只能// 经过 1..k这些点的”最短路径长度for i=1 to n do
for j=1 to n do if (w[i,k]+w[k,j]<w[i,j])
w[i,j]=w[i,k]+w[k,j]
在 w[i,j]被更新时记下其因哪个 k被更新,将之存储在mid[i,j]中,则mid[i,j]就是 i到 j的最短路径中经过的编号最大的点,由此可以还原出任意点对间的具体的最短路径。
Floyd算法
k
x y只经过 1..k-1
只经过
1..k-1
只经过1..k-1
Floyd算法如果有负权边但没有负权回路, Floyd算法还是正确的么?
答案是依然正确。但是 Floyd算法没法判断图中是否有负权回路存在。
时间复杂度显然是 O(n^3)的。
Floyd算法
如果图中有负权回路存在, Floyd算法最后得到的会是什么?
如果要求图(可能是无向图)的最小环, Floyd算法可以给你什么启示么?
最短路径问题
如果要求次短路径,甚至第 k短路径呢?
如果这个图存在负权回路,但要求每个点不走两次的最短路径呢?
无向图的最小生成树问题给定一个连通的带正权无向图,求此图的一个边集,要求:( 1)将不在此边集的边全删除后,图依然连通;( 2)边集中的边的权值之和最小。
易知这个边集一定构成一棵树,因此它又被称为“最小生成树”
无向图的最小生成树问题
1
2
5 4
3
2 3
1 4
2
3
4
17
3
无向图的最小生成树问题
• 将原图点集分成两个不相交的集合 A和 B
• 原图中所有连接了 A和 B的边中权值最小的那条,一定可以作为最小生成树中的一条边
无向图的最小生成树问题
1
2
5 4
3
2 3
1 4
2
3
4
17
3
Prim算法算法流程:1. 任选一个点 s得到集合 A={s},初始化 di
st[i]为 w[s,i], link[i]=s
2. 在 V-A中选取 dist最小的点 x,将 x加入集合 A,检查所有 V-A中的点 i,若 w[x,i]<dist[i]则 dist[i]=w[x,i]且令 link[i]=x
3. 重复步骤 2直到 A=V,此时所有 V-{s}中的点 i与 link[i] 相连的边就构成了原图的一个最小生成树,此外 dist[i]就是连接了 i与 link[i]的边的权值
Prim算法
1
2
5 4
3
2 3
1 4
2
3
4
17
3
Prim算法
时间复杂度是为O(n^2)的。
正确性依赖于之前说的原理。
虽然最小生成树本身是“无根树”,但 link数组实际上对应了一棵以 s为根的“有根树”,对于每个节点 i, link[i]就是其在这棵树中的父节点。
Kruskal算法算法流程:1. 将所有的边按权值从小到大排序,初始化
A[i]={i}对所有的 i, A[1..n]构成原图点集的一个划分
2. 依边权从小到大依次检查每条边,若这条边连接的两个点不在一个划分块中,则标记这条边,然后将这两个划分块合并
3. 重复步骤 2,直到所有点都在一个划分块中为止。最后所有标记的边构成原图的一个最小生成树
Kruskal算法
1
2
5 4
3
2 3
1 4
2
3
4
17
3
Kruskal算法
原图点集的划分通常使用并查集来维护,所以 Kruskal算法的时间复杂度就是 O(eloge)。
显然,如果图是稀疏图, Kruskal算法效率应该好于 Prim算法,而对于稠密图则相反。
无向图的最小生成树问题
如果图是动态变化的呢?比如图中的边会不断增加,或者不断减少。
如果要求的不是最小生成树,而是次小生成树,甚至第 k小生成树呢?
如果每条边有两个参数 c和 w,要求选出的边集的 c之和与 w之和的比值最小呢?这个又被称为“最优比率生成树”
有向图的最小生成树问题给定一个连通的带正权有向图,并指定其中的某个点 s,求此图的一个边集,要求:( 1)将不在此边集的边全删除后, s到其他所有点都有路径。( 2)边集中的边的权值之和最小。显然这个边集一定构成一棵以 s为根的有根树,且这棵树中的边都是父节点指向儿子节点的。这样的边集又被称为原图以 s为根的“最小树形图”。
有向图的最小生成树问题
1
2 4
3 5
35
5
4
2 6
3
7
有向图的最小生成树问题算法流程:1.对 s 外的每个点 i,找到指向 i的边中权值最小的那条,将这些边放入边集 A0
2.若图中没有环且没有收缩点,则 A0即为所求,算法结束;若有环则进入步骤 3 ;若没有环但有收缩点,则进入步骤 4
3.将每个环收缩为一个点,同时将这个环中的边从A0 剔除,然后回到步骤 1。收缩的具体操作如下:1. 假设收缩产生的新点为 P2.对于任意环外点 x指向环内点 y的权值为 w的边 xy,生成一条边 xP,并且权值改为 w 减去环中指向 y的边的权值,这条边实际上是原本的 xy的“等价边”
有向图的最小生成树问题
S
9 5
39 4
3738 5
9
48
46
S
9-7 5-33
9-48-3
9-5
4-36
4 8-5
收缩
有向图的最小生成树问题4. 此时的 A0(没环)对于现在的图(有收缩点)来说,就是一棵以 s为根的最小生成树了,但由于存在收缩点,所以现在的图还不是原图,所以需要把收缩点展开,以还原到原图。展开后可能依然存在收缩点,需要重复此步骤直到没有收缩点为止,那时的 A0即为所求1. 展开时需要将其在收缩时从 A0中剔除的边加回到 A0
2. 对每个展开后的收缩点,其中肯定有一个点有两条边指向它,其中一条是所在收缩点外的某点指向它的,另一条则是收缩点内的某点指向它的,将后者从 A0 剔除
有向图的最小生成树问题
S
9-7 5-33
9-48-3
9-5
4-36
48-5
S
9 5
39 4
3738 5
9
48
46
展开
有向图的最小生成树问题这个过程简而言之就是:• 找最小边• 收缩• 找最小边• 收缩• ……
• 展开• 展开• ……
练习题• POJ1502
• POJ3637
• POJ1511
• POJ1062
• POJ1860
• POJ1797
• POJ2253
• POJ1125
• POJ1258
• POJ2485
• POJ2075
• POJ3625
• POJ1679
• POJ3164