最短路算法总结---单源最短路径(SSSP)


众所周知,最短路算法在比赛中占有相当部分的分值

在大多数情况下,甚至使用并非最佳的算法也可以的得到相当大部分的分数。

以下选自书中核心内容,是竞赛生要熟练掌握且清晰理解的几种最基本算法。

(全部化为有向图做,双向边就化为两条单向边,恩,就这样操作)

以下所有讨论不考虑环,全部INF处理,请悉知。


 

 

一.Dijkstra算法(贪心)(O(n^2))(效率一般,但相当可做)(边权非负,否则。。。qwq)

1.dist[1]=0 , 其余 dist = INF

->2.找出一个未标记,dist[x]最小的节点x,标记x。

->3.扫描节点x的所有出边(x,y,z),if (dist[y]>dist[x]+z ) dist[y]=dist[x]+z (这是这个最基本的算法的核心语句,具体可想象三角形三边关系)。

(注意这里边权为负数的话,那么我们2中最先选择出的起点就不一定最小了,那万一以后跑出来个负边,全局都会受影响,那咱还贪个啥心,还跑个啥Dijkstra)

4.重复2-3直到全部点都被标记(233要是所有点走的线路都试过了,没有答案你来打我啊)

 

二.Bellman-Ford算法(O(nm))(我不咋用,但这个是SPFA的原型)

->1.扫描所有边(x,y,z),if (dist[y]>dist[x]+z)dist[y]=dist[x]+z (我没看错吧?我把上文抄了下来?没错,几种算法的基本套路是一样的)

(但要注意,这里不同的是这里Bell-ford的2并非像Dijkstra那样要求对所有点扫描,而仅仅是某一部分。)

(为神魔呢?这是样做有道理的。证明:若一张有向图的一条边满足三角形不等式,即dist[y]<=dist[x]+z,那么所有这样的边连起来的一条路肯定最短啦,干嘛还要把所有边都跑一边呢,这样就剪掉很多不必要去扫的边)

2.重复,直到1那家伙扫完 (即基于三角不等式的关系下不再发生任何更新)。

那么,就这样愉快而简单地结束啦233。(松弛,向最优)

 

注意到这里的不同:Dijkstra侧重基于点的扫描(直到全部点标记完),而Bell-ford侧重基于边的关系(直到不再发生更新),这也是两者设计的不同。(粗鄙见解)

但是这就结束了吗?ccf的样例可不会山吧干修(误),我们来把Bellman-Ford优化得到一个更低时间复杂度的版本。

怎么优化呢,加一个队列吧,让这个队列只有待扩展的点,每次都是满足三角不等式才入队,避免了Bellman-ford去扫那些不需要的边。(冗余扫描)

这样在稀疏的有向图上会更快,只在密图或特殊图上退化为朴素的Bellmand-ford。

 

三.SPFA算法(O(km))

1.建立一个队列,最初只含起点。

->2.取出队头x,扫描其几个出边(x,y,z),If (dist[y]>dist[x]+z ) dist[y]=dist[x]+z.把y入队(已在队中就不用了)(其实dist一直在记录起点到该点的距离,而我们的更新,就是在走不同路径时记录下该点到起点更近的距离)

3.重复2直到队列空。

(怎么样?基于队列操作的Bellman-ford是不是一下子省去很多不用扫的内容啊)


 

好了,到这里关于单源最短路(SSSP)的算法都复习完了。

相信是个像我这种笨蛋也思路清晰了。

那么,我们来敲下熟悉到吐了的板子吧233

敲板子是一件愉快的事(因为不用动脑子啊,理解题意后胡乱选个合适的算法,敲敲板子,再针对题中具体情况小小地改动一下,做做特殊处理,然后这种语文题就交给板子去跑啦qwq。大多数情况下还是可以直接拿到不错的部分分数,相信再调调细节聪明的你就可以ac啦)。

 来自lrd书上的几个板子

 

 

熟悉一下邻接表  空间复杂度O(n+m)

ver(V---点)记录每条边的终点,edge(E---边)记录对应每条边的边权。

head记录从每个节点出发第一条边在ver和edge数组中储存的位置。  

next是下一条边在ver和edge中的位置。

 1 void add(int x,int y,int z){
 2 ver[++tot]=y,edge[tot]=z;
 3 next[tot]=head[x],head[x]=tot;
 4 }
 5 //加边
 6 
 7 for(int i=head[x];i;i=next[i]){
 8 int y=ver[i],z=edge[i];
 9 。。。。。。
10 }
11 //访问从x出发的边
View Code

