最短路径(Dijkstra、Bellman-Ford和SPFA算法)

  • 前言
  • 图的存储方式
    • 邻接矩阵
    • 邻接表
      • 链表建立
      • 利用vector
      • 结构体
  • 核心思路
  • Dijkstra算法
    • 图解
    • 基本思想
    • 求解步骤
    • 细节解释
      • 变量的意义
      • 初始化
      • 迭代次数
      • 更新条件
    • 完整代码
  • Bellman-Ford算法
    • 思路:
    • 变量说明
    • 初始化
    • 完整代码
  • Bellman-Ford 检测图是否含有负圈(回路)
    • 思路
    • 代码
  • Bellman-Ford 算法优化
  • SPFA算法
    • 思路
    • 完整代码
  • 算法的比较

前言

提醒一下:最短路径一般用于有向网(就是有方向和权值的图),而最小生成树一般是用于无向网。
本来是想分开写,不过想到这样大家看起来比较麻烦。就写在一起了,这样可以很好的进行对比。

图的存储方式

常用的无非就

  1. 邻接矩阵
  2. 邻接表
  3. 链式前向星

邻接矩阵

这个可以说是最好理解的了,分为以下几种情况

  • 无权值得图,初始化为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

  1. 选择顶点D
    S={D(0)}
    U={A(∞), B(∞), C(3), E(4), F(∞), G(∞)}
  2. 选取顶点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就表明就加入到了源点所在的集合中)。然后遍历不在源点集合的点,找到距离源点最近的点。将这个点加入到源点所在的集合中。然后以这个点为中转点更新源点到其余点的距离。

求解步骤

  1. 将有向网用邻接矩阵储存,没有相连的边用一个较大的数表示。
  2. 找到距离源点最近的点,将其加入到源点所在的集合中。
  3. 以这个点为中转点,更新源点到其余点的距离。

细节解释

变量的意义

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算法)相关推荐

  1. 最短路径:Dijkstra、BellmanFord以及SPFA算法

    最短路径问题 1.Dijkstra算法 简介 (1)Dijkstra算法伪代码 (2)C++ 邻接表版代码 (3)优化 (4)题型分析 2.Bellman Ford算法 简介 (1)Bellman算法 ...

  2. Bellman算法和SPFA算法

    Dijkstra算法比较快速,但是如果遇到负边就无能为力了,而Bellman算法可以解决负边问题,只要不是负环. 这个算法数据结构没有讲过,他主要是每次对所以边进行松弛操作,进行n-1次得到最短路径. ...

  3. c语言bellman算法,求 最短路径中BELLMAN FORD算法实现的C程序

    匿名用户 1级 2010-06-01 回答 //这个是邻接表 typedef struct oo { int len,num; struct oo *next; } link; typedef str ...

  4. 单源最短路径-Dijkstra(迪杰斯特拉算法)

    迪杰斯特拉算法时间复杂度为O(n^2),其中n为顶点个数. 该算法用于求单源最短路径.并且图中的边不允许带负权值. #include <iostream> using namespace ...

  5. 最短路径--Floyd、Dijkstra、Bellman、SPFA算法

    前言 最短路径是数据结构-图中的一个经典问题,求解最短路径的问题,有四种算法,这四种算法各有各的不同,分别是: Floyd算法.Dijkstra算法.Bellman算法以及SPFA算法. 最常用的是前 ...

  6. (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法

    一.floyd 1.介绍 floyd算法只有五行代码,代码简单,三个for循环就可以解决问题,所以它的时间复杂度为O(n^3),可以求多源最短路问题. 2.思想: Floyd算法的基本思想如下:从任意 ...

  7. 【最短路径】:Dijkstra算法、SPFA算法、Bellman-Ford算法和Floyd-Warshall算法

    求最短路径最常用的算法有: Dijkstra算法.SPFA算法.Bellman-Ford算法和Floyd-Warshall算法. Dijkstra算法.SPFA算法.Bellman-Ford算法这三个 ...

  8. 几个最短路径算法Floyd、Dijkstra、Bellman-Ford、SPFA的比较

        几大最短路径算法比较 转自:http://blog.csdn.net/v_july_v/article/details/6181485 几个最短路径算法的比较: Floyd        求多 ...

  9. (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法模板的整理与介绍

    这一篇博客以一些OJ上的题目为载体.整理一下最短路径算法.会陆续的更新... 一.多源最短路算法--floyd算法 floyd算法主要用于求随意两点间的最短路径.也成最短最短路径问题. 核心代码: / ...

最新文章

  1. 2021年9月最新的保姆级计算机视觉学习路线
  2. 赠书:算法与数据中台“网约车业务实践”
  3. ROS学习(十一):ROS URDF-model
  4. WL 2009 professional【已解决】谢谢nooby跟海风
  5. JDK、TOMCAT、Ant环境变量设置
  6. AAAI 2020 | NAS+目标检测:AI设计的目标检测模型长啥样?
  7. 利用自定义分页技术提高数据库性能
  8. (String) 和 String.valueOf() 两种字符串转换的区别
  9. Python字典中 get() 函数的使用
  10. Form中获取数据源及扩展方法中获取变量
  11. react 生命周期函数
  12. jieba库(jieba库的介绍以及分词原理,jieba的三种模式和常用函数,利用Jieba模块进行中文词语的统计)
  13. MYSQL闪退的解决方法
  14. 使用vlmcsd搭建KMS服务器激活环境
  15. MongoDB勒索事件中,DBA们到底该学到什么?
  16. 有软件负载均衡,也有硬件负载均衡,选择哪个?
  17. # 工欲善其事必先利其器-C语言拓展--嵌入式C语言(九)
  18. 2021.09.27-10.3 AI行业周刊(第65期):坚持的力量
  19. 阿里云盘最新邀请码,某度颤抖吧(持续更新中~)
  20. CorelDRAW矢量绘图2023中文版下载

热门文章

  1. 查找100 sql oracle,Oracle中SQL语句执行效率的查找与解决
  2. linux7下安装git,centos7下安装配置git仓库
  3. 在EXCEL里如何输入X的平方
  4. torch.roll() 详解
  5. Java获取List泛型的真实类型
  6. y2第一章 初始mybatis的上机3_MyBatis3.2.x从入门到精通之第一章
  7. ostream作为函数返回值_GO语言基础函数
  8. QT5.11 + VS2017 环境搭建
  9. 联想电脑的一键换机软件——乐换机
  10. python与tensorflow的关系_Tensorflow GPU与CPU安装库的区别