最短路问题(Bellman/Dijkstra/Floyd)


寒假了,继续学习停滞了许久的算法。接着从图论开始看起,之前觉得超级难的最短路问题,经过两天的苦读,终于算是有所收获。把自己的理解记录下来,可以加深印象,并且以后再忘了的时候可以再看。
最短路问题在程序竞赛中是经常出现的内容,解决单源最短路经问题的有bellman-ford和dijkstra两种算法,其中,dijikstra算法是对bellman的改进。解决任意两点间的最短路有Floyd-warshall算法。

单源最短路1(bellman-ford)

单源最短路是从一个点出发,到其他所有顶点的最短距离。起点是s,假如求s到顶点i的最短路(用数组d[i]表示s到i的最短距离,d[s]=0,d[i]=INF),会有这样一个关系式:
d[i]=min[ d[j]+cost(从j到i的距离),e=(j,i)∈E} ] 即等于(s到j的最短距离加上j到i的距离)中的最小的,j是与i相连的顶点。
先反着想,想要求到i的就得求到j的,同样想要求到j的短距离,就得求与j相连的点的。这样追根溯源到s上,会发现此时d[s]是确定的,s到与其相连的顶点的距离也是确定的,原来还是得正着计算啊。
从s出发,因为两个值都是可以确定的,所以第一次执行那个式子后,与s相连的点的值都会被更新,并且会确定一个与s相连的点的最短路,再继续执行那个式子,又会确定一个(每次执行都要一个循环把所有的点试一遍,因为这样才能适用以任何一个点作为起点的情况),下面是代码:

#define INF 0x3f3f3f3f
struct edge{int from,to,cost;};
edge ee[max_e];//存储每条边的信息
int d[max_v];//表示s到每个顶点的最短距离
int v,e;//顶点数和边数
void bellman(int s){
memset(d,INF,sizeof(d));
d[s]=;
while(true){
bool update=false;
for(int i=;i<e;i++){
edge et=ee[i];
if(d[et.from]!=INF&&d[et.to]>d[et.from]+et.cost){
d[et.to]=d[et.from]+et.cost;
update=true;
}
}
if(!update) break;//如果不再进行更新就退出
}
}

然后在挑战程序设计竞赛上,又专门写了一个函数来判断是否有负圈,下面是代码:

//如果返回TRUE则存在负圈
bool find_negative_loop(){
memset(d,,sizeof(d));
for(int i=;i<v;i++){
for(int j=;j<e;j++){
edge et=ee[i];
if(d[et.to]>d[et.from]+et.cost){
d[et.to]=d[et.from]+et.cost;
//如果第n次仍然更新了,则存在负圈;
if(i==v-) return true;
}
}
}
return false;
}

因为如果不存在负圈,只需要循环v-1次就行了,所以在第一个代码的基础上稍微改动一下就行,只需要添加一个计数器。
这是最终版代码:

//返回true计算最短路成功,返回false存在负圈;
bool bellman(int s){
memset(d,INF,sizeof(d));//赋最大值的技巧
d[s]=;
int j=;//添加的计数器
while(true){
bool update=false;
for(int i=;i<e;i++){
edge et=ee[i];
if(d[et.from]!=INF&&d[et.to]>d[et.from]+et.cost){
d[et.to]=d[et.from]+et.cost;
update=true;
}
}
j++;
if(!update) return true;
if(j==v) return false;//当进行第v次循环时,说明存在负圈
}
}

这里给赋值无穷大有个小技巧。这是针对于有向图来说的,如果是无向图,自己把from和to交换后再输入一次就好。

单源最短路2(dijkstra)

迪杰斯特拉算法对bellman进行了修改,在bellman中,刚开始是到s的最短距离是0,是确定的,然后下一次循环会确定与其相连的顶点里面距离最小的那个,这样每次都会从已经确定了最短路的顶点里面往外扩散。dijkstra就是把每次确定了的顶点给记录下来,然后每次都从未确定的顶点里面找出最短距离最小的那个,然后用它对其相邻的顶点进行更新。先采用邻接矩阵的方式来实现,下面是代码:

int d[max_v];//表示s到每个顶点的最短距离
int v,e;//顶点数和边数
int g[max_v][max_v];// !初值全部为INF
bool used[max_v];//标记已经更新过的顶点
void dijkstra(int s){
fill(d,d+v,INF);
fill(used,used+v,false);//false表示未更新的。
d[s]=;
while(true){
int t=-;
for(int i=;i<v;i++){//查找出最短距离最小的那个点
if(!used[i]&&(t==-||d[t]>d[i])) t=i;
}
if(t==-) break;//如果未查找到说明所有的点都已经确定;
used[t]=true;//将确定了的点记录下来;
for(int i=;i<v;i++){
d[i]=min(d[i],d[t]+g[t][i]);
}
}
}