(LXL不对该段代码负责qwq)

 

Dijkstra板子(用于稠密图更快)

 1 int a[3010][3010],d[3010],n,m;
 2 bool v[3010];
 3 
 4 void dijkstra(){
 5 memset(d,0x3f,sizeof(d));//dist数组
 6 memset(v,o,sizeof(v));//节点标记
 7 d[1]=0;//漏了这句可是跑不了的哦。
 8 for(int i=1;i<n;i++){
 9 int x=0;//
10 for(int j=1;j<=n;j++) if(!v[j]&&(x==0||d[j]<d[x])) x=j;//找剩余未扫描点中更近的点
11 v[x]=1;//
12 for(int y=1;y<=n;y++) d[y]=min(d[y],d[x]+a[x][y]);//有没有找回动态规划的感觉,没错,是初恋的感觉。
13 //当然,这里只是更新下每个 <点到源> 的最小距离。
14 }
15 //重复n-1次扫描全部节点
16 }
17 
18 int main(){
19 cin>>n>>m;
20 memset(a,0x3f,sizeof(a));
21 for(int i=1;i<=n;i++) a[i][i]=0;
22 for(int i=1;i<=m;i++){
23 int x,y,z;
24 scanf("%d%d%d",&x,&y,&z);
25 a[x][y]=min(a[x][y],z);
26 //在建图时已经开始预处理下
27 }
28 dijkstra();
29 for(int i=1;i<=n;i++)
30 printf("%d\n",d[i]);//记录了所有最终态的距离,每个点到源的最短距。
31 return 0;
32 }
朴素写法

 

 1 const int N=100010,M=1000010;
 2 int head[N],ver[N],edge[M],Next[M],d[N];
 3 bool v[N];
 4 int n,m,tot;
 5 priority_queue<pair<int,int>> q;
 6 void add(int x,int y,int z){
 7    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
 8 }
 9 void dijkstra();
10 int main(){
11    cin>>n>>m;
12    for(int i=1;i<=m;i++){
13    int x,y,z;
14    scanf("%d%d%d",&x,&y,&z);
15    add(x,y,z);
16    }
17    dijkstra();
18    for(int i=1;i<=n;i++){
19       printf("%d\n",d[i]);
20    }
21 return 0;
22 }
23 void dijkstra(){
24    memset(d,0x3f,sizeof(d));
25    memset(v,0,sizeof(v));
26    d[1]=0;
27    q.push(make_pair(0,1));
28    while(q.size()){
29       int x=q.top().second;q.pop();
30       if(v[x]) continue;
31       v[x]=1;
32       //注意这里的运行原理是找到当前的最短距离(贪心),继续更新
33       for(int i=head[x];i;i=Next[i]){
34         int y=ver[i],z=edge[i];
35         if(d[y]>d[x]+z) {
36             d[y]=d[x]+z;
37             q.push(make_pair(-d[y],y));
38         }
39       } //整段在当前全局最小值的基础上去更新其他d值
40    }//原理是用二叉堆来节省find全局最小值的时间。
41 
42 }
堆优化写法(推荐掌握)

上面注释写了不少废话(甚至描述有偏差),请直接无视。

SPFA板子(用于稀疏图更快)

 1 const int N=100010,M=1000010;
 2 int head[N],ver[M],edge[M],Next[M],d[N];
 3 int n,m,tot;
 4 queue<int>q;
 5 bool v[N];
 6 void add(int x,int y,int z){ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;}
 7 void spfa(){
 8  memset(d,0x3f,sizeof(d));
 9  memset(v,0,sizeof(v));
10  d[1]=0;v[1]=1;
11  q.push(1);
12  while(q.size()){
13    int x=q.front();q.pop();
14    v[x]=0;
15    for(int i=head[x];i;i=Next[i]){
16       int y=ver[i],z=edge[i];
17       if(d[y]>d[x]+z){
18          d[y]=d[x]+z;
19          if(!v[y]) q.push(y),v[y]=1;
20       }
21     }
22   } 
23 }
24 int main(){
25  cin>>n>>m;
26  for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);}
27  spfa();
28  for(int i=1;i<=n;i++) printf("%d\n",d[i]);
29  return 0;
30 }
SPFA

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM