🐾算法理解没有用,会思路,会敲代码才OK🐾
文章目录
0.知识结构图
每种情况下,都有一个最合适的算法
n 表示顶点的个数,m表示边的个数
单源最短路:1号点到所有点的最短路;多元最短路:任意两个点之间的最短路
稠密图和稀疏图:m是 1 0 5 10^5 105 级别的话就是稠密图,m是n级别的就是稀疏图
-
⭐️难点
出题的重难点在于建图 -> 把题目抽象成最短图问题,重点学习算法的实现,原理了解即可(比赛是不会考原理),做到会应用,会做题。无向图没有专门的算法,无向图即两个有向边,这里讲有向图。
-
⭐️牢记时间复杂度
注意时间复杂度,看一下数据范围,可以给我们很多提示
-
⭐️学习
算法理解没有用,会思路,会敲代码;背模板,压短写算法的时间压短,你的代码水平就会有很高的提升。
-
⭐️调代码技巧:printf大法(出现错误答案时);删代码(出现段错误时)
1.朴素Dijkstra算法
1.1思想
集合S为已经确定最短路径的点集。
- 初始化距离
一号结点的距离为零,其他结点的距离设为无穷大(看具体的题)。- 循环n次,每一次将集合S之外距离最短X的点加入到S中去(这里的距离最短指的是距离1号点最近。
点X的路径一定最短)。然后用点X更新X邻接点的距离。- 两层循环,时间复杂度为 O($n^2 $)。
s[] ; //表示当前已确定的最短距离
//1.初始化距离
dis[1]=0,dis[i]=inf; //dis表示到起点的最短距离,inf表示正无穷
//2.迭代过程,就是用个循环
for(int i=0;i<n;i++) //每次确定一个点到起点的最短路
t<- //找不在s中的,距离最近的点t
s<-t //把t放进去,即确定好t了
dis[x]>dis(t)+w[x] //用 t 更新其他点的距离
1.2图示
略微模糊,但是思路清晰
求 a 到 b 的最短路
从编号 1 开始 ,找起点到每个编号的最短路
红点表示已经确定最短路的顶点,
蓝色表示可以走到,但不能确定是最短路
绿色的线表示,更新当前顶点的邻接点的距离
1.3模板题
-
⭐️题目
链接: 849. Dijkstra求最短路 I - AcWing题库
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500,
1≤m≤ 1 0 5 10^5 105 ,
图中涉及边长均不超过10000。输入样例:
3 3 1 2 2 2 3 1 1 3 4
输出样例:
3
-
⭐️题解
#include<bits/stdc++.h> using namespace std; const int N=510; int g[N][N]; //用一个邻接矩阵来存储稠密图 int dist[N]; //表示每个点到起点的最短路 bool st[N]; //true表示已经确定最短路,属于s集合 int n,m; int dijkstra() { //给距离初始化 memset(dist,0x3f,sizeof dist); //每个顶点到起点的距离是无限大 dist[1]=0; //起点到起点的距离是 0 //迭代即循环过程 for(int i=1;i<=n;i++) //遍历每一个顶点每次可以确定一个点到起点的最短路 { int t=-1; //t 来存储当前访问这个点 for(int j=1;j<=n;j++) if(!st[j]&&(t==-1||dist[t]>dist[j])) //j到起点的最短路还没有确定并且t没有被更新过,或者找到比t 还短的距离 t=j; st[t]=true; //已经确定当前 点 t 的最短路 for(int j=1;j<=n;j++) //利用距离最小的点,去更新其他点到其他点到起点的距离 { dist[j]=min(dist[j],dist[t]+g[t][j]); } } if(dist[n]==0x3f3f3f3f) return -1; //如果起点到达不了n号节点,则返回-1 return dist[n]; //否则,直接返回最短路的值 } int main() { scanf("%d%d",&n,&m); memset(g,0x3f,sizeof g); //初始化,所有的边,无限大 while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); //存边,邻接矩阵,因为是稠密图 g[a][b]=min(g[a][b],c); //可能会出现重边的情况,存下最小的边 } printf("%d",dijkstra()); return 0; }
2.堆优化版的Dijkstra算法
2.1思想
-
⭐️思路
堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离
最短的点O( 1 0 5 10^5 105 )可以使用最小堆优化。- 一号点的距离初始化为零,其他点初始化成无穷大。
- 将一号点放入堆中。
- 不断循环,直到堆空。每一次循环中执行的操作为:
弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
用该点更新临界点的距离,若更新成功就加入到堆中。
-
⭐️堆的实现
方式 手写堆 优先队列(STL) 区别 可以保证n个数,支持修改堆中任意元素,使用映射 不支持修改,每次修改需要新加一个数,有m个数,好写方便 -
⭐️稠密图与稀疏图
- 连线很多的时候,对应的就是稠密图,显然易见,稠密图的路径太多了,所以就用点来找,也就是抓重点;
- 点很多,但是连线不是很多的时候,对应的就是稀疏图,稀疏图的路径不多,所以按照连接路径找最短路,这个过程运用优先队列,能确保每一次查找保留到更新到队列里的都是最小的,同时还解决了两个点多条路选择最短路的问题。
2.2模板题
-
⭐️题目
链接: 850. Dijkstra求最短路 II - AcWing题库
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n,m≤1.5× 1 0 5 10^5 105 ,
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 1 0 9 10^9 109 。输入样例:
3 3 1 2 2 2 3 1 1 3 4
输出样例:
3
-
⭐️题解
#include<bits/stdc++.h> using namespace std; typedef pair<int,int> PII; const int N=1.5*1e5+10; int n,m; int h[N],w[N],ne[N],e[N],idx; //稀疏图用邻接表来存 int dist[N]; //表示该点到起点的距离 bool st[N]; //表示该点的到起点的最短距离有没有被确定 void add(int a,int b,int c) //存边和边权值 { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; //记住这个顺序,不可以变!! } int dijkstra() { memset(dist,0x3f,sizeof dist); //初始化各点到起点的最短距离 dist[1]=0; priority_queue<PII,vector<PII>,greater<PII>> heap; //定义根小堆,内部根据距离排序,保证堆顶是距离最小的顶点;这个顺序不能倒,pair排序时是先根据first,再根据second, heap.push({0,1}); //把起点放进去 while(heap.size()) //队列不空 { auto t=heap.top(); //取出队列中最短路径最小的点 heap.pop(); int ver=t.second,distance=t.first; //简写这个 当前的队列元素 if(st[ver]) continue; //当前的元素 最短已经被确定,跳过这个点,找下一个 st[ver]=true; //改变他的状态,表示当前的顶点最短距离已经被确定 for(int i=h[ver];i!=-1;i=ne[i]) //遍历 当前顶点的相邻顶点 { int j=e[i]; //j 当前相邻顶点的编号,i只是一个下标而不是编号 if(dist[j]>distance+w[i]) //更新 相邻顶点到起点的最短距离 { dist[j]=distance+w[i]; heap.push({dist[j],j}); //距离变小则入队 } } } if(dist[n]==0x3f3f3f3f) return -1; //如果n到起点的距离是正无穷,说明n与起点不通 return dist[n]; //否在,返回n到起点的最短路 } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); //初始化邻接表 while(m--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); //加边 add(a,b,c); } printf("%d",dijkstra()); return 0; }
3.Bellman-Ford算法
3.1思想
-
⭐️为什么 Dijkstra 算法不能解决负权边?
-
dijkstra要求每个点被确定后st[j] = true,dist[j]就是最短距离了,之后就不能再被更新了(一锤子买卖),而如果有负权边的话,那已经确定的点的dist[j]不一定是最短了。
-
dijstra算法基于贪心思想,当有负权边时,局部最优不一定是全局最优
-
-
⭐️什么是Bellman-Ford算法?
- Bellman - ford 算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1 次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
- 通俗的来讲就是:假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新。
-
⭐️步骤
for n次 //表示更新n条边,还要记得备注 for 所有边 a,b,w (松弛操作) dist[b] = min(dist[b],back[a] + w) //注意:back[] 数组是上一次迭代后 dist[] 数组的备份,由于是每个点同时向外出发,因此需要对 dist[] 数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点
-
⭐️是否能到达n号点的判断中需要进行if(dist[n] > INF/2)判断
-
在下面代码中,是否能到达n号点的判断中需要进行if(dist[n] > INF/2)判断,而并非是if(dist[n] == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,dist[n]大于某个与INF相同数量级的数即可
-
比如5号节点距离起点的距离是无穷大,利用5号节点更新n号节点距离起点的距离,将得到 1 0 5 10^5 105 −2,它 虽然小于 1 0 9 10^9 109 , 但并不存在最短路,(在边数限制在k条的条件下)。
-
-
⭐️只要题中没有负环就可以用这个算法
有负权回路,是没有最短路的,负权回路的最小路就是负无穷
-
⭐️但是如果有边数限制的话,求最短路有负权回路也就无所谓了,也只能使用Bellman-Ford算法
举个例子:比如乘客从某个城市到另一个城市,之间可以进行周转,但是没周转一次,乘客的心情就会变坏一次,所以限制周转的次数为k次,这样就限制了边的次数
3.2模板题
-
⭐️题目
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 11 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出
impossible
。注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
点的编号为 1∼n。
输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。
如果不存在满足条件的路径,则输出
impossible
。数据范围
1≤n,k≤500,
1≤m≤10000,
1≤x,y≤n,
任意边长的绝对值不超过 10000。输入样例:
3 3 1 1 2 1 2 3 1 1 3 3
输出样例:
3
-
⭐️题解
#include<bits/stdc++.h> using namespace std; const int N=510,M=10010; int n,m,k; int dist[N],backup[N]; //dist表示该点到起点的最短路,backup用来备份,具体看函数内部实现 struct Edge{ int a,b,w; }edges[M]; //a到b的距离为 w int bellman_ford() { memset(dist,0x3f,sizeof dist); //初始化 每个顶点到起点的最短路 dist[1]=0; for(int i=0;i<k;i++) //遍历 k 条边 { memcpy(backup,dist,sizeof dist); //备份 for(int j=0;j<m;j++) //遍历所有遍,找最短路 { int a=edges[j].a,b=edges[j].b,w=edges[j].w; //松弛,更新边最短路 dist[b]=min(dist[b],backup[a]+w); } } if(dist[n]>0x3f3f3f3f/2) return 0x3f3f3f3f/2+1; //n到起点的距离是无穷,表示n到起点不通 return dist[n]; //否则返回 n到起点的最短路 } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=0;i<m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); edges[i]={a,b,w}; //加边 } int t=bellman_ford(); if(t>0x3f3f3f3f/2) puts("impossible"); else printf("%d\n",t); return 0; }
4.SPFA算法
4.1思想
-
⭐️分析
SPFA算法仅仅只是对该Bellman-ford算法的一个优化。我觉得吧,区别就是Bellman-Ford没有用邻接表,而SPFA用了邻接表
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
-
⭐️注意
-
st数组的作用:判断当前的点是否已经加入到队列当中了;已经加入队列的结点就不需要反复的把该点加入到队列中了,就算此次还是会更新到源点的距离,那只用更新一下数值而不用加入到队列当中。
即便不使用st数组最终也没有什么关系,但是使用的好处在于可以提升效率。 -
SPFA算法看上去和 Dijstra 算法长得有一些像但是其中的意义还是相差甚远的:
-
Dijkstra算法中的st数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了(不可标记为true后改变为false);
SPFA算法中的st数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。
-
Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;
SPFA算法中使用的是队列(你也可以使用别的数据结构),目的只是记录一下当前发生过更新的点。
-
-
Bellman-ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f;
其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。
-
Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。
-
由于SPFA算法是由Bellman_ford算法优化而来,在最坏的情况下时间复杂度和它一样即时间复杂度为 O(nm),假如题目时间允许可以直接用SPFA算法去解Dijkstra算法的题目。
-
-
⭐️限制
没有负环就可以使用,99%的题没有负环,但是可以用来判断负环
阴险的出题人会把SPFA卡掉,使达到最坏O(nm), 网格图可能会卡
-
⭐️步骤
由Bellman-Ford算法用宽搜做优化
queue <- 1 while queue 不空 //队列里面放的是 待更新的点 t=q.front q.pop() 更新t的所有边 t-w->b queue <- b
4.2图示
给定一个有向图,如下,求A~E的最短路。
源点A首先入队,然后A出队,计算出到BC的距离会变短,更新距离数组,BC没在队列中,BC入队
B出队,计算出到D的距离变短,更新距离数组,D没在队列中,D入队。然后C出队,无点可更新。
D出队,计算出到E的距离变短,更新距离数组,E没在队列中,E入队。
E出队,此时队列为空,源点到所有点的最短路已被找到,A->E的最短路即为8
4.2模板题
-
⭐️题目
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出
impossible
。数据保证不存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出
impossible
。数据范围
1≤n,m≤ 1 0 5 10^5 105 ,
图中涉及边长绝对值均不超过 10000。输入样例:
3 3 1 2 5 2 3 -3 1 3 4
输出样例:
2
-
⭐️题解
#include<bits/stdc++.h> using namespace std; const int N=1e5+10; int n,m; int h[N],e[N],ne[N],w[N],idx; int dist[N]; bool st[N]; //表明该点已经在 队列里面了,防止队列里面出现重复元素 void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } int spfa() { memset(dist,0x3f,sizeof dist); dist[1]=0; queue<int> q; q.push(1); //把起点放进去 st[1]=true; while(q.size()) //队列不空 { int t=q.front(); q.pop(); //取出队头 st[t]=false; //该点离开 队列,状态改变 for(int i=h[t];i!=-1;i=ne[i]) //遍历 该点的相邻顶点 { int j=e[i]; if(dist[j]>dist[t]+w[i]) //更新 { dist[j]=dist[t]+w[i]; if(!st[j]) //该点没有在队列里面,则放进去,否则会出现重复 { q.push(j); st[j]=true; } } } } if(dist[n]>0x3f3f3f3f/2) return 0x3f3f3f3f/2+1; //记住有负权边的最短路,最后都这样处理 return dist[n]; } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); //链表初始化 for(int i=0;i<m;i++) { int a,b,w; scanf("%d%d%d",&a,&b,&w); add(a,b,w); } int t=spfa(); if(t>0x3f3f3f3f/2) puts("impossible"); else printf("%d",t); return 0; }
4.4判断负环
-
⭐️思路
用
cnt
记录每个点到起点的边数,当cnt[i] >= n 表示出现了边数>=结点数,必然有环,而且一定是负环根据抽屉原理,可得。
抽屉原理:如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。
-
⭐️题目
链接: AcWing 852. spfa判断负环 - AcWing
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
如果图中存在负权回路,则输出
Yes
,否则输出No
。数据范围
1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。输入样例:
3 3 1 2 -1 2 3 4 3 1 -4
输出样例:
Yes
-
⭐️题解
#include<bits/stdc++.h> using namespace std; const int N=2010,M=1e4+10; int n,m; int h[N],e[M],w[M],ne[M],idx; int dist[N],cnt[N]; //dist 存的是当前从1号点到n号点的长度 //cnt 表示从1到x的最短路径中经过的点数 bool st[N]; void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } bool spfa() { queue<int> q; //这里不用初始化距离,因为咱不是求最短路滴 for(int i=1;i<=n;i++) //把所有的点放进队列里面去,可能存在负环,无法到达起点1,所有遍历所有点 { st[i]=true; q.push(i); } while(q.size()) { int t=q.front(); q.pop(); st[t]=false; for(int i=h[t];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>dist[t]+w[i]) //更新 { dist[j]=dist[t]+w[i]; cnt[j]=cnt[t]+1; //当前到该点的边数+1 if(cnt[j]>=n) return true; //cnt>=n,说明有点走了两遍,一定存在负环 if(!st[j]) { st[j]=true; //入队 q.push(j); } } } } return false; //走到这了,函数还没结束,意味着边数一直小于结点数,不存在负环 } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); while(m--) { int a,b,w; scanf("%d%d%d",&a,&b,&w); add(a,b,w); } if(spfa()) puts("Yes"); else puts("No"); return 0; }
5.Floyd算法
5.1思想
不能应用于含负权回路的图
d[i][j] //邻接矩阵,存的是 i到j 的最短路问题
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]) //更新
5.2模板题
-
⭐️题目
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。
再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出
impossible
。数据保证图中不存在负权回路。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。
输出格式
共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出
impossible
。数据范围
1≤n≤200,
1≤k≤ n 2 n^2 n2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。输入样例:
3 3 2 1 2 1 2 3 2 1 3 1 2 1 1 3
输出样例:
impossible 1
-
⭐️题解
#include<bits/stdc++.h> using namespace std; const int N=210,INF=1e9; int n,m,k; int d[N][N]; void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]); } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i==j) d[i][j]=0; else d[i][j]=INF; while(m--) { int a,b,w; scanf("%d%d%d",&a,&b,&w); d[a][b]=min(d[a][b],w); } floyd(); while(k--) { int a,b; scanf("%d%d",&a,&b); if(d[a][b]>INF/2) puts("impossible"); else printf("%d\n",d[a][b]); } return 0; }
五种算法总结🎈
⭐️Dijkstra-朴素 O(n^2)
- 初始化距离数组, dist[1] = 0, dist[i] = inf;
- for n次循环 每次循环确定一个min加入S集合中,n次之后就得出所有的最短距离
- 将不在S中dist-min的点->t
- t->S加入最短路集合
- 用t更新到其他点的距离
⭐️Dijkstra-堆优化 O(mlogn)
- 利用邻接表,优先队列
- 在priority_queue[HTML_REMOVED], greater[HTML_REMOVED] > heap;中将返回堆顶
- 利用堆顶来更新其他点,并加入堆中类似宽搜
⭐️Bellman-ford O(nm)
- 注意连锁想象需要备份, struct edge { int a , b , c } Edge[M];
- 初始化 dist , 松弛 dist [x.b] = min ( dist [x.b] , backup [x.a] + x.w ) ;
- 松弛 k 次,每次访问 m 条边,有边数限制
⭐️SPFA O(n)~O(nm)
- 利用队列优化仅加入修改过的地方
- for k次
- for 所有边利用宽搜模型去优化 Bellman-ford 算法
- 更新队列中当前点的所有出边
⭐️Floyd O( n 3 n^3 n3)
- 初始化d
- k, i, j 去更新d
部分解释及图示转自acwing