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