你可能会发现每次查找最小距离的顶点的时候,是对所有的点进行的查找,在这里可以进行优化,可以用堆来进行优化,我们可以用stl中的priority_queue来实现。你可能还会发现在用最短距离已经确定了的点来对其相邻的点进行更新的时候,可以用邻接表来优化(邻接表记录着每个顶点与哪些顶点相连。)。下面是代码:

struct edge{int to,cost;};
typedef pair<int,int> P;//first是最短距离,second是顶点编号
vector<edge> g[max_v];
int d[max_v];//表示s到每个顶点的最短距离
int v,e;//顶点数和边数
void dijkstra(int s){
priority_queue<P,vector<P>,greater<P> > que;
fill(d,d+v,INF);
d[s]=;
que.push(P(,s));
while(!que.empty()){
P p=que.top();
que.pop();
int vt=p.second;
for(int i=;i<g[vt].size();i++){
edge e=g[vt][i];
if(d[e.to]>d[vt]+e.cost){
d[e.to]=d[vt]+e.cost;
que.push(P(d[e.to],e.to));
}
}
}
}

任意两点间的最短路(floyd-warshall)

floyd是用动态规划的思想来解决问题,用一个数组d[i][j]来表示从i到j的最短距离。在顶点i和j之间可能会是顶点k,如果用0-v-1来表示每个顶点会有这样一个关系式: d[i][j]=min(d[i][j],d[i][k]+d[k][j] ) 然后对所有的顶点k都计算一遍。下面是代码:

