Dijkstra,floyd,spfa三种最短路的区别和使用

这里不列举三种算法的实现细节,只是简单描述下思想,分析下异同

 

一 Dijkstra

Dijkstra算法可以解决无负权图的最短路径问题,只能应付单源起点的情况,算法要求两个集合,开始所有点在第二个集合,然后将起点加入第一个集合,接着第二个集合剩下的点哪个离起点距离最小,就加入第一个集合,并对其相关的边进行松弛,如此循环直到所有点都进入集合。每个点都加进集合需要循环n次,每个点进入集合又要对不在集合的点距离进行更新,内层又要循环n次。开始将map全部初始化为INF(一个很大的数),这样松弛的时候比较轻松

①复杂度:O(V^2 + E)

一般用邻接矩阵表示图,这种情况下是比较普遍的情况,因为要循环每个点,每个点又要找最小值,复杂度还是挺高的。

②邻接表+优先队列优化后复杂度:O((V + E)lgV)

使用了STL中的优先队列

 

 1 typedef pair<int,int> PII;
 2 priority_queue<PII,vector<PII>,greater<PII> > q;
 3 ...
 4 while(!q.empty()){  // O(V) 加上count<n可以优化一点点 
 5     int w=q.top().first, u=q.top().second;
 6     q.pop();   // O(lgV)
 7     if(b[u])continue; b[u]=true;
 8     //++count;
 9     for(int i=head[u];i;i=e[i].next){ // Sum -> O(E)
10         int v=e[i].to;
11         if(d[u]+e[i].w<d[v]){
12             d[v]=d[u]+e[i].w;
13             q.push(PII(d[v],v));  // O(lgV)
14         }
15     }
16 }

 

Dijkstra+heap是用小根堆,每次取出d最小的点,来更新距离,那么这个点来说,最小距离就是当前的d。

稠密图中,Dijkstra+heap优化比较快

 

③记录路径

记录路径是通过一个pre[]数组记录前驱的节点,初始化的时候需要注意,与s直接相连的点i要初始化为pre[i] = s,即使与s直接相连的边不一定是i的最短路径

 1 void dijkstra(int s, int e)  
 2 {  
 3     int Min, next;  
 4     for(int i = 1; i <= n; i++)  
 5     {  
 6         dist[i] = Map[s][i];  
 7         vis[i] = false;  
 8         pre[i] = dist[i]!=INF&&i!=s ? s : -1;//初始化要注意   
 9     }   
10     vis[s] = true;  
11     for(int i = 2; i <= n; i++)  
12     {  
13         Min = INF;  
14         for(int j = 1; j <= n; j++)  
15         {  
16             if(!vis[j] && dist[j] < Min)  
17             {  
18                 Min = dist[j];  
19                 next = j;  
20             }  
21         }   
22         vis[next] = true;  
23         for(int j = 1; j <= n; j++)  
24         {  
25             if(!vis[j] && dist[j] > dist[next] + Map[next][j])  
26             {  
27                 dist[j] = dist[next] + Map[next][j];  
28                 pre[j] = next;//记录   
29             }  
30         }  
31     }  
32 } 

 

 

二 Floyd

Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。

基本思路就是假设图有n个点到n个点的距离共n^2段距离(如果是完全图),又分别用n个点松弛它,所以为n^3

Floyd算法的好处是可以计算任意两点间的最短距离,若遇到需要对多个起点终点找最短路径的题目就适合使用Floyd算法。

需要学习的是Floyd记录路径的方法

path[i][j]记录的是i到j的最短路的下一个点,比如i--k--j是i到j的最短路,则path[i][j] = k; path[k][j] = j

需要注意的是path的初始化。

1   for(int i = 1; i <= n; i++)  
2         for(int j = 1; j <= n; j++)  
3             pre[i][j] = j;            //初始化   
1 for( int k = 1; k <= N; k++ )
2     for( int i = 1; i <= N; i++ )
3         for( int j = 1; j <= N; j++ )
4         {
5             //////处理最短路图map///////
6             pre[i][j] = pre[i][k];//记录    
7         }   

 

三 spfa(Shortest Path Faster Algorithm)

spfa是求单源最短路的一种算法,他是再Bellman-Ford算法的基础上加入了队列queue优化,spfa和Dijkstra很像,但spfa可以处理带负权边的图(但是不能有负权环,即围成环的各边权值加起来不能为负)

基本思路是建立一个队列,

①复杂度:??

证明比较复杂,因为每个点不一定只入队列一次,

网上流传的期望时间复杂度为O(me), 其中m为所有顶点进队的平均次数,"可以证明m一般小于等于2n:“算法编程后实际运算情况表明m一般没有超过2n.事实上顶点入队次数m是一个不容易事先分析出来的数,但它确是一个随图的不同而略有不同的常数.所谓常数,就是与e无关,与n也无关,仅与边的权值分布有关.一旦图确定,权值确定,原点确定,m就是一个确定的常数.所以SPFA算法复杂度为O(e).证毕."(SPFA的论文)不过,这个证明是非常不严谨甚至错误的,事实上在bellman算法的论文中已有这方面的内容,所以国际上一般不承认SPFA算法。

SPFA算法有两个优化策略SLF和LLL

SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾;

LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。

SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。

在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra

②记录路径

思路与Dijkstra相似,参考Dij的算法。

③(据说稀疏图spfa比较快)

 

posted @ 2017-11-23 16:15  LBNOQYX  阅读(1441)  评论(0编辑  收藏  举报