最短路径(Dijkstra、Bellman-Ford和SPFA算法)
最短路径(Dijkstra、Bellman-Ford和SPFA算法)
- 前言
- 图的存储方式
- 邻接矩阵
- 邻接表
- 链表建立
- 利用vector
- 结构体
- 核心思路
- Dijkstra算法
- 图解
- 基本思想
- 求解步骤
- 细节解释
- 变量的意义
- 初始化
- 迭代次数
- 更新条件
- 完整代码
- Bellman-Ford算法
- 思路:
- 变量说明
- 初始化
- 完整代码
- Bellman-Ford 检测图是否含有负圈(回路)
- 思路
- 代码
- Bellman-Ford 算法优化
- SPFA算法
- 思路
- 完整代码
- 算法的比较
前言
提醒一下:最短路径一般用于有向网(就是有方向和权值的图),而最小生成树一般是用于无向网。
本来是想分开写,不过想到这样大家看起来比较麻烦。就写在一起了,这样可以很好的进行对比。
图的存储方式
常用的无非就
- 邻接矩阵
- 邻接表
- 链式前向星
邻接矩阵
这个可以说是最好理解的了,分为以下几种情况
- 无权值得图,初始化为0,有边想连(有关系)就赋值为1
- 有权值图(网),初始化为 INF(一个大的常数),有边想连(有关系)就赋值为 该权值
邻接表
可以说邻接表的建图就很花里胡哨了,如果不理解它的意思,肯定被每一个人都不一样的邻接表给弄糊涂的。为了让大家看下边的代码不乱,特意说明一下。
链表建立
- 对于链表,就是一个一维数组,其中存放表头节点,每一个表头节点都连着一个链表,是由其相邻的顶点组成。
利用vector
基于链表中的特性,可以用一个vector数组来代替链表
- 如果是不带权值的
vector<int>edge[N];int main()
{int V, E;cin >> V >> E;for (int i = 0; i < E; ++i){int u,v;cin >> u >> v;edge[u].push_back(v);/*如果是无向图edge[u].push_back(v);edge[v].push_back(u);*/}// 遍历,通过顶点来遍历,for (int i = 1; i <= V; ++i){for (int j = 0; j < edge[i].size(); ++j)cout << edge[i][j] << " ";}}
- 如果有权值的话,有两个地方改就好
#define pi pair<int,int>
vector<pi>edge[N];
- 存的时候,
for (int i = 0; i < E; ++i){int u,v,w;cin >> u >> v>>w;edge[u].push_back(make_pair(v,w));/*如果是无向图edge[u].push_back(make_pair(v,w));edge[v].push_back(make_pair(u,w));*/}
结构体
结构体的话就想到与用边来作为一个头来建立
//从顶点u到顶点v的权值为w的边
struct edge
{int u, v, w;
};
edge es[N];//储存所有边int main()
{int V, E;cin >> V >> E;for (int i = 1; i <= E; ++i){int u, v,w;cin >> u >> v>>w;es[i] = edge{ u, v, w };/*如果是无向图es[i] = edge{ u, v, w };es[i] = edge{ v, u, w };*/}// 遍历,通过边来遍历,for (int i = 1; i <= E; ++i){edge e = es[i];cout << e.u << " " << e.v << " " << e.w << endl;}}
相信说到这,就可以慢慢悟了把
核心思路
本来想放在后头,不过怕大家一路看过去直接迷了。就放在前边,可以带着这个去看.
Dijkstra算法
// dis[k] 中 dis[k] 表示源点到 顶点k的距离(直接到k)
//dis[mark] + g[mark][k] 先到当前距离短的点mark,通过这个点转到k
dis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距离
Bellman-Ford和SPFA算法
if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;
其实大家理解和这两个是一个道理。都可以通过下面这段话理解。
可以想一下有三个点,1,2,3.刚开始1到3距离为6,1到2是3,2到3是2.以1为源点,因为到顶点2的距离小,所以将2加入。这个时候在更新距离时,之前没有加入2的时候1到3只有1–>3这个路径可以走,而现在2加入后,有新的路径了1–>2–>3,我们在算一下长度,哇才是5,比直接走到3小,好那么我们就改变路径。这就是上面代码的意义。
Dijkstra算法
图解
由于本博主画的图太,借了点图
----- S是已计算出最短路径的顶点的集合
----- U是未计算出最短路径的顶点的集合
----- C(3)表示顶点C到起点D的最短距离为3
- 选择顶点D
S={D(0)}
U={A(∞), B(∞), C(3), E(4), F(∞), G(∞)}
- 选取顶点C
S={D(0), C(3)}
U={A(∞), B(13), E(4), F(9), G(∞)}
就这样当作一个引子
基本思想
把带权图中的所有顶点V分成两个集合S和T,S中存放已经确定最短路径的顶点,初始时,S中仅包含一个源点; T=V−S,存储待确定最短路径的顶点,初始时,T中包含除源点外的顶点,此时各顶点的当前最短路径长度为源点到各顶点的弧上的权值。开始操作时,从T中选取当前最短路径长度最小的一个顶点 Vi 加入S,然后修改T中剩余顶点的当前最短路径长度,修改的原是:当 Vi 的最短路径长度与 Vi 到T中的顶点之间的权值之和小于该顶点的当前路径长度时,用前者替换后者。重复上述过程,直到S中包含所有的顶点
为止。
简单来说,将点分为两个集合,将源点放入一个集合中,其余的点放入另一个集合。(实现算法不需要用到集合,可以用一个bool 数组来记录,为true就表明就加入到了源点所在的集合中)。然后遍历不在源点集合的点,找到距离源点最近的点。将这个点加入到源点所在的集合中。然后以这个点为中转点更新源点到其余点的距离。
求解步骤
- 将有向网用邻接矩阵储存,没有相连的边用一个较大的数表示。
- 找到距离源点最近的点,将其加入到源点所在的集合中。
- 以这个点为中转点,更新源点到其余点的距离。
细节解释
变量的意义
int g[N][N];//邻接矩阵存图,g[i][j] 表示 顶点i带顶点j的距离
int dis[N];//表示 源点src到 i的最短距离
bool vis[N];//是否在集合中
int stc;//源点
int n, m;//n 顶点数,m边数
初始化
void init()
{mst(g, 0);mst(vis, 0); //初始化点的集合为空mst(dis, INF);//初始化距离为一个很大的数
}
迭代次数
for (int i = 1; i < n; ++i) //n 个点,只用迭代n-1次就好
要明确算法的结束标识是:将所有点加入到源点所在的集合,这样就好理解了。
因为刚开始就将源点放入集合中了,所要就还需要n-1次
更新条件
这就是神秘的松弛操作了
// dis[k] 中 dis[k] 表示源点到 顶点k的距离(直接到k)
//dis[mark] + g[mark][k] 先到当前距离短的点mark,通过这个点转到k
dis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距离
可以想一下有三个点,1,2,3.刚开始1到3距离为6,1到2是3,2到3是2.以1为源点,因为到顶点2的距离小,所以将2加入。这个时候在更新距离时,之前没有加入2的时候1到3只有1–>3这个路径可以走,而现在2加入后,有新的路径了1–>2–>3,我们在算一下长度,哇才是5,比直接走到3小,好那么我们就改变路径。这就是上面代码的意义。
完整代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<set>
//#include<unordered_map>
#include<ctime>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define rep(i,k,n) for(int i=k;i<=n;i++)
#define INF 0x3f3f3f3f
const int N = 2e3 + 10;int g[N][N];//邻接矩阵存图,g[i][j] 表示 顶点i带顶点j的距离
int dis[N];//表示 源点src到 i的最短距离
bool vis[N];//是否在集合中
int stc;//源点
int n, m;//n 顶点数,m边数
void init()
{mst(g, 0);mst(vis, 0); //初始化点的集合为空mst(dis, INF);//初始化距离为一个很大的数
}void Dijkstra()
{dis[stc] = 0;//自己到自己的距离肯定为1vis[stc] = 1;//把源点存入集合中for (int i = 1; i < n; ++i) //n 个点,只用迭代n-1次就好{int mark = stc, midis = INF;int j;for ( j = 0; j < n; ++j){if (!vis[j] && dis[j] < midis)//从没有加入的点中{//寻找一个最小值mark = j;midis = dis[j];}}// mark 就是找到的 不在集合内部且当前路径长度(与源点的距离)最小的顶点if (mark != stc) //如果不是本身vis[mark] = 1; //将点j加入集合//对不在集合中的顶点修改dis[j]的值for (int k = 0; k < n; ++k){if (!vis[k])// dis[k] 中 dis[k] 表示源点到 顶点k的距离(直接到k)//dis[mark] + g[mark][k] 先到当前距离短的点mark,通过这个点转到kdis[k] = min(dis[k], dis[mark] + g[mark][k]); //取最小的距离}}
}
Bellman-Ford算法
思路:
要让以下等式成立
dis[v]=min(dist[v],dis[u]+e(u,v)∣(u,v)∈Edis[v] = min ({dist[v],dis[u]+e(u,v)|(u,v)\in E} dis[v]=min(dist[v],dis[u]+e(u,v)∣(u,v)∈E
其中 v是终点,u是起点。
变量说明
//从顶点u到顶点v的权值为w的边
struct edge
{int u,v,w;
}
edge es[maxn] ;//储存所以边
int dis[maxn]; //最短距离
int V,E; //顶点数、边数
初始化
如果s是源点(起点)则dis[s] = 0,之外,其余为INF(足够大的常数)
完整代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<set>
//#include<unordered_map>
#include<ctime>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define rep(i,k,n) for(int i=k;i<=n;i++)
#define INF 0x3f3f3f3fconst ll maxn = 2e5 + 10;
//从顶点u到顶点v的权值为w的边
struct edge
{int u, v, w;
};
edge es[maxn];//储存所有边
int dis[maxn]; //最短距离
int V, E; //顶点数、边数//求解从顶点s出发到所以点的最短距离void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;//对每一条边进行V-1次松弛for (int i = 0; i < V; ++i){for (int j = 0; j < E; ++j){edge e = es[j];if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;}}}
- 为什么是V-1次呢?
因为一个含有n个顶点的图中,任意两个顶点之间的最短路径最多包含n-1条边,
Bellman-Ford 检测图是否含有负圈(回路)
思路
如果进行了V-1 次(V为顶点数)松弛后还可以进行松弛就表明存在负圈
代码
在上面的Bellman-Ford基础上,
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<set>
//#include<unordered_map>
#include<ctime>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define rep(i,k,n) for(int i=k;i<=n;i++)
#define INF 0x3f3f3f3fconst ll maxn = 2e5 + 10;
//从顶点u到顶点v的权值为w的边
struct edge
{int u, v, w;
};
edge es[maxn];//储存所以边
int dis[maxn]; //最短距离
int V, E; //顶点数、边数//求解从顶点s出发到所以点的最短距离void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;//对每一条边进行V-1次松弛for (int i = 0; i < V; ++i){for (int j = 0; j < E; ++j){edge e = es[i];if (dis[e.v] > dis[e.u] + e.w)dis[e.v] = dis[e.u] + e.w;}}}bool neg_cir()
{bool flag = false;for (int i = 0; i < E; ++i){edge e = es[i];if (dis[e.v] > dis[e.u] + e.w){flag = true;break;}}return flag;
}
Bellman-Ford 算法优化
在实际操作中,Bellman-Ford 算法经常会未达到 n - 1轮松弛前就已经计算出所有最短路,之前我们已经说过,n - 1其实是最大值。因此我们需要变量判断一下下轮循环 dis中的值是否改变,可以提前跳出循环,提高效率,基本优化如下:
void Bellman_Ford(int s) {for (int i = 0; i < V; ++i)dis[i] = INF;dis[s] = 0;while (true){bool update = false; //检测是否有更新for (int i = 0; i < E; ++i){edge e = es[i];//如果起点到 e.u 距离为 INF 就说明无法松弛了if (dis[e.u] != INF && dis[e.v] > dis[e.u] + e.w) {dis[e.v] = dis[e.u] + e.w;update = true;}}//没有的话就退出循环if (!update)break;}}
SPFA算法
思路
SPPA其实是Bellman-Ford的队列优化。我们用数组dits比录每个结点的最短路径估计值,并用邻接表来存储图g。我们采取的方法是松弛:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
只要最短路径存在,上述SPFA算法必定能求出最小值。因为每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点p的最短路径估计值d[9]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。
完整代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<set>
//#include<unordered_map>
#include<ctime>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define rep(i,k,n) for(int i=k;i<=n;i++)
#define INF 0x3f3f3f3f
const int N = 2e3 + 10;//也可以用结构体
vector<pi> g[N];//邻接表储存所以边,// g[i][j].first 表示i节点的第j条边的编号(本来邻接表的作用)//g[i][j].second 表示边的长度,权值
int dis[N];//表示 源点src到 i的最短距离
bool vis[N];//是否在集合中
int src;//源点
int n, m;//n 顶点数,m边数
queue<int>q;void init()
{for (int i = 0; i <= n; ++i)g[i].clear();mst(vis, 0); //初始化点的集合为空mst(dis, INF);//初始化距离为一个很大的数while (!q.empty())q.pop(); //将队列清空
}void spfa()
{init();//记得初始化dis[src] = 0;//自己到自己的距离肯定为1vis[src] = 1;//把源点存入集合中q.push(src); //将源点压入队列while (!q.empty()){int u = q.front(); //取出对首元素q.pop();for (int i = 0; i < g[u].size(); ++i){//这个语句和 Dijstra 中的// dis[k] 中 dis[k] 表示源点到 顶点k的距离(直接到k)//dis[mark] + g[mark][k] 先到当前距离短的点mark,通过这个点转到k//意思是一样的if (dis[u] + g[u][i].second < g[u][i].first){//如果更小就更新g[u][i].first = dis[u] + g[u][i].second;if (!vis[g[u][i].first]) //不在集合中{//就加入vis[g[u][i].first] = 1;q.push(g[u][i].first);}}}//让这个点还有进入队列的机会vis[u] = false;}
}
算法的比较
最短路径(Dijkstra、Bellman-Ford和SPFA算法)相关推荐
- 最短路径:Dijkstra、BellmanFord以及SPFA算法
最短路径问题 1.Dijkstra算法 简介 (1)Dijkstra算法伪代码 (2)C++ 邻接表版代码 (3)优化 (4)题型分析 2.Bellman Ford算法 简介 (1)Bellman算法 ...
- Bellman算法和SPFA算法
Dijkstra算法比较快速,但是如果遇到负边就无能为力了,而Bellman算法可以解决负边问题,只要不是负环. 这个算法数据结构没有讲过,他主要是每次对所以边进行松弛操作,进行n-1次得到最短路径. ...
- c语言bellman算法,求 最短路径中BELLMAN FORD算法实现的C程序
匿名用户 1级 2010-06-01 回答 //这个是邻接表 typedef struct oo { int len,num; struct oo *next; } link; typedef str ...
- 单源最短路径-Dijkstra(迪杰斯特拉算法)
迪杰斯特拉算法时间复杂度为O(n^2),其中n为顶点个数. 该算法用于求单源最短路径.并且图中的边不允许带负权值. #include <iostream> using namespace ...
- 最短路径--Floyd、Dijkstra、Bellman、SPFA算法
前言 最短路径是数据结构-图中的一个经典问题,求解最短路径的问题,有四种算法,这四种算法各有各的不同,分别是: Floyd算法.Dijkstra算法.Bellman算法以及SPFA算法. 最常用的是前 ...
- (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法
一.floyd 1.介绍 floyd算法只有五行代码,代码简单,三个for循环就可以解决问题,所以它的时间复杂度为O(n^3),可以求多源最短路问题. 2.思想: Floyd算法的基本思想如下:从任意 ...
- 【最短路径】:Dijkstra算法、SPFA算法、Bellman-Ford算法和Floyd-Warshall算法
求最短路径最常用的算法有: Dijkstra算法.SPFA算法.Bellman-Ford算法和Floyd-Warshall算法. Dijkstra算法.SPFA算法.Bellman-Ford算法这三个 ...
- 几个最短路径算法Floyd、Dijkstra、Bellman-Ford、SPFA的比较
几大最短路径算法比较 转自:http://blog.csdn.net/v_july_v/article/details/6181485 几个最短路径算法的比较: Floyd 求多 ...
- (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法模板的整理与介绍
这一篇博客以一些OJ上的题目为载体.整理一下最短路径算法.会陆续的更新... 一.多源最短路算法--floyd算法 floyd算法主要用于求随意两点间的最短路径.也成最短最短路径问题. 核心代码: / ...
最新文章
- 2021年9月最新的保姆级计算机视觉学习路线
- 赠书:算法与数据中台“网约车业务实践”
- ROS学习(十一):ROS URDF-model
- WL 2009 professional【已解决】谢谢nooby跟海风
- JDK、TOMCAT、Ant环境变量设置
- AAAI 2020 | NAS+目标检测:AI设计的目标检测模型长啥样?
- 利用自定义分页技术提高数据库性能
- (String) 和 String.valueOf() 两种字符串转换的区别
- Python字典中 get() 函数的使用
- Form中获取数据源及扩展方法中获取变量
- react 生命周期函数
- jieba库(jieba库的介绍以及分词原理,jieba的三种模式和常用函数,利用Jieba模块进行中文词语的统计)
- MYSQL闪退的解决方法
- 使用vlmcsd搭建KMS服务器激活环境
- MongoDB勒索事件中,DBA们到底该学到什么?
- 有软件负载均衡,也有硬件负载均衡,选择哪个?
- # 工欲善其事必先利其器-C语言拓展--嵌入式C语言(九)
- 2021.09.27-10.3 AI行业周刊(第65期):坚持的力量
- 阿里云盘最新邀请码,某度颤抖吧(持续更新中~)
- CorelDRAW矢量绘图2023中文版下载
热门文章
- 查找100 sql oracle,Oracle中SQL语句执行效率的查找与解决
- linux7下安装git,centos7下安装配置git仓库
- 在EXCEL里如何输入X的平方
- torch.roll() 详解
- Java获取List泛型的真实类型
- y2第一章 初始mybatis的上机3_MyBatis3.2.x从入门到精通之第一章
- ostream作为函数返回值_GO语言基础函数
- QT5.11 + VS2017 环境搭建
- 联想电脑的一键换机软件——乐换机
- python与tensorflow的关系_Tensorflow GPU与CPU安装库的区别