目录
一.朴素Dijkstra算法
二.堆优化的Dijkstra
三.bellman_ford算法
四.spfa算法
五.floyd算法
使用区别:
所有边权都是正数的单源最短路:朴素Dijkstra算法,堆优化的Dijstra算法。
存在负权边的单源最短路:Bellman-ford,spfa。
多源汇最短路:floyd算法。
总结:
朴素Dijkstra算法: 适用于稠密图,适合邻接矩阵存储,时间复杂是 O(n^2+m), n表示点数,m 表示边数。
堆优化的Dijkstra算法:适用于稀疏图,适合邻接表存储,时间复杂度是O(mlogn)
Bell-Ford算法: 适用于处理可能存在负环的有限路线单源最短路问题,时间复杂度是O(nm),一般不能有负权回路。
spfa算法:时间复杂度是O(m)最坏O(nm)。
floyd算法:时间复杂度O(n^3)。
一.朴素Dijkstra算法
简介:迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
算法思路:
按路径长度递增次序产生算法:
把顶点集合V分成两组:
(1)S:已求出的顶点的集合(初始时只含有源点V0)
(2)V-S=T:尚未确定的顶点集合
将T中顶点按递增的次序加入到S中,保证:
(1)从源点V0到S中其他各顶点的长度都不大于从V0到T中任何顶点的最短路径长度
(2)每个顶点对应一个距离值
S中顶点:从V0到此顶点的长度
T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度
依据:可以证明V0到T中顶点Vk的,或是从V0到Vk的直接路径的权值;或是从V0经S中顶点到Vk的路径权值之和。
算法步骤如下:
G={V,E}
1. 初始时令 S={V0},T=V-S={其余顶点},T中顶点对应的距离值
若存在,d(V0,Vi)为弧上的权值
若不存在,d(V0,Vi)为∞
2. 从T中选取一个与S中顶点有关联边且权值最小的顶点W,加入到S中
3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值
重复上述步骤2、3,直到S [1] 中包含所有顶点,即W=Vi为止
简单地说就是先找出一个距离起点最近的,然后再由这个距离起点最近的点更新别的点
题目:https://www.acwing.com/problem/content/851/
代码如下:
#includeusing namespace std; //dijkstra算法适用于正权边稠密图 const int N = 510; int n,m; int dist[N],g[N][N];//存放距离和图 bool st[N];//存放状态 int dijkstra() { memset(dist,0x3f,sizeof dist); dist[1] = 0; //遍历n次,将n个点的最值都找出 for(int i=0;i dist[j])) t = j;//依次比较距离1号点最近的点赋给t } //根据t更新t的出边 for(int j = 1;j<=n;j++) { dist[j] = min(dist[j],dist[t]+g[t][j]); } //表示这个点已经确定了 st[t] = true; } //如果没有边就是无穷 if(dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { cin>>n>>m; memset(g,0x3f,sizeof g); while(m--) { int a,b,c; cin>>a>>b>>c; g[a][b] = min(g[a][b],c);//因为有重边,取一条最短的边 } int t = dijkstra(); cout< 二.堆优化的Dijkstra
简介:在确定一个还未确定最短距离的点中距离源点最近距离的点时 朴素Dijkstra 的方法是遍历所有的点通过比较找出最近的点,在这个地方可以使用 优先队列(小根堆) 来进行优化,在所有的点中取出距离最小的点。
算法思路:
首先,将 优先队列 定义成 小根堆,将源点的距离初始化为 0 加入到优先队列中。
然后,从这个点开始扩展。先将队列中的队头元素 ver 保存到一个临时变量中,并将队头元素出队,然后遍历这个点的所有出边所到达的点 j,更新所有到达的点距离源点最近的距离。
如果源点直接到 j 点的距离比源点先到 ver 点再从 ver 点到 j 点的距离大,那么就更新 dist[j],使 dist[j] 到源点的距离最短,并将该点到源点的距离以及该点的编号作为一个 pair 加入到优先队列中,然后将其标记,表示该点已经确定最短距离。因为是小根堆,所以会根据距离进行排序,距离最短的点总是位于队头。一直扩展下去,直到队列为空。
因为有 重边 的缘故,所以该点可能会有冗余数据,即如果在扩展的时候,第一次遍历到的点是 2 号点,距离 源点 的距离为 10,此时 dist[2] = 0x3f3f3f3f > dist[1] + distance[1 -> 2] = 0 + 10 = 10 所以 dist[2] 会被更新为 10,此时会将 {10, 2} 入队。但是很不巧从 源点 到 2 号点有一个距离为 6 的重边,当遍历到这个重边时,由于 dist[2] = 10 > dist[1] + distance[1 -> 2] = 0 + 6 = 6,所以 {6, 2} 也入队了,入队之后由于是 小根堆 所以 {6, 2} 会排在 {10, 2} 前面,所以 {6, 2} 会先出队,出队之后会被标记。所以当下一次再遇到已经被标记的 2 号点时,直接 continue 忽略掉冗余数据继续扩展下一个点即可。
题目:https://www.acwing.com/problem/content/852/
代码如下:
#includeusing namespace std; const int N = 1e6+10; int h[N],e[N],ne[N],w[N],idx;//稀疏图采用邻接表存储 typedef pair p;//存结点距离和编号 int dist[N]; int n,m; 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() { priority_queue ,greater
> q; memset(dist,0x3f,sizeof dist); q.push({0,1}); dist[1] = 0; while(!q.empty()) { p t = q.top(); q.pop(); int ver = t.second,dis = t.first; //如果此节点已经确定最小值就continue if(st[ver]) continue; st[ver] = true; //else就拓展此节点,根据t更新t的拓展节点 for(int i = h[ver];i!=-1;i=ne[i]) { int j = e[i]; if(dist[j]>dist[ver]+w[i]) { dist[j] = dist[ver]+w[i]; q.push({dist[j],j}); } } } if(dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { cin>>n>>m; memset(h,-1,sizeof h); while(m--) { int a,b,c; cin>>a>>b>>c; add(a,b,c); } cout<
三.bellman_ford算法
简介:贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼和莱斯特·福特创立的,求解单源最短路径问题的一种算法。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,最坏O(nm).
Bellman-Ford算法是一种处理存在负权边的单元最短路问题的算法。解决了Dijkstra无法求的存在负权边的问题。 虽然其算法效率不高,但是也有其特别的用处。其实现方式是通过m次迭代求出从源点到终点不超过m条边构成的最短路的路径。一般情况下要求途中不存在负环。但是在边数有限制的情况下允许存在负环。因此Bellman-Ford算法是可以用来判断负环的。很重要的一点是本算法每次迭代都是在上一次的基础上进行的,因此我们在代码实现时要注意保留上一次的结果,在上一次的基础上算,故需要将上次的数组copy一份。
模板题目:https://www.acwing.com/problem/content/855/
代码如下:
#includeusing namespace std; const int N = 510,M = 10010; int dist[N],backup[N]; int n,m,k; struct Edge{ int a,b,c; }edges[M]; void bellman_ford() { memset(dist,0x3f,sizeof dist); dist[1] = 0; //k表示从i到j经过k条边 for(int i=0;i >n>>m>>k; for(int i=0;i >a>>b>>c; edges[i] = {a,b,c}; } bellman_ford(); if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); else printf("%dn", dist[n]); return 0; } 四.spfa算法
简介: SPFA 算法是Bellman-Ford算法的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素Bellman-Ford相同,为O(nm)
优化思路:
Bellman-Ford算法在每次更新时,遍历了所有的边,而也如前面看到的,每次遍历未必会真的更新最短距离。SPFA算法就是对该步骤的优化。每次只有当前的起点到源点的距离变小了,该点连通的其他点才会变小。
只要一个结点的边变小了,我们就把他放进队列(其他的也行)中,只要队列中还有值,我们就继续更新,更新到的点也加入队列,如此往复,直到标记全部的点。题目:https://www.acwing.com/problem/content/853/
代码如下:
#includeusing namespace std; const int N = 100010; int h[N],e[N],w[N],ne[N],idx; int dist[N]; bool st[N]; int n,m; void add(int a,int b,int c) { e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++; } int spfa() { queue q; memset(dist,0x3f,sizeof dist); dist[1] = 0; q.push(1); st[1] = true; while(!q.empty()) { 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; } } } } return dist[n]; } int main() { cin>>n>>m; memset(h,-1,sizeof h); while(m--) { int a,b,c; cin>>a>>b>>c; add(a,b,c); } int t = spfa(); if(t==0x3f3f3f3f) cout<<"impossible"< 五.floyd算法
简介:弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包,Floyd 算法是一个基于贪心、动态规划求一个图中 所有点到所有点 最短路径的算法。
算法思路:
以每个点为「中转站」,刷新所有「入度」和「出度」的距离。每次从「未求出最短路径」的点中 取出 最短路径的点,并通过这个点为「中转站」刷新剩下「未求出最短路径」的距离。简单地说就是dist[i,j]表示从i到j经过k这个点的最短距离是多少。
题目:https://www.acwing.com/problem/content/853/
代码如下:
#includeusing namespace std; const int N = 210,M = 20010; int dist[N][M]; int n,m,k; void floyd() { //以k这个点作为中转点更新从i到j的距离 for(int k=1;k<=n;k++) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) //比较从i直接到j的距离和从i到j以k为中转的距离最小值 dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]); } } } int main() { cin>>n>>m>>k; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { if(i==j) dist[i][j]=0; else dist[i][j] = 0x3f3f3f3f; } while(m--) { int x,y,z; cin>>x>>y>>z; dist[x][y] = min(dist[x][y],z); } floyd(); while(k--) { int x,y; cin>>x>>y; if(dist[x][y]>0x3f3f3f3f/2) cout<<"impossible"<



