专题讲解--三种最短路

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 }

猜你喜欢

转载自www.cnblogs.com/very-beginning/p/12389623.html
今日推荐