首页 > 编程语言 >图的最短路径算法(SPFA,Dijkstra,Bellman_Ford)(迪杰斯特拉算法,Spfa算法,贝尔曼-福特算法)(代码注释+例题)(C/C++)

图的最短路径算法(SPFA,Dijkstra,Bellman_Ford)(迪杰斯特拉算法,Spfa算法,贝尔曼-福特算法)(代码注释+例题)(C/C++)

时间:2024-07-25 09:00:11浏览次数:18  
标签:输出 ch int 算法 贝尔曼 例题 号点 dis

目录

Dijkstra迪杰斯特拉算法

写法

时间复杂度

例题

描述

输入描述

输出描述

样例

输入用例

输出用例

写法

Spfa算法

例题

描述

输入描述

输出描述

样例

输入用例

输出用例

写法

Bellman_Ford算法(贝尔曼-福特算法)

写法

例题

描述

输入描述

输出描述

样例

输入样例

输出样例

写法


Dijkstra迪杰斯特拉算法

迪杰斯特拉算法(Dijkstra)是由迪杰斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。

缺点:在负权图中无法使用(SPFA,Bellman_Ford皆可)

写法

1. 利用邻接表记录下边与边的关系;

2. 建立优先队列,以距离出发点的距离为判断依据;

3.初始化全部为0x3f3f3f3f;

4. BFS,求出最长路径

时间复杂度

朴素写法:O(n^2)

优化写法:O((n+m) logm)

例题

描述

给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值(边权小于10000)。
请你求出1号点到n号点的最短距离。如果无法从1号点走到n号点,则输出-1。

输入描述

第一行包含整数n和m(n<=1e5,m<=2e5)。
接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。

输出描述

输出一个整数,表示1号点到n号点的最短距离
如果路径不存在,则输出-1。

样例

输入用例
3 3
1 2 2
2 3 1
1 3 4
输出用例
3

写法

本题如果用朴素的dijkstra算法,按O(n^2)算的话,1e5*1e5一定会超时,故用优化算法

上优化后算法

