图论算法

来源:互联网 发布:php到底是做什么的 编辑:程序博客网 时间:2024/04/19 08:02
1Relaxation(松弛操作):
procedure relax(u,v,w:integer);//多数情况下不需要单独写成procedure。
begin
  if dis[u]+w<dis[v] then
    begin
      dis[v]:=dis[u]+w;
      pre[v]:=u;
    end
end;
2Dijkstra
适用条件&范围:
a)       单源最短路径(从源点s到其它所有顶点v);
b)       有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图)
c)       所有边权非负(任取(i,j)∈E都有Wij≥0);
1)      算法描述:
a)       初始化:dis[v]=maxint(v∈V,v≠s); dis[s]=0; pre[s]=s; S={s};
b)       For i:=1 to n
1.取V-S中的一顶点u使得dis[u]=min{dis[v]|v∈V-S}
2.S=S+{u}
3.For V-S中每个顶点v do Relax(u,v,Wu,v)
c)       算法结束:dis[i]为s到i的最短距离;pre[i]为i的前驱节点
2)      算法优化:
 使用二叉堆(Binary Heap)来实现每步的DeleteMin(ExtractMin,即算法步骤b中第1步)操作,算法复杂度从O(V^2)降到O((V+E)㏒V)。推荐对稀疏图使用。
 使用Fibonacci Heap(或其他Decrease操作O(1),DeleteMin操作O(logn)的数据结构)可以将复杂度降到O(E+V㏒V);如果边权值均为不大于C的正整数,则使用Radix Heap可以达到O(E+V㏒C)。但因为它们编程复杂度太高,不推荐在信息学竞赛中使用。
 
3Bellman-Ford
1)        适用条件&范围:
a)       单源最短路径(从源点s到其它所有顶点v);
b)       有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
c)       边权可正可负(如有负权回路输出错误提示);
d)       差分约束系统;
2)      算法描述:
对每条边进行|V|次Relax操作;
完成后,如果存在(u,v)∈E使得dis[u]+w<dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。
3)      算法实现:
For i:=1 to |V| do
For 每条边(u,v)∈E do
 
       Relax(u,v,w);
For每条边(u,v)∈E do
If dis[u]+w<dis[v] Then Exit(False)
               
算法时间复杂度O(VE)。因为算法简单,适用范围又广,虽然复杂度稍高,但因为它实践中的效果不错,仍不失为一个很实用的算法。
4)      菜鱼有话说:
这个……N久前有人提出个叫“SPFA”的东西,主要思想是:维护一队列,对队头的顶点u,枚举它的邻接边,如果它可以用来更新顶点v,则更新v的dis。更新后,如果顶点不在队中,则加入队。重复,直至队列空。有人说这个算法是O(kE),k平均为2。看完算法的描述,我直观上就感觉这是bellman-ford的一种实现方式,但无奈没有证据,在与Zroge和Amber的讨论中没有能说明。但是,偶尔翻起一本书《Data structures and Algorithm analysis》,终于确定所谓SPFA就是Bellman-Ford(如果以上算法描述的确是SPFA的描述的话)。如有怀疑可以参考此书,网上有CHM电子书。既然是Bellman-Ford,它的算法复杂度就是O(VE)。而实际上,对于这个所谓“SPFA”是可以很轻易构造出使它复杂度为VE的例子的(有负权回路即可)。
以上为我个人说法,有不同观点可以讨论。
 
4Topological Sort(拓扑排序)
1)        适用条件&范围:
a)       AOV网(Activity On Vertex Network);
b)       有向图;
c)       作为某些算法的预处理过程(如DP)
2)      算法描述:
很简单的算法:每次挑选入度为0的顶点输出(不计次序)。
如果最后发现输出的顶点数小于|V|,则表明有回路存在
3)        算法实现:
a)       数据结构: adj:邻接表;有4个域{u,v,w,next}
indgr[i]:顶点i的入度;
stack[]:栈
b)       初始化:top=0 (栈顶指针)
c)       将初始状态所有入度为0的顶点压栈
d)       I=0 (计数器)
e)       While 栈非空(top>0) do
                                                 i.              顶点v出栈;输出v;计数器增1;
                                              ii.              For 与v邻接的顶点u do
1.       dec(indgr[u]);
2.       If indgr[u]=0 then 顶点u入栈
f)       EXIT(I=|V|)
 
简单&高效&实用的算法。上述实现方法复杂度O(V+E)
 
 
5SSSP On DAG
1)        适用条件&范围:
a)       DAG(Directed Acyclic Graph,有向无环图);
 
