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