本节适合对最短路稍有了解的读者阅读。最短路是图论这一节中重要的应用,涉及到了相当多的算法。当然这些算法可以不用全部掌握,但最少要略知一二。最短路问题求解主要有两个方向,一个是单源最短路,还有一个是多源最短路(就是是否只有一个起点)。单源最短路求解方法包含了Dijkstra算法,Bellman-ford算法和SPFA算法,而多源最短路问题主要就是用Floyd算法解决,但其时间复杂度较高,代码较为简单,一般算法竞赛中考的比较少(目前本蒟蒻是这样认为的)。

算法分类大概如下所示:

首先是Dijkstra算法,这是所有人都应当熟练掌握的最短路算法。如果图的存储方式不同(邻接矩阵和邻接表),这一种算法的代码根据题目需要也会有稍许不同。

我们先看个最简单易学的代码,运用邻接矩阵存储(具体介绍都放注释里了)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 105;
int graph[N][N];   //用邻接矩阵存储图,注意:有时候算法题,是个稀疏图,点数多,而边数少,容易爆栈
bool st[N];  //表示该点是否已经确定好了最短路
int dist[N];  //表示起点到该点的最短路
int n, m;  //分别表示点数和边数int dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;  //初始化别忘了,之前笔者在这个地方debug了好久for (int i = 0;i < n;i++){int t = -1;   //t初值为-1for (int j = 1;j <= n;j++){if (!st[j] && (t == -1 || dist[t] > dist[j]))  //每次找到尚未确定最短路的,但目前距离起点最短的点{t = j;}}st[t] = true;  //将该点状态设为已确定for (int j = 1;j <= n;j++){dist[j] = min(dist[j], graph[t][j] + dist[t]);  //用t去优化每一个点的最短路}}if (dist[n] == 0x3f3f3f3f) return -1;  //如果终点到起点的距离无穷大,说明没有路径可以从起点到终点,返回-1return dist[n];
}int main()
{ cin >> n >> m;memset(graph, 0x3f, sizeof graph);  //无穷大表示两点之间没有边while (m--)  //将每一条边存入graph中{int a, b, c;  //分别表示边的两端和权重cin >> a >> b >> c;graph[a][b] = min(graph[a][b],c);  //取最短的重边}cout << dijkstra();return 0;
}

认真看懂算法的读者,可能会想,开一个n2空间复杂度的数组,如果是稠密图还好,要是稀疏图的话不就太浪费空间了吗。甚至在算法竞赛中有的题目会故意卡空间,那不就谢了吗?所以我们可以换种方式--邻接表来存储图。用这种方式存储图,空间o(n)左右,但时间复杂度不会降低。邻接表可以用模拟数组的方式或者vector容器实现(STL大法)。

模拟数组实现邻接表的Dijkstra算法

