笔试算法题(49):简介

图最短路径算法(Graph Shortest Path Algorithm, eg: Floyd-Warshall, Dijkstra, Bellman-Ford, SPFA, Kruskal, Prim, Johnson)

  • 最短路径问题有多个衍生问题(并且每个衍生问题都涉及是否有负权边)

    单源点最短路径

    单终点最短路径

    单对顶点最短路径

    任意顶点间最短路径

Floyd-Warshall Algorithm

  • 适用于多源,可有负权边的有向图的最短路径;时间复杂度为O(V^3),空间复杂度为O(V^2);

  • 二维数组path[i][j]表示顶点i到顶点j之间的代价(初始化时由于没有探测他们之间的代价关系,所以为无限大);本算法采取的策略是穷举所有顶点对之间所有可能的中间顶点,并选择代价最小的作为最优解;

 1 procedure FloydWarshallWithPathReconstruction ()
 2  for k := 1 to n
 3    for i := 1 to n
 4      for j := 1 to n
 5        if path[i][k] + path[k][j] < path[i][j] then
 6           path[i][j] := path[i][k]+path[k][j];
 7           next[i][j] := k; //next[i][j]表示顶点i和顶点j的中间点
 8 
 9  procedure GetPath (i,j)
10  if path[i][j] equals infinity then
11     return "no path";
12  int intermediate := next[i][j];
13  if intermediate equals 'null' then
14     return " "; /* there is an edge from i to j, with no vertices between */
15  else
16     return GetPath(i,intermediate) + intermediate + GetPath(intermediate,j);

Dijkstra Algorithm

  • 适用于有向、无负权边图中,单个源点到其他所有顶点的最短路径问题(Single-Source Shortest Path Problem for Graph with Non-Negative Edge Path Costs)。给定一个带权重的有向图G,V表示所有点的集合,E表示所有边的集合,并且每条边具有权重值w,要求找到给定start点到V中所有其他点 的最短距离。在寻路过程中,如果发现一个新的顶点M,并且从源点到这一个顶点M再到前一个目标顶点的距离比之前的距离小,则更新这个目标定点的最小距离;

  • Dijkstra算法适用于稠密图(边多点少),时间复杂度:使用最小优先队列实现Extract_Min()函数的话,为O(V^2 + E);使用二叉堆实现Extract_Min()函数的话,为O(V^2);使用斐波那契堆(Fibonacci Heap)实现Extract_Min()函数的话,为O(V*lgV + E);Dijkstra算法的一个应用是OSPF(Open Shortest Path First,开放最短路径优先),网络路由寻址的实现

 1 function Dijkstra(G, w, s)
 2  for each vertex v in V[G] //初始化
 3     d[v] := infinity //d[v]存储其他顶点到起始点s的最短距离
 4     previous[v] := undefined //previous[v]存储所有顶点的前置节点
 5  d[s] := 0
 6  S := empty set
 7  Q := set of all vertices
 8  while Q is not an empty set // Dijkstra演算法主體
 9     u := Extract_Min(Q) //Extract_Min()一般使用最小堆实现O(logN)
10     S := S union {u}
11     for each edge (u,v) outgoing from u
12        if d[v] > d[u] + w(u,v) //根据当前节点u的d[u]更新其相邻节点的d[v]
13           d[v] := d[u] + w(u,v)
14           previous[v] := u

Bellman-Ford Algorithm

  • 适用于单源、可有负权边的有向图的最短路径,Bellman-Ford对每个顶点都只进行一次处理,所以可以处理负权边,而Dijkstra由于是根据边权值大小选择下一条边,所以负权边可能造成循环;此算法时间复杂度为O(VE),空间复杂度为O(V);

  • 数组V[k]表示所有顶点到source的最短距离(初始化为Infinite),并且使用数组P[h]记录所有顶点的前驱,用于记录最短路径;遍历所有 的边集合E内的边e,e连接顶点u和v,判断u和v之间是否可以组成最短路径,这样的遍历重复|V|次;由于V[k]中元素初始化为Infinite,所 以如果如果某个环内存在负权值边,则算法失败;

 1 procedure BellmanFord(list vertices, list edges, vertex source)
 2    // This implementation takes in a graph, represented as lists of vertices
 3    // and edges, and modifies the vertices so that their distance and
 4    // predecessor attributes store the shortest paths.
 5 
 6    // Step 1: initialize graph
 7    for each vertex v in vertices:
 8        if v is source then v.distance := 0
 9        else v.distance := infinity
