最短路问题(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. 常见爬虫/BOT对抗技术介绍(一)

    爬虫,是大家获取互联网公开数据的有效手段.爬虫.反爬虫技术.反-反爬虫技术随着互联网的不断发展,也在不断发展更新, 本文简要介绍现代的爬虫/BOT对抗技术,如有疏漏,多谢指正! 一.反爬虫/BOT技术 ...

  2. x264阅读记录-2

    x264阅读记录-2 7. x264_encoder_encode函数-1 查看该函数代码(Encoder.c文件)可以发现,该函数中注释很详细,对编码的整个步骤展示的也相对比较清晰. 在查看具体的代 ...

  3. 阿里云物联网平台体验(树莓派+Python篇)

    阿里云物联网平台体验(树莓派+Python篇) 虽然对阿里云物联网平台比较熟悉了,从一开始就有幸参与了飞凤平台(Link Develop 一站式开发平台的前身)的一些偏硬件接入的工作.但是同时也见证了 ...

  4. 朗科32G TF卡的读写测试

    卡是这样的, 下面是实际测试的结果. 容量测试 SKS的USB2外置读卡器, X240内置读卡器加上SD卡套    UNITEK的USB3.0读卡器, 经过UNITEK的USB3.0 HUB 看来读4 ...

  5. maven error: element dependency can not have character children

    就是Mavn pom.xml的解析错误,因为dependency这个标签中有不可见的垃圾字符,解决方法就是删掉重新打字进去就可以了. references: https://stackoverflow ...

  6. php : 开发记录(2017-03-10)

    0.后台 循环N*10000次操作的简单处理 后台需要循环做N*10000次级别的工作时候,比如,发送邮件,推送通知.可以先把所有数据导入数据表(数据库操作所需的时间1~2秒),然后前台循环发送请求, ...

  7. server后台TCP连接存活问题

    公司的server后台部署在某一个地方,接入的是用户的APP,而该地方的网络信号较差,导致了server后台在执行一段时间后用户无法接入,那边的同事反馈使用netstat查看系统.存在较多的TCP连接 ...

  8. Atitti 互联网时代三大竞争战略 ——平台化战略 锚”战略、价值领先战略

    Atitti 互联网时代三大竞争战略 ——平台化战略 锚”战略.价值领先战略 美国著名管理学家迈克尔•波特在<竞争战略>一书中提出了集中化战略和差异化战略.成本领先战略三种基本竞争战略,从 ...

  9. js快速排序算法

    真正的快速排序算法一: function quickSort(array){ function sort(prev, numsize){ var nonius = prev; var j = nums ...

  10. hdoj:2061

    #include <iostream> #include <string> using namespace std; int main() { int n,k; double ...