边权可正可负
2)      算法描述:
a)       Toposort;
b)       If Toposort=False Then HALT(Not a DAG)
c)       For 拓扑序的每个顶点u do
For u的每个邻接点v do
  Relax(u,v,w);
 
     算法结束后:如有环则输出错误信息;否则dis[i]为s到i的最短距离,pre[i]为前驱顶点。
3)        算法实现:
基本从略。此算法时间复杂度O(V+E),时间&编程复杂度低,如遇到符合条件的题目(DAG),推荐使用。
还有,此算法的步骤c可以在toposort中实现,这样即减小了此算法复杂度的一个系数。
 
6Floyd-Warshall
1)        适用范围:
a)       APSP(All Pairs Shortest Paths)
b)       稠密图效果最佳
c)       边权可正可负
2)        算法描述:
a)       初始化:dis[u,v]=w[u,v]
b)       For k:=1 to n
For i:=1 to n
For j:=1 to n
If dis[i,j]>dis[i,k]+dis[k,j] Then
Dis[I,j]:=dis[I,k]+dis[k,j];
c)       算法结束:dis即为所有点对的最短路径矩阵
3)      算法小结:
此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法。时间复杂度O(n^3)。
考虑下列变形:如(I,j)∈E则dis[I,j]初始为1,else初始为0,这样的Floyd算法最后的最短路径矩阵即成为一个判断I,j是否有通路的矩阵。更简单的,我们可以把dis设成boolean类型,则每次可以用“dis[I,j]:=dis[I,j]or(dis[I,k]and dis[k,j])”来代替算法描述中的蓝色部分,可以更直观地得到I,j的连通情况。
Dijkstra算法类似地,算法中蓝色的部分可以加上对Pre数组的更新,不再赘述。
 
7Prim
1)        适用范围:
a)       MST(Minimum Spanning Tree,最小生成树)
b)       无向图(有向图的是最小树形图)
c)       多用于稠密图
2)        算法描述:
a)       初始化:dis[v]=maxint(v∈V,v≠s); dis[s]=0; pre[s]=s; S={s};tot=0
 
For i:=1 to n
1.取顶点v∈V-S使得W(u,v)=min{W(u,v)|u∈S,v∈V-S,(u,v)∈E}
2.S=S+{v};tot=tot+W(u,v);输出边(u,v)
3.For V-S中每个顶点v do Relax(u,v,Wu,v)
c)        算法结束:tot为MST的总权值
 
注意:这里的Relax不同于求最短路径时的松弛操作。它的代码如下:
procedure relax(u,v,w:integer);        //松弛操作
begin
  if w<dis[v] then
    begin
      pre[v]:=u;
      dis[v]:=w;
    end;
end;
            可以看到,虽然不同,却也十分相似。
3)      算法优化:
                        使用二叉堆(Binary Heap)来实现每步的DeleteMin(ExtractMin)操作
算法复杂度从O(V^2)降到O((V+E)㏒V)。推荐对稀疏图使用。
 使用Fibonacci Heap可以将复杂度降到O(E+V㏒V),但因为编程复杂度太高,不推荐在信息学竞赛中使用。
 (不要问我为什么和Dijkstra一样……观察我的prim和dijkstra程序,会发现基本上只有relax和输出不一样……)
 
 
8Kruskal
1)        适用范围:
a)       MST(Minimum Spanning Tree,最小生成树)
b)       无向图(有向图的是最小树形图)
c)       多用于稀疏图
d)       边已经按权值排好序给出
2)        算法描述:
基本思想:每次选不属于同一连通分量(保证无圈)且边权值最小的2个顶点,将边加入MST,并将所在的2个连通分量合并,直到只剩一个连通分量
3)        算法实现:
a)       将边按非降序排列(Quicksort,O(E㏒E))
b)       While 合并次数少于|V|-1
                                                 i.              取一条边(u,v)(因为已经排序,所以必为最小)
                                              ii.              If u,v不属于同一连通分量 then
1)       合并u,v所在的连通分量
2)       输出边(u,v)
3)       合并次数增1;tot=tot+W(u,v)
c)       算法结束:tot为MST的总权值
 
分析总结:
检查2个顶点是否在同一连通分量可以使用并查集实现(连通分量看作等价类)。
我们可以看到,算法主要耗时在将边排序上。如果边已经按照权值顺序给出,那太棒了……
另外一种可以想到的实现方法为:O(n)时间关于边权建二叉小根堆;每次挑选符合条件的边时使用堆的DelMin操作。这种方法比用Qsort预排序的方法稍微快一些,编程复杂度基本一样。附程序。
另外,如果边权有一定限制,即<=某常数c,则可以使用线性时间排序以获得更好的时间效率。 
原创粉丝点击