10        v.predecessor := null
11 
12    // Step 2: relax edges repeatedly
13    for i from 1 to size(vertices)-1:
14        for each edge uv in edges: // uv is the edge from u to v
15            u := uv.source
16            v := uv.destination
17            if u.distance + uv.weight < v.distance:
18                v.distance := u.distance + uv.weight
19                v.predecessor := u
20 
21    // Step 3: check for negative-weight cycles
22    for each edge uv in edges:
23        u := uv.source
24        v := uv.destination
25        if u.distance + uv.weight < v.distance:
26            error "Graph contains a negative-weight cycle"

SPFA (Shortest Path Faster Algorithm)

  • 适用于单源、可有负权边的有向图;大多数时候当图存在负权边,Dijkstra不能使用,而Bellman-Ford时间复杂度过高,所以最好使用 SPFA;其实SPFA是Bellman-Ford的优化版本,时间复杂度为O(kE),K是一个远小于V的数字,表示所有顶点进入FQ队列的平均次数 (<=2),但是SPFA及其不稳定,并严重依赖于图数据;

  • SPFA的策略是只有那些在前一次的Relax中的最小距离减小的点,才检查他们的邻接节点的最小距离是否也可减小。SPFA并不会在每一个顶点的最短路 径更新之后就去更新与其相连的顶点,而是等所有顶点都处理完全之后再进行最短路径的更新,这样大大减少重复更改最短路径的次数;所以SFPA中顶点会多次 进入队列,SPFA适合用于稀疏图,此时其拥有最高的效率;

  • 数组D[k]存储所有其他顶点到Source的最短距离(初始化为Infinite);数组G[i][j]存储图中直接相连的顶点之间的距离;FIFO队 列Q存储需要进一步优化的顶点,每次从Q中取出顶点u,对每一个u直接相连的顶点v进行Relax操作,如果顶点v的最短距离改变了,并检查v没有在Q 中,则将v加入Q中,之后处理下一个与u直接相连的顶点;检查完u所有直接相连的顶点之后从Q中取出下一个顶点,并进行相同的处理;当Q为空的时候算法结 束,次数D[k]存储的就是图中每一个顶点到source的最短距离;

  • 根据将顶点插入到FIFO队列Q的位置不同,SPFA可有两种优化:Small Label First (SLF)和Large Label Last (LLL);但由于SPFA及其不稳定,所以一般情况都使用Dijkstra替代;

 1 void spfa(int start){
 2   int i,j;
 3   //初始化部分
 4   for (i=1;i<=n;++i){
 5     dist[i]=2147483647;
 6     inqueue[i]=0;
 7   }
 8   //将头节点入队
 9   dist[start]=0;
10   int h=0,t=1;
11   inqueue[start]=1;
12   queue[1]=start;
13   int now;
14   do{
15     h++;
16     now=Connect[queue[h];
17     inqueue[queue[h]=0;
18     while (now){
19       if (dist[Data[now].v]>dist[queue[h]+Data[now].w){
20         dist[Data[now].v]=dist[queue[h]+Data[now].w;
21         //进行松弛并扩展被松弛的点
22         if (!inqueue[Data[now].v]){
23           inqueue[Data[now].v]=1;
24           queue[++t]=Data[now].v;
25         }
26       }
27       now=Pre[now];
28     }
29   }while (h<t);
30  }

Johnson Algorithm

  • 为了让Dijkstra适用于具有负权值边的图,Johnson通过特殊方式将负权值图转换为正权值图,并且为所有顶点之间的最短路径;
  • 首先进行一次Bellman-Ford,然后利用等式W(i,j)=h[i]-h[j]+w(i,j);对原图进行重新标号(Re-wrighting, 其实是去除负权值边从而可以使用Dijkstra算法,h[]就是通过Bellman-Ford得到的路径标记,w[][]是边的原始权值);最后对每个 点使用Dijkstra。Johnson是目前在无负权值图中对所有点求最短路径的最高效的算法;
原文地址:https://www.cnblogs.com/leo-chen-2014/p/3756559.html