众所周知,最短路算法在比赛中占有相当部分的分值
在大多数情况下,甚至使用并非最佳的算法也可以的得到相当大部分的分数。
以下选自书中核心内容,是竞赛生要熟练掌握且清晰理解的几种最基本算法。
(全部化为有向图做,双向边就化为两条单向边,恩,就这样操作)
以下所有讨论不考虑环,全部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出发的边
(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 }