#include<bits/stdc++.h>
using namespace std;
#pragma GCC s
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC fast
//优化程序运行效率 
void read(int &x) {
	x=0;
	int flag=1;
	char ch=getchar();
	for(; ch<'0'||ch>'9';) {
		if(ch=='-') flag=-1;
		ch=getchar();
	}
	for(; ch>='0'&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
	x=x*flag;
}//快读进行常数优化 
const int N=1e5+5;
int dis[N],n,m;
int h[N],nx[N*2],v[N*2],w[N*2],tot;
bool vis[N];
//n*2原型:n*(n-1)=m,n为节点数,m为一个图的最大边数
priority_queue <pair <int,int> ,vector<pair <int,int> >,greater<pair <int,int> > > q;
/*
	优先队列大根堆写法:priority_queue < pair<int,int> > q; 
	优先队列小根堆写法:priority_queue <pair <int,int> ,vector<pair <int,int> >,greater<pair <int,int> > > q;
*/
void add(int x,int y,int z) {
	tot++;
	v[tot]=y;
	w[tot]=z;
	nx[tot]=h[x];
	h[x]=tot;
}//邻接表记录图的关系
void dijkstra(int x) {
	memset(dis,0x3f,sizeof dis);
    /*
	    1. 首先,memset()是按内存地址命名的,故用0x3f;
	    2. memset()中的0x3f赋完值后,dis[]中的数字是为0x3f3f3f3f,而int的最大值为0x7f7f7f7f,
	    为了防止两个标记的数组值相加导致爆int,故对半开为0x3f3f3f3f 
	*/ 
	dis[x]=0;//出发点到出发点的距离是0 
	q.push({0,x});//放入队列
	while(!q.empty()) {//队列为空时代表遍利完成 
		pair <int,int> tmp;
		tmp=q.top();//取得队列的第一位 
		q.pop();//弹出队列的第一位 
		vis[tmp.second]=1;//vis标记,防止二次入队,降低时间复杂度 
		for(int i=h[tmp.second]; i; i=nx[i]) {
			if(vis[v[i]]==0/*vis标记检查(==0代表没入队),防止二次入队,降低时间复杂度 */&&dis[v[i]]>dis[tmp.second]+w[i]) {
				dis[v[i]]=dis[tmp.second]+w[i];
				q.push({dis[v[i]],v[i]});//入队 
			}
		}
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
    //优化程序 
	read(n);
	read(m);
	for(int i=1; i<=m; i++) {
		int x;
		int y;
		int z;
		read(x);
		read(y);
		read(z);
		add(x,y,z);
	}
	dijkstra(1);//进行遍历
	if(dis[n]==0x3f3f3f3f) cout<<-1;//如果没有被访问证明不联通,故输出-1 
	else cout<<dis[n];
	return 0;
}

Spfa算法

SPFA就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数),所以此时利用数组记录节点访问可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决是放弃数组,此时所需时间自然就是指数的,所以我们不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。

缺点:在菊花图(下图)中,时间复杂度飘升至O(mn)(m为边数,n为点数)与Bellman_Ford的时间复杂度相同,也不能无法解决限制变数的问题(Bellman_Ford 可以)

不过在正常情况下时间复杂度为O(km) (在稀疏图中一般小于等于2)

例题

描述

给定一个n个点m条边(n<=1e5,m<=2e5)的有向图,图中可能存在重边和自环,边权绝对值小于104。数据保证图中不存在负权回路。

请你求出1号点到n号点的最短距离。如果无法从1号点走到n号点,则输出-1。

输入描述

第一行包含整数n和m(n<=1e5,m<=2e5)。

接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。

输出描述

输出一个整数,表示1号点到n号点的最短距离
如果路径不存在,则输出-1。

样例

输入用例
3 3
1 2 1
2 3 2
1 3 1
输出用例
1

写法

#include<bits/stdc++.h>
using namespace std;
#pragma GCC s
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC fast
//优化程序运行效率 
void read(int &x) {
	x=0;
	int flag=1;
	char ch=getchar();
	for(; ch<'0'||ch>'9';) {
		if(ch=='-') flag=-1;
		ch=getchar();
	}
	for(; ch>='0'&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
	x=x*flag;
}//快读进行常数优化 
const int N=1e5+5;
int dis[N],n,m;
int h[N],nx[N*2],v[N*2],w[N*2],tot;
bool vis[N];
//n*2原型:n*(n-1)=m,n为节点数,m为一个图的最大边数
queue<int>q;
void add(int x,int y,int z) {
	tot++;
	v[tot]=y;
	w[tot]=z;
	nx[tot]=h[x];
	h[x]=tot;
}//邻接表记录图的关系 
void spfa(int x) {
	memset(dis,0x3f,sizeof dis);
	/*
	1. 首先,memset()是按内存地址命名的,故用0x3f;
	2. memset()中的0x3f赋完值后,dis[]中的数字是为0x3f3f3f3f,而int的最大值为0x7f7f7f7f,
	为了防止两个标记的数组值相加导致爆int,故对半开为0x3f3f3f3f 
	*/ 
	dis[x]=0;//出发点到出发点的距离是0 
	q.push(x);//放入队列 
	vis[x]=1;//vis标记,防止二次入队,降低时间复杂度
	while(!q.empty()) {
		int tmp=q.front();//取得队列的第一位 
		q.pop();//弹出队列的第一位 
		vis[tmp]=0;//vis出队标记
		for(int i=h[tmp];i; i=nx[i]) {
			if(dis[v[i]]>dis[tmp]+w[i]) {
				dis[v[i]]=dis[tmp]+w[i];
				if(vis[v[i]]==0) {//vis标记检查(==0代表没入队),防止二次入队,降低时间复杂度 
					vis[v[i]]=1;//vis入队标记
					q.push({v[i]});//入队 
				}
			}
		}
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	//优化程序 
	read(n);
	read(m);
	for(int i=1; i<=m; i++) {
		int x;
		int y;
		int z;
		read(x);
		read(y);
		read(z);
		add(x,y,z);
	}
	spfa(1);
	if(dis[n]==0x3f3f3f3f) cout<<-1;//如果没有被访问证明不联通,故输出-1 
	else cout<<dis[n];
	return 0;
}

Bellman_Ford算法(贝尔曼-福特算法)

遇到有限制边数的题可采用bellman_ford,从起点开始,每一次循环只更新一次,更新出新的最小值。

时间复杂度为O(nm)。

写法

如果只使用一个dis数组进行存储的话 ,在面对下图的时候,会更新多个节点,这不满足我们的需要。

而为了解决此问题,我们建立了一个dis_old数组进行存储,将dis数组第i轮的操作后得到的值,完整复制到dis_old数组,用此数组和dis数组进行处理,就可以避免一次循环更新多个节点的问题了。

下图为dis数组和dis_old数组用上图样例进行更新时候的过程。

——12345
tmp
dis0
————————————
tmp0
dis01
————————————
tmp01
dis013
————————————
tmp013
dis0136
————————————
tmp0136
dis013610
————————————

例题

描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。

输入描述

第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x
到点 y 的有向边,边长为 z。

点的编号为 1∼n。

输出描述

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible。

样例

输入样例
3 3 1
1 2 1
2 3 1
1 3 3
输出样例
3

写法

#include<bits/stdc++.h>
using namespace std;
#pragma GCC s
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC fast
//优化程序
void read(int &x) {
	x=0;
	int flag=1;
	char ch=getchar();
	for(; ch<'0'||ch>'9';) {
		if(ch=='-') flag=-1;
		ch=getchar();
	}
	for(; ch>='0'&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
	x=x*flag;
}//快读进行常数优化 
int n,m,k,dis[505],tmp[505];//俩数组,用来记录和复制 
struct node {
	int x;
	int y;
	int z;
} t[10005];//记录边的两顶点,边权 
void bellman_ford(int x) {
	memset(dis,0x3f,sizeof(dis));
	/*
		优先队列大根堆写法:priority_queue < pair<int,int> > q; 
		优先队列小根堆写法:priority_queue <pair <int,int> ,vector<pair <int,int> >,greater<pair <int,int> > > q;
	*/
	dis[x]=0;//出发点到出发点的距离是0
	for(int i=0; i<k; i++) {//模拟边数 
		memcpy(tmp,dis,sizeof(tmp));//复制数组 
		for(int j=1; j<=m; j++) {//枚举边 
			int x=t[j].x;
			int y=t[j].y;
			int w=t[j].z;
			dis[y]=min(dis[y],tmp[x]+w);//找最小值 
		}
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	//优化程序 
	read(n);
	read(m);
	read(k);
	for(int i=1; i<=m; i++) {
		read(t[i].x);
		read(t[i].y);
		read(t[i].z);
	}
	bellman_ford(1);
	if(dis[n]>=0x3f3f3f3f/2) cout<<"impossible";
	/*
		由于负边权的存在初始化的值可能被减小
		但题目中负边权的值一般不会小于 -5e8,故用 dis[n]>=0x3f3f3f3f/2
	*/
	else cout<<dis[n];
	return 0;
}

标签:输出,ch,int,算法,贝尔曼,例题,号点,dis
From: https://blog.csdn.net/he10101/article/details/140671247

相关文章

  • 基于微信小程序+协同过滤推荐算法+SpringBoot+数据可视化的校园顺路代送平台设计和实
    博主介绍:✌全网粉丝50W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、P......
  • Day 22 回溯算法 part01
    77.组合我的这个解法还算挺简单易懂的,还是看注释classSolution{List<List<Integer>>ans=newArrayList();//存储最终结果集合List<Integer>tmp=newArrayList();//记录单次的pathpublicList<List<Integer>>combine(intn,intk){backTr......
  • C#实现MergeSort算法
    publicclassMergeSortLearn{///<summary>///分治递归///</summary>///<paramname="oriArray"></param>///<returns></returns>publicstaticdouble[]MergeSort(double[]oriArray){......
  • 《算法竞赛进阶指南》0x05排序
    在程序设计中通常会用到以下排序:1.选择排序、插入排序、冒泡排序2.堆排序、归并排序、快速排序3.计数排序、基数排序、桶排序前两类排序时基于比较的排序,第一类排序的时间复杂度为O(......
  • 机器学习 | 回归算法原理——多项式回归
    Hi,大家好,我是半亩花海。接着上次的最速下降法(梯度下降法)继续更新《白话机器学习的数学》这本书的学习笔记,在此分享多项式回归这一回归算法原理。本章的回归算法原理基于《基于广告费预测点击量》项目,欢迎大家交流学习!目录一、多项式回归概述二、案例分析1.设置问题2.......
  • 关于珞石机器人二次开发SDK的posture函数的算法RX RY RZ纠正 C#
    在珞石SDK二次开发的函数钟,获取当前机器人位姿的函数posture函数在输出时会发现数据不正确,与示教器数据不一致。其中第一个数据正确第二三各数据为相反第四五六各数据为弧度制转换方法为(弧度/PI)*180度然后发现第四个数据还要加上180度第五六各数据要取反,,所以设计了以下......
  • 七大基于比较的排序算法
    目录一、基于比较的排序算法概述1.插入排序(InsertionSort)2.选择排序(SelectionSort)3.冒泡排序(BubbleSort)4.归并排序(MergeSort)5.快速排序(QuickSort)6.堆排序(HeapSort)7.希尔排序(ShellSort)二、排序算法的性能分析三、Java中的常用排序方法 在计算机科......
  • 算法介绍(一):LLCNN低光照
    对于一张灰度图片,像素值越大则亮度越高,像素值越小则亮度越低在数字图像处理领域有一种很简单的图像亮度调整算法——伽马变换伽马变换是一种用于调整图像亮度和对比度的非线性操作,其基本公式为(I'=I^\gamma),其中(I')是输出图像的灰度值,(I)是输入图像的灰度值,而(\g......
  • java中的一些经典算法code
    //1.importjava.util.LinkedList;importjava.util.Queue;publicclassCandyGame{//定义一个点的类,用于记录位置和当前累计的糖果数量staticclassPoint{intx,y,steps,candies;Point(intx,inty,intsteps,intcandies){......
  • 改进的灰狼优化算法(GWO)(附完整matlab代码)
    1.灰狼优化算法灰狼优化算法作为一种高效率群体智能优化算法,其结构简单,收敛速度快,调节参数少,在时间序列预测,机械设计,结构设计,聚类分析等工程问题上都有着十分广泛的应用。但是在应用过程中发现,其存在种群多样性差,后期收敛速度缓慢,容易陷入局部最优以及局部搜索和全局搜索不均......