//数组模拟
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 105;
const int M = 10005;
int u[M], v[M], w[M]; //存储每一条边的两端和权重,**如果是无向图,记得数组开两倍
int Head[N];
int Next[N];
bool st[N];  //表示该点是否已经确定好了最短路
int dist[N];  //表示起点到该点的最短路
int n, m;  //分别表示点数和边数int dijkstra()
{memset(dist, 0x3f, sizeof dist);dist[1] = 0;for (int i = 0;i < n;i++){int t = -1;   //t初值为-1for (int j = 1;j <= n;j++){if (!st[j] && (t == -1 || dist[t] > dist[j]))  //每次找到尚未确定最短路的,但目前距离起点最短的点{t = j;}}st[t] = true;  //将该点状态设为已确定int k = Head[t];while(k != -1){dist[v[k]] = min(dist[v[k]], dist[t] + w[k]);k = Next[k];}}if (dist[n] == 0x3f3f3f3f) return -1;  //如果终点到起点的距离无穷大,说明没有路径可以从起点到终点,返回-1return dist[n];
}
int main()
{ cin >> n >> m;for (int i = 1;i <= n;i++)   //初始化head数组下标1~n的值为-1,表示1~n顶点暂时都没有边{Head[i] = -1;}for (int i = 1;i <= m;i++){cin >> u[i] >> v[i] >> w[i];Next[i] = Head[u[i]];  //next和head里存储的是第几条边,而不是顶点Head[u[i]] = i;}cout << dijkstra();return 0;
}

模拟数组实现邻接表的方式可能要多看几遍,确实不容易理解。当然看不懂也没事,我们可以用vector容器来实现。

vector实现邻接表的Dijkstra算法

​
//用邻接表存储的dijkstra算法
//邻接表存储一种方式使用vector容器,另一种方式是用数组模拟
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N = 105;struct Point
{int u, w;
};
vector<Point>g[N];  //用容器开辟一个邻接表
bool st[N];  //表示该点是否已经确定好了最短路
int dist[N];  //表示起点到该点的最短路
int n, m;  //分别表示点数和边数int dijkstra()
{for (int i = 0;i < n;i++){int t = -1;   //t初值为-1for (int j = 1;j <= n;j++){if (!st[j] && (t == -1 || dist[t] > dist[j]))  //每次找到尚未确定最短路的,但目前距离起点最短的点{t = j;}}st[t] = true;  //将该点状态设为已确定for (int k = 0;k < g[t].size();k++){dist[g[t][k].u] = min(dist[g[t][k].u], dist[t] + g[t][k].w);}}if (dist[n] == 0x3f3f3f3f) return -1;  //如果终点到起点的距离无穷大,说明没有路径可以从起点到终点,返回-1return dist[n];
}
int main()
{cin >> n >> m;while (m--){int a, b, c;  //a,b,c分别表示边的两个端点和权重cin >> a >> b >> c;g[a].push_back({ b,c });}//遍历每一条边只要遍历整个容器就行cout << dijkstra();return 0;
}​

STL确实是个好东西,写出来的代码浅显易懂。(什么vector也看不懂or没学过?右上角点一下,谢谢。)

当一种算法学会了之后,我们会很自然的想这种算法能不能再次优化。对于稀疏图其实是可以的,我们用优先队列对其进行堆优化,每次取边只要取小根堆的顶端即可,保证取到的是最短距离。笔者在此不再赘述,对自己要求高的读者,可以自行搜索资料(才不是笔者懒得写呢)。优化后可以把复杂度降到o(mlogn)。

Dijkstra算法有一定的局限性,不能处理负权图。

所以便需要一种新的算法,Bellman-ford算法,这种算法可以处理负权图的问题。同时题目有时候还会加上限制条件,例如:最多只能走多少条边到达终点,这样用dijkstra更不行了。下面代码中的k指的就是最多能走多少条边。

Bellman-ford算法求解最短路

//Bellman-ford算法,可以处理含有负环的题目,但主要用于解决有路径条数限制的题目
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;const int N = 510;
const int M = 10010;
int n, m, k;
int dist[N];
int backup[N];   //用于限制边数后,防止修改一条边的长度,发生连锁反应,修改多条边的长度
struct Edge {     //Bellman-ford直接存储边int a, b, w;
}edges[M];  //存边int bellman_ford()
{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;int b = edges[j].b;int w = edges[j].w;dist[b] = min(dist[b], backup[a] + w);}}if (dist[n] > 0x3f3f3f3f / 2) return -1;  //表示不存在else return dist[n];
}int main()
{cin >> n >> m >> k;for (int i = 0;i < m;i++){int a, b, w;cin >> a >> b >> w;edges[i] = { a,b,w };}memset(dist, 0x3f, sizeof dist);dist[1] = 0;int t = bellman_ford();cout << t;return 0;
}

看完代码,可以发现这个算法只是把每条边存储了起来,还加上了一个拷贝数组,这是尤为需要注意的。那么Bellman-ford算法还能优化吗,答案是可以的,用队列优化(不是优先队列),代码有些类似于BFS。这种优化后的算法称为SPFA,时间复杂度严格意义上来说和Bellman-ford一致的,都是o(nm),但那只是最坏情况,除非出题人恶心,要不然时间复杂度会降低的。还要注意的是SPFA只能处理负权图,不能处理负权回路。同样的笔者只是提一下,具体感兴趣的读者自己查找资料学习。

单源最短路就讲这么多。

多源最短路就一个算法Floyd算法,本质上是一种动态规划的思想。其代码量和动态规划的特点一样----小,但是并不好理解,笔者不赘述。

//floyd算法,能用于求随便两个点之间的最短路,算法复杂度是n3
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
const int N = 105;
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()
{cin >> n >> m;memset(d, 0x3f, sizeof d);while (m--){int a, b, c;cin >> a >> b >> c;d[a][b] = c;}floyd();//打印你想求的两个点就行了return 0;
}

可能有的读者会想,既然这个代码这么短,还能适用于各种情况,那么还要学上面的干嘛?原因很简单,因为时间复杂度太高,o(n3)。

大家这篇博客可以多花一些时间看,辅以别的资源,慢慢学习这些算法。笔者没有好好讲一些算法的优化,需要感兴趣的读者自行寻找资料了。end~~~

图的应用--最短路算法相关推荐

  1. 【讲解 + 模板】四种最短路算法的比较

    四种最短路算法的比较 最短路 最短路,顾名思义,最短的路径. 我们把边带有权值的图称为带权图.边的权值可以理解为两点之间的距离.一张图中任意两点之间会有不同的路径相连.最短路径就是指连接两点的这些路径 ...

  2. 最短路算法总结(入门版)

    最近花了大约一个月左右的时间集中刷了一些图论的题目.虽然收获了许多但是还是付出了很多作业没有做的代价0.0.在这里先把自己所做的关于最短路的基础算法来做一个总结,至少把学到的东西记录下来. 先说明一下 ...

  3. dijkstra算法matlab程序_编程习题课 | 用最短路算法为你的小地图导航

    简介:路网拓扑的正确导入方式,运筹学算法的完整实战案例,最详细的代码讲解与分享. 引言:在研究路径选择和流量分配等交通问题时,常常会用到最短路算法.用最短路算法解决交通问题存在两个难点:一.算法的选择 ...

  4. 坐在马桶上看算法:Dijkstra最短路算法

                                                             [坐在马桶上看算法]算法7:Dijkstra最短路算法 上周我们介绍了神奇的只有五行的 ...

  5. 坐在马桶上看算法:只有五行的Floyd最短路算法

    坐在马桶上看算法:只有五行的Floyd最短路算法 此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在"Communications of the ACM" ...

  6. 疯子的算法总结(八) 最短路算法+模板

    Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV) BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE) SPFA: ...

  7. Dijkstra 最短路算法(只能计算出一条最短路径,所有路径用dfs)

    上周我们介绍了神奇的只有五行的 Floyd 最短路算法,它可以方便的求得任意两点的最短路径,这称为"多源最短路".本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做&q ...

  8. 算法6:只有五行的Floyd最短路算法

           暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路, ...

  9. spfa算法_10行实现最短路算法——Dijkstra

    今天是算法数据结构专题的第34篇文章,我们来继续聊聊最短路算法. 在上一篇文章当中我们讲解了bellman-ford算法和spfa算法,其中spfa算法是我个人比较常用的算法,比赛当中几乎没有用过其他 ...

  10. 浅谈K短路算法(KSP)之一(A*算法求解)

    对于具有n个顶点和m条边且边的权值非负的简单图(无重边和环),K短路,是指的起点s到终点t的最短路径中第k个最小的.K短路分为有限制的K短路和无限制的K短路,有限制的K短路是指求得的路径中不含有回路( ...

最新文章

  1. 苏黎世华人博士提出模型SwinIR,只用33%的参数量就碾压图像修复领域sota
  2. 局域网***-Dos***CDP
  3. linux 下创建并动态加载.so 文件
  4. 操作系统复习题+最终版
  5. 7段均衡器最佳调节图_超高级的吉他均衡器 更细腻的控制 你值得拥有
  6. 信用评分系统运行原理上篇
  7. ubuntu18.04 有线未托管解决
  8. DataGrid相邻行有相同内容时对指定列合并和C#可以实现DLL库的动态调用
  9. sdl android rtp h264,使用RTP协议发送和接收H264的例子(支持解码、播放)
  10. Unity3D图像后处理特效——Crease
  11. 如何使用Java开发QQ机器人 方法一
  12. php导出生成word,php导出生成word的方法
  13. 【时间管理】如何保持精力充沛
  14. 360手机号码归属地查询抓取、免费稳定高效手机号码归属地查询
  15. 【MarkDown】基础语法
  16. 苹果官方mfi认证名单_苹果入驻抖音,完成官方认证
  17. 公云等相关应用怎样用
  18. Elasticsearch索引yellow修复
  19. [翻译] 使用 TensorFlow 进行分布式训练
  20. 利用计算机引号作用,计算机双引号怎么打出来

热门文章

  1. linux 开根号函数,Linux里隐藏的计算器开根号,问他它的地球未解之谜吗?
  2. 12个不容错过的Vue UI 组件库
  3. java通用教务管理系统_基于java的教务管理系统.doc
  4. oracle 英文 简历,免费英文简历范文模板
  5. 手机无法服务器获取信息,荒野行动获取服务器信息一直不动怎么办 获取服务器信息为0解决方法...
  6. Gif动图体积过大如何缩小?仅需三步教你在线压缩gif
  7. 配置jdk与maven环境变量
  8. Odoo的采购入库单、销售发货单整单被取消,或选择了不生成欠单后又想继续入库或发货,如何处理?
  9. java blocked_Java 线程状态之 BLOCKED
  10. 【易我分区大师】磁盘分区助手无损c盘扩容方法