int d[max_v][max_v];//不存在时设为INF,注意d[i][i]=0;
int v;//顶点数
for(int k=;k<v;k++){
for(int i=;i<v;i++){
for(int j=;j<v;j++){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}

floyd跟bellman一样,都可以处理边为负数的情况,只需要判断d[i][i]为负数的即可。

路径记录

有的问题需要输出最短路径,以dijkstra算法为例,当d[j]=d[k]+cost[k][j]时候,顶点k便是顶点j的前驱,我们只需在更新的时候进行记录就行了。

int prev[max_v];// ***添加这一个数组
int d[max_v];//表示s到每个顶点的最短距离
int v,e;//顶点数和边数
int g[max_v][max_v];// !初值全部为INF
bool used[max_v];//标记已经更新过的顶点
void dijkstra(int s){
fill(d,d+v,INF);
fill(used,used+v,false);//false表示未更新的。
d[s]=;
while(true){
int t=-;
for(int i=;i<v;i++){//查找出最短距离最小的那个点
if(!used[i]&&(t==-||d[t]>d[i])) t=i;
}
if(t==-) break;//如果未查找到说明所有的点都已经确定;
used[t]=true;//将确定了的点记录下来;
for(int i=;i<v;i++){
//d[i]=min(d[i],d[t]+g[t][i]);
if(d[i]>d[t]+g[t][i]) prev[i]=t;//***新增
}
}
}
//到t的最短路求解
vector<int> get_path(int t){
vector<int> path;
for( ;t!=-;t=prev[t]) path.push_back(t);
reverse(path.begin(),path.end());
return path;
}

经过阅读发现《挑战程序设计竞赛》中的算法描述还是比较准确易懂的,代码实现也非常简洁。

理解是一回事,会运用又是一回事。结果我又花了两天才写完这篇文章并把代码实现出来。自己在写的时候,总是感觉很多东西有点模棱两可,不知道该怎么解释,这应该就是理解不够深刻的原因吧。如果很清楚其中的过程,就不会有这种感觉了。
自己写的可能有很多错误的地方或需要改进的地方,如有发现,欢迎指正!
最短路算法还有A*算法,对bellman进行优化的SPFA算法,以后学习了再来更新。

转载请注明出处:http://taowusheng.cn/
微博:寒枫–0-0–
知乎:https://www.zhihu.com/people/tao-wu-sheng
豆瓣:YIFEI

最短路问题(Bellman/Dijkstra/Floyd)的更多相关文章

  1. 最短路径:Dijkstra & Floyd 算法图解,c++描述

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. poj1847 Tram(Dijkstra || Floyd || SPFA)

    题目链接 http://poj.org/problem?id=1847 题意 有n个车站,编号1~n,每个车站有k个出口,车站的出口默认是k个出口中的第一个,如果不想从默认出口出站,则需要手动选择出站 ...

  3. PKU 1932 XYZZY(Floyd+Bellman||Spfa+Floyd)

    题目大意:原题链接 给你一张图,初始你在房间1,初始生命值为100,进入每个房间会加上那个房间的生命(可能为负),问是否能到达房间n.(要求进入每个房间后生命值都大于0) 解题思路: 解法一:Floy ...

  4. 图论算法——最短路径Dijkstra,Floyd,Bellman Ford

    算法名称 适用范围 算法过程 Dijkstra 无负权 从s开始,选择尚未完成的点中,distance最小的点,对其所有边进行松弛:直到所有结点都已完成 Bellman-Ford 可用有负权 依次对所 ...

  5. 关于SPFA Bellman-Ford Dijkstra Floyd BFS最短路的共同点与区别

    关于模板什么的还有算法的具体介绍 戳我 这里我们只做所有最短路的具体分析. 那么同是求解最短路,这些算法到底有什么区别和联系: 对于BFS来说,他没有松弛操作,他的理论思想是从每一点做树形便利,那么时 ...

  6. hdoj2544 最短路(Dijkstra || Floyd || SPFA)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2544 思路 最短路算法模板题,求解使用的Dijkstra算法.Floyd算法.SPFA算法可以当做求解 ...

  7. 图论-最短路径<Dijkstra,Floyd>

    昨天: 图论-概念与记录图的方法 以上是昨天的Blog,有需要者请先阅读完以上再阅读今天的Blog. 可能今天的有点乱,好好理理,认真看完相信你会懂得 分割线 第二天 引子:昨天我们简单讲了讲图的概念 ...

  8. Dijkstra,floyd,spfa三种最短路的区别和使用

    这里不列举三种算法的实现细节,只是简单描述下思想,分析下异同 一 Dijkstra Dijkstra算法可以解决无负权图的最短路径问题,只能应付单源起点的情况,算法要求两个集合,开始所有点在第二个集合 ...

  9. 最短路径---Dijkstra/Floyd算法

    1.Dijkstra算法基础: 算法过程比prim算法稍微多一点步骤,但思想确实巧妙也是贪心,目的是求某个源点到目的点的最短距离,总的来说dijkstra也就是求某个源点到目的点的最短路,求解的过程也 ...

随机推荐

  1. tk.mybatis通用插件updateByPrimaryKeySelective无法自动更新ON UPDATE CURRENT_TIMESTAMP列的解决办法

    tk.mybatis是一个很好用的通用插件,把CRUD这些基本的数据操作全都用动态SQL语句自动生成了,mapper和xml里十分清爽,但是昨天发现有一个小坑,记录在此: 有一张表,结构如下(已经简化 ...

  2. Amazon Publisher Studio让产品推广更简单

    Amazon联盟最近推出了Publisher Studio新功能,只要在Amazon联盟网站后台添加一段特定的代码,以后推广所有Amazon产品的时候就不需要再访问Amazon Associates网 ...

  3. 毕业论文Word格式技巧

    word利用多级列表功能实现章节标题自动编号 坑:在选择级别1后,直接在当前对话框直接继续设置级别2:然后选中级别2,设置相应的格式,主要在将级别链接到样式处选择为标题2,然后要在库中显示的级别选择级 ...

  4. 如何对正在运行的进程,进行heap profile

    简单来说, 就是先preload上tcmalloc, 日常用用没啥问题, 当感觉出现问题时, gdb attach 上, 然后执行 call HeapProfilerStart("xxx&q ...

  5. PL/SQL学习笔记之集合

    一:PL/SQL集合 集合是一个有序且存有相同的类型数据的数据结构. PL/SQL提供了三种集合类型: 索引表(关联数组) 嵌套表 数组 二:索引表:一个索引表(也叫关联数组)是一组键 - 值对.每个 ...

  6. 【PHP】PHP 7.4 新特性

    PHP 7.4 预计在 2019 年年末就会正式发布了,本文先来看看一下 PHP 7.4 的新特性. 1.预加载 预加载的实现理论上是可以为 PHP 带来很大的性能提升的.比如说:现在传统的 PHP ...

  7. OpenLayers Node环境安装运行构建-支持Vue集成OpenLayers

    NodeJS 环境安装包下载:https://nodejs.org/zh-cn/download/ 安装vue-cli3.0.1: https://cli.vuejs.org/guide/instal ...

  8. Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 2)

    在我们设计和开发应用程序时,经常要用到控件.比如开发一个客户端WinForm应用程序时,微软就为我们提供了若干控件,这些控件为我们提供了可被定制的属性和事件.属性可以更改它的外观,比如背景色,标题等, ...

  9. [转]Anatomy of a Program in Memory

    Memory management is the heart of operating systems; it is crucial for both programming and system a ...

  10. Windows安装VNC服务端

    下载VNC服务端 由于服务器在IDC机房,只能使用系统自带远程桌面连接到服务器进行安装VPC服务端 但在安装过程发现,如果是通过远程桌面连接到服务器进行安装,VNC Mirror Driver会报错无 ...