最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)

最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)

Dijkstra算法:

解决的问题:

    带权重的有向图上单源最短路径问题。且权重都为非负值。如果采用的实现方法合适,Dijkstra运行时间要低于Bellman-Ford算法。

思路:

    如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist[i]+matrix[i][j]},应用了贪心的思想。根据这种思路,直接给出Dijkstra算法的伪代码,他可用于计算正权图的单源最短路径,同时适用于无向图和有向图。

清除所有点的标号

设d[0]=0,其他d[i]=INF

循环n次

{

    在所有点的标号中,选出d值最小的结点x

    给结点x标记

    对于从x出发的所有边(x,y),更新d[y]=min{d[y],d[x]+w(x,y)}

}

除了求出最短路的长度外,使用Dijkstra算法也能很方便地打印出结点0到所有节点的最短路本身.

代码实现:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void  dijkstra( int  start) //从start点开始
{
     int  i,j,k;
     memset (vis,0, sizeof (vis)); //标记是否访问过
     for (i=1; i<=n; i++) //n为总点数
     {
         if (i==start)
             dis[i]=0;
         else
             dis[i]=INF;
     }
     for (i=1; i<=n; i++)
     {
         int  r;
         int  min=INF;
         for (j=1; j<=n; j++)
             if (!vis[j]&&dis[j]
             {
                 min=dis[j];
                 r=j;
             }
         vis[r]=1;
         for (k=1; k<=n; k++) //对所有从r出发的边进行松弛
             if (dis[k]<(dis[r]+g[r][k]))
                 dis[k]=dis[k];
             else
                 dis[k]=dis[r]+g[r][k];
     }
     return ;
}

 

  Floyd算法:

         负权重的边可以存在,但不能存在权重为负值的环路

         算法考虑的是一条最短路径上的中间结点。

              算法核心思想:  三圈for循环

                 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for  ( int  k = 0; k < graph.getNumVex(); k++) {
 
  
 
                      for  ( int  v = 0; v < graph.getNumVex(); v++) {
 
  
 
                             for  ( int  w = 0; w < graph.getNumVex(); w++) {
 
  
 
                                    if  (d[v][w] > d[v][k] + d[k][w]) {
 
  
 
                                           d[v][w] = d[v][k] + d[k][w];
 
  
 
                                           p[v][w] = p[v][k]; // p[v][w]是v--w最短路径上 v的下一顶点
 
 
                                    }
  
 
                             }
  
 
                      }
  
 
               }

  

 

第一层 k是作为中间顶点

第二层 v是作为起始顶点

第三层 w是作为终点顶点

内层核心代码

 

以v为起点,w为终点,再以k作为v和w之间的中间点,去判断d[v][ w]和d[v][k] + d[k][w]的大小关系,如果d[v][w] > d[v][k] + d[k][w],说明找到从v→w的更短路径了,此时更改d[v][w]的值为d[v][k] + d[k][w]。

 

p[v][w]的值也要相应改成p[v][k]的值,因为 p[v][k]的值是v→k最短路径上v的后继顶点,而v→w这段最短路径是连接在v→k这段路径后面的,所以令所当然p[v][w]也要指向p[v][k]。

 

注意:最外层的k循环,前面的n此循环的结果跟后面n+1次循环的错做过程是息息相关,

 

       三次循环完成后,各个顶点之间的最短路径权重会存储在d矩阵中:d[i][j]表示i→j的最短路径权重。

 

邻接矩阵算法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void  Floyd(MGraph g)
{
     int  A[MAXV][MAXV];
     int  path[MAXV][MAXV];
     int  i,j,k,n=g.n;
     for (i=0;i
        for (j=0;j
       {   
              A[i][j]=g.edges[i][j];
             path[i][j]=-1;
        }
     for (k=0;k
    {
          for (i=0;i
             for (j=0;j
                 if (A[i][j]>(A[i][k]+A[k][j]))
                {
                      A[i][j]=A[i][k]+A[k][j];
                      path[i][j]=k;
                 }
      }
}

 Bellman-Ford算法 

   解决的问题:

             一般情况下的单源最短路径问题,这里权重可以为负值。

Bellman-ford算法返回一个布尔值,一表明是否存在一个从源结点可以到达的权重为负的环路。如果存在这样一个环路,算法将告诉我们不存在解决方案,如果没有这种环路的存在算法将给出最短路径和他们的权重。

   Bellman-Ford算法的流程如下:
             给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;

    以下操作循环执行至多n-1次,n为顶点数:
    对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
    若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

    为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出     单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

    可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).

  Bellman-Ford算法可以大致分为三个部分
    第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
    第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
    第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
    d(v) > d (u) + w(u,v)
    则返回false,表示途中存在从源点可达的权为负的回路。
 
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include
#include
using  namespace  std;
#define MAX 0x3f3f3f3f
#define N 1010
int  nodenum, edgenum, original;  //点,边,起点
typedef  struct  Edge  //边
{
     int  u,v;
     int  cost;
} Edge;
Edge edge[N];
int  dis[N], pre[N];
bool  Bellman_Ford()
{
     for ( int  i = 1; i <= nodenum; ++i)
     {
         if (i==original)
             dis[i]=0;
         else
             dis[i]=MAX;
     }
     for ( int  i = 1; i <= nodenum - 1; ++i) //循环n-1次
         for ( int  j = 1; j <= edgenum; ++j) //遍历每条边
         {
             if (dis[edge[j].v] > dis[edge[j].u] + edge[j].cost)  //松弛(顺序一定不能反~)
             {
                 dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
                 printf ( "%d " ,dis[edge[j].v]);
                 pre[edge[j].v] = edge[j].u;
             }
             printf ( "%d " ,dis[edge[j].v]);
         }
     bool  flag = 1;  //判断是否含有负权回路
     for ( int  i = 1; i <= edgenum; ++i)
         if (dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
         {
             flag = 0;
             break ;
         }
     return  flag;
}
void  print_path( int  root)  //打印最短路的路径(反向)
{
     while (root != pre[root])  //前驱
     {
         printf ( "%d-->" , root);
         root = pre[root];
     }
     if (root == pre[root])
         printf ( "%d\n" , root);
}
int  main()
{
     scanf ( "%d%d%d" , &nodenum, &edgenum, &original);
     pre[original] = original;
     for ( int  i = 1; i <= edgenum; ++i)
     {
         scanf ( "%d%d%d" , &edge[i].u, &edge[i].v, &edge[i].cost);
     }
     if (Bellman_Ford())
         for ( int  i = 1; i <= nodenum; ++i)  //每个点最短路
         {
             printf ( "%d\n" , dis[i]);
             printf ( "Path:" );
             print_path(i);
         }
     else
         printf ( "have negative circle\n" );
     return  0;
}

spfa算法:

算法流程

算法大致流程是用一个队列来进行维护。初始时将源加入队列。每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。直到队列为空时算法结束。

这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法

SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单:

设Dist代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dist全部为+∞,只有Dist[S]=0,Fa全部为0。

维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。

每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于 Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环

SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右。

代码模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SPFA
void  Spfa()
{
     for  ( int  i(0); i //初始化
     {
         dis[i] = MAX;
         visited[i] =  false ;   
     }
     queue< int > Q;
     dis[start] = 0;
     visited[start] =  true ;
     Q.push(start);
     while  (!Q.empty()){
         int  temp = Q.front();
         Q.pop();
         for  ( int  i(0); i
         {
             if  (dis[temp] + road[temp][i] < dis[i]) //存在负权的话,就需要创建一个COUNT数组,当某点的入队次数超过V(顶点数)返回。
             {
                 dis[i] = dis[temp] + road[temp][i];
                 if  (!visited[i])
                 {
                     Q.push(i);
                     visited[i] =  true ;   
                 }       
             }           
         }
         visited[temp] =  false ;           
     }   
}

  

东拼西凑这四种最短路算法,SPFA到现在还不会,菜鸟就是菜鸟,眼看暑假集训已经过去十来天了,总在抱怨啥都不会,十来天学到了啥呢,

时间会很快的,你要走的路还很长,最近一直在啃书,题也不写,代码都不会敲了,残废...                


                                          

你可能感兴趣的:(知识丶)