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比较快)