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

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

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

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

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


一.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出发的边
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 }
朴素写法

猜你喜欢

转载自www.cnblogs.com/CrazyBoyM/p/9836061.html
今日推荐