算法基础篇:常见图论最短路算法(Bellman-Ford→SPFA→Dijkstra Floyd-Warshall )入门以及代码解析
来源:互联网 发布:暗黑西游记淘宝专区 编辑:程序博客网 时间:2024/05/23 01:23
单源最短路
单源最短路的意思就是一个起点,算出到其他点的最短路,这里介绍三种算法,bellman-ford,spfa和dijkstra
BF算法
算法思想
BF是bellman_ford的简称,算法的想法非常简单,进行|V|−1次操作,每次操作对所有的边松弛。
松弛可以形象的理解为更新值,比如有一条从u到v的边,如果u上的值加上边的长度小于v上的值,那么就更新v上的值。
为什么这样做是对的呢?我们可以回顾整个过程,第一次操作,我们一定能将起点出去的点中的某一个点更新为最小值(这个点以后不会被更新),关于这点可以用反证法证明,如果没有一个点是更新完毕的,那么从起点到这个点必然存在一条比起点到这个点的边更短的路径,与假设矛盾。故而每次我们至少能确定一个点,所以总共需要|V|−1次(起点不用更新)。
最开始的图,0是起点
初始化
第一次松弛更新了1,2,3三个点
最后全部更新好了
#include<iostream>#include<queue>#include<vector>using namespace std;const int M = 1001;const int Maxn = 1000001;int d[M],V,n;//V为最大点的数值(也就是总点数-1,因为还包括0),n为有向边的总条数struct edge {int to;int cost;edge() {};//默认构造函数+重设构造函数赋值edge(int a,int b) {to = a;cost = b;}//相当于edge(int tt,int cc):to(tt),cost(cc){}};vector<edge>G[M];void Bellman_Ford(int s) {fill(d , d +M, Maxn);//将s到各个点的距离初始化(为寻找最短路,若一条边不存在,默认距离为无穷大)d[s] = 0;//s到s自身的距离为0for (int i = 0; i < V - 1; i++) { //总共最多需要更新V-1次(有V个点)for (int u = 0; u < V; u++) {for (int j = 0; j < G[u].size(); j++) {//G[u]里面存放的就是所有以u为起点的有向边edge(to,cost)int u_to_distance = G[u][j].cost;//u_to_distance为以u为起点的这条边的长度int nextpoint = G[u][j].to;//nextpoint为以u为起点的边的终点if (u_to_distance + d[u] < d[nextpoint]) {d[nextpoint] = u_to_distance + d[u];}//if目标点s→nextpoint(不经过u)的距离大于s→u→nextpoint的距离,//那么更新s→nextpoint(不经过u)的距离为s→u→nextpoint的距离(因为更短)}}}}int main() {int a, b, c;//设为每条边的起点、终点和长度while (~scanf("%d%d", &V,&n)) {//<span style="font-family: Arial, Helvetica, sans-serif;">V为最大点的数值(也就是总点数-1,因为还包括0),n为有向边的总条数</span>for (int i = 0; i < n; i++) {scanf("%d%d%d", &a, &b, &c);G[a].push_back(edge(b, c));}Bellman_Ford(1);//这个起始点可以修改测试for (int i =0; i <=V; i++)cout << d[i] << " ";}system("pasue");}
复杂度分析
复杂度很明显是O(V∗E),在完全图的时候,会变成
接下来谈一下SPFA算法:
SPFA算法又叫做bellman-ford队列优化,至于为啥叫SPFA我也不知道。
算法思想
纵观整个BF算法,我们可以用宽度优先搜索来代替V-1次的暴力松弛,每次用队列头部的元素去更新其他点,如果将一个点更新成功,就将这个点加入队列,直到更新不动为止。
可以证明,这样做和BF本质上是一样的。但这样做有个好处,这个好处就在于我们可以记录一个点是否在队列中,如果这个点在队列中,那么我们知道这个点一定会被拿出来松弛,那么当这个点被更新的时候,我们就不用讲他加入队列了。如此,就大大改进了BF算法。
SPFA代码就不重新打了,因为可以直接在Bellman-Ford算法的基础上加上队列的运用,但是可以得到很大大的效率优化,代码及注释如下:
#include<iostream>#include<queue>#include<vector>using namespace std;const int M = 1001;const int Maxn = 1000001;int d[M],V,n;queue<int>pp;//声明一个队列放入被更新的点,下一次更新就以这些点为基础对其它跟这个点有关联的边进行更新
//这里先不用优先队列,到后面更加优化的Dijkstra算法才用bool inqueue[M];//用于判断队列中是否已经存在这个点struct edge {int to;int cost;edge() {};//默认构造函数+重设构造函数赋值edge(int a,int b) {to = a;cost = b;}//相当于edge(int tt,int cc):to(tt),cost(cc){}};vector<edge>G[M];void SPFA(int s) {while (!pp.empty()) {pp.pop();}//初始化队列,将里面的数据清空(规范)fill(d , d +M, Maxn);//将s到各个点的距离初始化(为寻找最短路,若一条边不存在,默认距离为无穷大)d[s] = 0;//s到s自身的距离为0pp.push(s);//此时队列中只有一个点,就是起始点sinqueue[s] = 1;while (!pp.empty()) { //总共最多需要更新V-1次(有V个点)int u = pp.front();pp.pop(); //每次拿出一个点,用初始点s到改点的距离更新与该点有关联的其它边的距离,同时把该点移除队列inqueue[u] = 0;//去标记for (int j = 0; j < G[u].size(); j++) {//G[u]里面存放的就是所有以u为起点的有向边edge(to,cost)int u_to_distance = G[u][j].cost;//u_to_distance为以u为起点的这条边的长度int nextpoint = G[u][j].to;//nextpoint为以u为起点的边的终点if (u_to_distance + d[u] < d[nextpoint]) {d[nextpoint] = u_to_distance + d[u];if (!inqueue[nextpoint]) {//没有在队列里面,可以放进去pp.push(nextpoint);inqueue[nextpoint] = 1;}}//if目标点s→nextpoint(不经过u)的距离大于s→u→nextpoint的距离,//那么更新s→nextpoint(不经过u)的距离为s→u→nextpoint的距离(因为更短)}}}int main() {int a, b, c;//设为每条边的起点、终点和长度while (~scanf("%d%d", &V,&n)) {for (int i = 0; i < n; i++) {scanf("%d%d%d", &a, &b, &c);G[a].push_back(edge(b, c));}SPFA(1);for (int i =0; i <=V; i++)cout << d[i] << " ";}system("pasue");}
算法复杂度
这个算法的复杂度是个迷,据传是O(k∗E),其中
接下来再谈一下 Dijkstra算法
算法思想
我们依旧用宽度优先搜索的角度思考BF算法,我们发现,如果我们每次都将最小的值从队列中拿出,那么拿出来的一定是已经更新好了的(同样可以用反证法证明),所以只要我们将队列变成堆,就能快速处理最短路了。
还是刚刚那个图,最开始将0号节点加入堆
现在从堆中拿出最小的数0,用他更新1,2,3,并把1,2,3加入堆
现在堆里面最小的数是2号节点,说明2号节点已经更新好了,那么用2号节点去更新其他节点
最短的都更新好了
Dijkstra的代码要多敲几次加深理解,有几点需要注意一下,看一下注释:
#include<iostream>#include<queue>#include<vector>using namespace std;const int M = 1001;const int Maxn = 1000001;int d[M],V,n;//V为最大点的数值(因为还包括0,所以总点数是V+1),n为有向边的总条数struct edge {int to;int cost;edge() {};//默认构造函数+重设构造函数赋值edge(int a,int b) {to = a;cost = b;}//相当于edge(int tt,int cc):to(tt),cost(cc){}};struct node {int point;int d; //node是用来储存 (更新完的点point+s到point的距离d)node() {};node(int a, int b) {point = a;d = b;}bool operator<(const node &a) const{return d > a.d;//(比较运算符的重载,是优先队列原本系统默认的从大到小的排列顺序}//变成从小到大的排序,方便直接用nn.top()直接取队首的元素,也就是最小的};vector<edge>G[M];priority_queue<node>nn;//声明一个名为nn的优先队列void Dijkstra(int s) {fill(d , d +M, Maxn);//将s到各个点的距离初始化(为寻找最短路,若一条边不存在,默认距离为无穷大)d[s] = 0;//s到s自身的距离为0while (!nn.empty())nn.pop();nn.push(node(s, 0));//同样,将初始数据放进优先队列while(!nn.empty()){node Next = nn.top();nn.pop();int u = Next.point;int dd = Next.d;if (d[u] < dd)continue;for (int j = 0; j < G[u].size(); j++) {//G[u]里面存放的就是所有以u为起点的有向边edge(to,cost)int u_to_distance = G[u][j].cost;//u_to_distance为以u为起点的这条边的长度int nextpoint = G[u][j].to;//nextpoint为以u为起点的边的终点if (u_to_distance + d[u] < d[nextpoint]) {d[nextpoint] = u_to_distance + d[u];nn.push(node(nextpoint, d[nextpoint]));//把更新完的点和距离放进队列里}//if目标点s→nextpoint(不经过u)的距离大于s→u→nextpoint的距离,//那么更新s→nextpoint(不经过u)的距离为s→u→nextpoint的距离(因为更短)}}}int main() {int a, b, c;//设为每条边的起点、终点和长度while (~scanf("%d%d", &V,&n)) {for (int i = 0; i < n; i++) {scanf("%d%d%d", &a, &b, &c);G[a].push_back(edge(b, c));}Dijkstra(1);//这个起始点可以修改测试for (int i =0; i <=V; i++)cout << d[i] << " ";}system("pasue");}
算法复杂度
由于我们至少将所有边遍历一次,所以我们需要O(E)的时间,堆优化使得我们在搜索时将一个
需要注意的一点是,在图中存在负边的情况下,Dijkstra算法无法正确求解问题,还是需要使用Bellman-Ford和它的优化SPFA算法。
接下来是 多源最短路 Floyd-Warshall算法,
由于这个算法比较简单,在此处就直接引用源码了,不再由自己敲了,注释应该不需要,理解一下就好
如果我们想知道任意两个点之间的最短路,这时候可以暴力跑n次单源最短路,也可以用floyd算法
Floyd算法
这个算法的特点就是非常好写
算法思想
Floyd算法运用了动态规划的思想,对于一个最短路径(u,v)而言,我们一定是从u到了k,再从k到v。所以我们只要知道了(u,k)和(k,v)的最短路,那么我们就能得到(u,v)的最短路。
所以暴力枚举就好
算法分析
这个算法呢?是用来求任意两点间的最短路问题。可以试着用DP来求解任意两点间的最短路问题。只使用顶点0~k和i,j的情况下,记i到j的最短路长度为d[k+1][i][j]。k=-1时,认为只使用i和j,所以d[0][i][j]=cost[i][j]。接下来让我们把只使用顶点0~k的问题归约到只使用0~k-1的问题上。
只使用0~k时,我们分i到j的最短路正好经过顶点k一次和完全不经过顶点k两种情况来讨论。不经过顶点k的情况下,d[k][i][j]=d[k-1][i][j]。通过顶点k的情况下,d[k][i][j]=d[k-1][i][k]+d[k-1][k][j]。合起来就得到了d[k][i][j]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j])。这个DP也可以使用同一个数组,不断进行d[i][j]=min(d[i][j],d[i][k]+d[k][j])的更新来实现。
算法实现
1234567891011121314151617181920212223242526272829
#include <iostream>#include <cstring>#include <algorithm>#define MAX_V 102#define INF 1008611using namespace std;int d[MAX_V][MAX_V];int V,E;int main(){cin>>V>>E;for(int i=1;i<=V;i++)for(int j=1;j<=V;j++)d[i][j]=(i==j?0:INF);while(E--){int u,v,c;cin>>u>>v>>c;d[u][v]=c;}for(int k=1;k<=V;k++)for(int i=1;i<=V;i++)for(int j=1;j<=V;j++)d[i][j]=min(d[i][k]+d[k][j],d[i][j]);for(int i=1;i<=V;i++)for(int j=1;j<=V;j++)cout<<i<<" "<<j<<" "<<d[i][j]<<endl;return 0;}
算法复杂度
很明显,复杂度是
- 算法基础篇:常见图论最短路算法(Bellman-Ford→SPFA→Dijkstra Floyd-Warshall )入门以及代码解析
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- 带权最短路 Dijkstra、SPFA、Bellman-Ford、ASP、Floyd-Warshall 算法分析
- 模板--Floyd Dijkstra Bellman-Ford spfa 四种最短路经典算法
- 【最短路径】:Dijkstra算法、SPFA算法、Bellman-Ford算法和Floyd-Warshall算法
- Dijkstra、Bellman-ford、SPFA、Floyd算法
- 最短路算法 Dijkstra Bellman-Ford SPFA
- hdu-2544-最短路-(bellman-ford、dijkstra、floyd、SPFA算法)
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法 详解
- 最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法详解&BFS
- 几大最短路径算法比较(Floyd & Dijkstra & Bellman-Ford & SPFA)
- 四种最短路径算法(Dijkstra,Floyd,Bellman-ford&&spfa)
- 最短路算法(Floyd、Dijsktra、Bellman-Ford、SPFA)
- 单片机之STM32 adc Regular injected 意思和区别
- Git之创建版本库
- SpringMVC集成Tiles布局引擎框架
- A mini simplest cross platform socket wrapper APIs, support win32 & linux & ios & android & wp8 & wp
- AC自动机
- 算法基础篇:常见图论最短路算法(Bellman-Ford→SPFA→Dijkstra Floyd-Warshall )入门以及代码解析
- 程序员面试宝典--8.2递归(2)
- 【引用】C#读写app.config中的数据
- C#打印时,自定义纸张
- C#命名规范
- C#窗口中的控件都看不见了
- Struts2中通过validation.xml验证数字,后台报错
- jsp request 对象详解
- DataGridView调整行的高度