最短路问题(Bellman/Dijkstra/Floyd)
最短路问题(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)的更多相关文章
- 最短路径:Dijkstra & Floyd 算法图解,c++描述
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- poj1847 Tram(Dijkstra || Floyd || SPFA)
题目链接 http://poj.org/problem?id=1847 题意 有n个车站,编号1~n,每个车站有k个出口,车站的出口默认是k个出口中的第一个,如果不想从默认出口出站,则需要手动选择出站 ...
- PKU 1932 XYZZY(Floyd+Bellman||Spfa+Floyd)
题目大意:原题链接 给你一张图,初始你在房间1,初始生命值为100,进入每个房间会加上那个房间的生命(可能为负),问是否能到达房间n.(要求进入每个房间后生命值都大于0) 解题思路: 解法一:Floy ...
- 图论算法——最短路径Dijkstra,Floyd,Bellman Ford
算法名称 适用范围 算法过程 Dijkstra 无负权 从s开始,选择尚未完成的点中,distance最小的点,对其所有边进行松弛:直到所有结点都已完成 Bellman-Ford 可用有负权 依次对所 ...
- 关于SPFA Bellman-Ford Dijkstra Floyd BFS最短路的共同点与区别
关于模板什么的还有算法的具体介绍 戳我 这里我们只做所有最短路的具体分析. 那么同是求解最短路,这些算法到底有什么区别和联系: 对于BFS来说,他没有松弛操作,他的理论思想是从每一点做树形便利,那么时 ...
- hdoj2544 最短路(Dijkstra || Floyd || SPFA)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2544 思路 最短路算法模板题,求解使用的Dijkstra算法.Floyd算法.SPFA算法可以当做求解 ...
- 图论-最短路径<Dijkstra,Floyd>
昨天: 图论-概念与记录图的方法 以上是昨天的Blog,有需要者请先阅读完以上再阅读今天的Blog. 可能今天的有点乱,好好理理,认真看完相信你会懂得 分割线 第二天 引子:昨天我们简单讲了讲图的概念 ...
- Dijkstra,floyd,spfa三种最短路的区别和使用
这里不列举三种算法的实现细节,只是简单描述下思想,分析下异同 一 Dijkstra Dijkstra算法可以解决无负权图的最短路径问题,只能应付单源起点的情况,算法要求两个集合,开始所有点在第二个集合 ...
- 最短路径---Dijkstra/Floyd算法
1.Dijkstra算法基础: 算法过程比prim算法稍微多一点步骤,但思想确实巧妙也是贪心,目的是求某个源点到目的点的最短距离,总的来说dijkstra也就是求某个源点到目的点的最短路,求解的过程也 ...
随机推荐
- JavaScirpt对象原生方法
Object.assign() Object.assign()方法用于合并对象,只会合并可枚举的属性 const obj1= {a: 1} const obj2 = Object.assign({}, ...
- javagc日志详解
https://blog.csdn.net/aliveTime https://plumbr.io/blog/garbage-collection/understanding-garbage-coll ...
- Get Set的问题解决
- JavaScript数组(三)数组对象使用整理
一.数组声明方法1. var a=new Array();2. var a=new Array([size]);3.var a=new Array(['a'],[1],['b'],[123]);4. ...
- 这13个开源GIS软件,你了解几个?【转】
泰伯网有看点的空间地理信息资讯都在这,你还在等什么? 这些开源GIS软件,你了解几个?本文内容部分来源于一份罗列了关于GIS软件应用的文章,笔者将其编译整合. 地理信息系统(Geographic In ...
- fopen和fopen_s用法的比较
open和fopen_s用法的比较 fopen 和 fopen_s fopen用法: fp = fopen(filename,"w"). fop ...
- linux 安装nvm,通过nvm安装node
1,nvm git地址点击打开链接,安装命令 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh ...
- 单片机成长之路(51基础篇) - 006 在Linux下搭建51单片机的开发烧写环境
在Linux下没有像keli那样好用的IDE来开发51单片机,开发环境只能自己搭建了. 第一步:安装交叉编译工具 a) 安装SDCC sudo apt-get install sdcc b)测试SDC ...
- 超简单的okhttp封装工具类(上)
版权声明:转载请注明出处:http://blog.csdn.net/piaomiao8179 https://blog.csdn.net/piaomiao8179/article/details/ ...
- LaTeX网址
https://www.latex-project.org/ latex官网 http://www.latexstudio.net/ 国内知名latex学习中心 https://www.ove ...