Floyd
个人认为,Floyd算法是三种最短路算法中最简单、最好理解的算法。它的适用范围是任意两点之间的最短路。这一点是其他两种算法(单源最短路)无法比拟的。它的实现思路也很简单:用三重循环,枚举断点、起始点和终点(注意:顺序千万不能反!!),如果起始点到断点,断点到终点的距离和小于起始点到终点当前状态下的最短路(也就是说找到了一个比它还短的),那么就更新最短路。
它的优点就是简洁明了,易于理解,但是缺点也显而易见,通过它的实现途径,我们可以发现,使用Floyd算法的题一定要用邻接矩阵存图,这样的一个二维数组显然对空间有着要求,一般来讲,只能支持不超过500个点的图,假如更多,便无法支持。同时,Floyd算法还对时间有着要求,因为是三重循环,所以它的时间复杂度是O(n^3)的,这样的复杂度如果出现在一个复杂程序中,极其容易TLE。
1 void floyd() 2 { 3 memset(map,0x3f,sizeof(map)); 4 for(int i=1;i<=n;i++) 5 map[i][i]=0; 6 for(int k=1;k<=n;k++) 7 for(int i=1;i<=n;i++) 8 for(int j=1;j<=n;j++) 9 map[i][j]=min(map[i][j],map[i][k]+map[k][j]); 10 }
Dijkstra
Dijkstra算法,中文名是迪杰斯特拉算法,简写是DIJ算法。DIJ算法是求解单源最短路,即从某一个源点到达其他所有点的最短路的一个经典算法。它的实现途径也很好理解:我把点分成两个集合,一个是已经扫描过的集合,另一个是没有扫描的集合。即已经确定最短路和没确定最短路的两个集合,用v数组标记。然后我们从没有扫描的集合中挑出一个dist最小的点放入已经扫描的集合中,然后从这个点开始搜索它的所有出边,进行松弛操作即可。
DIJ算法的时间复杂度比较高,大约是O(n^2)级别的,同SPFA比确实是差了一些,但它有自己独特的优势,由于它的复杂度只和点有关,所以针对于稠密图(边多点少的图),它的求解效率要比SPFA算法高很多
1 const int INF=1e9; 2 void dijkstra(int s) 3 { 4 int temp,k,y; 5 memset(dist,0x3f,sizeof(dist)); 6 memset(v,0,sizeof(v)); 7 dist[s]=0; 8 for(int i=1;i<=n;i++) 9 { 10 temp=INF; 11 for(int j=1;j<=n;j++) 12 if(dist[j]<temp && !v[j]) 13 k=j,temp=dist[j]; 14 v[k]=1; 15 for(int j=head[i];j;j=nxt[j]) 16 { 17 y=to[i]; 18 if(dist[y]>dist[k]+val[j]) 19 dist[y]=dist[k]+val[j]; 20 } 21 } 22 }
堆优化Dijkstra
DIJ算法的时间复杂度是O(n^2)的,在一些题目中,这个复杂度显然不满足要求。所以我们需要继续探讨DIJ算法的优化方式。
堆优化的原理
堆优化,顾名思义,就是用堆进行优化。我们通过学习朴素DIJ算法,明白DIJ算法的实现需要从头到尾扫一遍点找出最小的点然后进行松弛。所以我们使用小根堆,用优先队列来维护这个“最小的点”。从而大大减少DIJ算法的时间复杂度。
堆优化的代码实现
首先,我们需要往优先队列中push最短路长度,但是它一旦入队,就会被优先队列自动维护离开原来的位置,换言之,我们无法再把它与它原来的点对应上,也就是说没有办法形成点的编号到点权的映射。
我们用pair解决这个问题。
pair是C++自带的二元组。我们可以把它理解成一个有两个元素的结构体。这个二元组有自带的排序方式:以第一关键字为关键字,再以第二关键字为关键字进行排序。所以,我们用二元组的first位存距离,second位存编号即可。
然后我们发现裸的优先队列其实是大根堆,我们如何让它变成小根堆呢?
有两种方法,第一种是把第一关键字取相反数,取出来的时候再取相反数。第二种是重新定义优先队列:
1 void dijkstra() 2 { 3 memset(dist,0x3f,sizeof(dist)); 4 dist[0]=0; 5 q.push(make_pair(0,0)); 6 while(!q.empty()) 7 { 8 int x=q.top().second; 9 if(v[x]) 10 { 11 q.pop(); 12 continue; 13 } 14 x=q.top().second;q.pop();v[x]=1; 15 for(int i=head[x];i;i=nxt[i]) 16 { 17 int y=to[i]; 18 if(dist[y]>dist[x]+val[i]) 19 dist[y]=dist[x]+val[i],q.push(make_pair(-dist[y],y)); 20 } 21 } 22 }
SPFA
SPFA的适用范围同样也是单源最短路,但是它的实现途径较抽象:新建一个队列,保存等待优化的节点,把源点加入队列。取出队首节点x,遍历x的所有出边,进行松弛操作,如果x点的到达点y被更新,且y不在当前队列中,就把y压入队尾。重复直到队列空为止。
SPFA的时间复杂度也很优越,根据证明,期望的时间复杂度可以达到O(kM),其中M为边数,k为所有顶点进队的平均次数。一般来讲,k<=2。
所以我们发现,SPFA算法适用于稀疏图,即点特别多边特别少的图。
1 void spfa(int s) 2 { 3 memset(dist,0x3f,sizeof(dist)); 4 memset(v,0,sizeof(v)); 5 queue<int> q; 6 q.push(s);v[s]=1;dist[s]=0; 7 while(!q.empty()) 8 { 9 int x=q.front(); 10 v[x]=0;q.pop(); 11 for(int i=head[x];i;i=nxt[i]) 12 { 13 int y=to[i]; 14 if(dist[y]>dist[x]+val[i]) 15 { 16 dist[y]=dist[x]+val[i]; 17 if(!v[y]) 18 v[y]=1,q.push(y); 19 } 20 } 21 } 22 }