zoukankan      html  css  js  c++  java
  • [ACM算法]图的基本知识


    图的基本知识 (本文有我转载高人,红色为我个人理解或个人感觉需注意的地方)
      
    顶点:图中的数据元素称为顶点.
    有向图:有方向的图叫有向图.
    无向图:没有方向的图叫无线图.
    完全图:有n(n-1)/2条边的无向图称为完全图.
    有向完全图:具有n(n-1)条弧的有向图称为有向完全图.
    稀疏图:有很少条边或弧的图称为稀疏图,反之称为稠密图.
    权:与图的边或弧相关的数叫做权(weight).

    1. Relaxation(
    松弛操作): 
    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; 



    2. Dijkstra 
    1) 适用条件&范围: 
    a) 单源最短路径(从源点s到其它所有顶点v); 
    b) 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图) 
    c) 所有边权非负(任取(i,j)∈E都有Wij≥0); 
    2) 算法描述: 
    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的前驱节点 
    3) 算法优化: 
    使用二叉堆(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)。但因为它们编程复杂度太高,不推荐在信息学竞赛中使用。 


    3. Bellman-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. Topological 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) 


    5. SSSP On DAG 
    1) 适用条件&范围: 
    a) DAG(Directed Acyclic Graph,有向无环图); 
    b) 边权可正可负 
    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中实现,这样即减小了此算法复杂度的一个系数。 

    6. Floyd-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数组的更新,不再赘述。 

    7. Prim 
    (Dijksta的推广)
    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 
    b) 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和输出不一样……) 

    8 Kruskal 
    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的总权值 
    4) 分析总结: 
    检查2个顶点是否在同一连通分量可以使用并查集实现(连通分量看作等价类)。 
    我们可以看到,算法主要耗时在将边排序上。如果边已经按照权值顺序给出,那太棒了…… 
    另外一种可以想到的实现方法为:O(n)时间关于边权建二叉小根堆;每次挑选符合条件的边时使用堆的DelMin操作。这种方法比用Qsort预排序的方法稍微快一些,编程复杂度基本一样。附程序。 
    另外,如果边权有一定限制,即<=某常数c,则可以使用线性时间排序以获得更好的时间效率。


    SPFA算法 
    求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm。 
    从名字我们就可以看出,这种算法在效率上一定有过人之处。 
    很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。 
    简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。 
    我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存
    待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 

    定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。 
    证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕) 

    SPFA(G,w,s) 
    1. INITIALIZE-SINGLE-SOURCE(G,s) 
    2. INITIALIZE-QUEUE(Q) 
    3. ENQUEUE(Q,s) 
    4. While Not EMPTY(Q)  
    5.  Do u<-DLQUEUE(Q) 
    6.  For 每条边(u,v) in E[G] 
    7.  Do tmp<-d[v] 
    8.  Relax(u,v,w) 
    9.  If (d[v] < tmp) and (v不在Q中) 
    10.  ENQUEUE(Q,v)
     
  • 相关阅读:
    Http请求处理整个过程
    C#文件下载方法
    EF链接ORACLE
    js复制功能的有效方法总结新
    js复制功能的有效方法总结
    pre即可保持原来样式也可以换行
    读取url后参数方法
    同名窗口不能重新打开
    2020/06/19 mysql 表分组查询 表约束 主键 外键 外键约束
    2020/06/17 mysql 表内容的增删改查
  • 原文地址:https://www.cnblogs.com/10jschen/p/2639650.html
Copyright © 2011-2022 走看看