求最短路总结

17 篇文章 0 订阅

目录


单源有向无环无负权最短路

单源无负权最短路

单源有负权最短路+判断负环

多源有负权最短路+判断负环


单源有向无环无负权最短路

采用拓扑+贪心求解,按拓扑把入度为0的节点删去,更新与其相连的节点距离源点s的最短距离,因为无负,因此与其直接相连最短的点的一定比绕一个中转点的距离短,三角形两边和大于第三边

(注意,更新的是距离那个点最短的点,而不是任意点!!!)

时间复杂度N+M,最优秀了

//单源有向无环最短路  拓扑 思路:按拓扑把入度为0的节点删去,更新与其相连的节点距离源点s的最短距离
#include<cstdio>
#include<queue>
const int MAX=1e4+10,INF=0x3f3f3f3f;
using namespace std;
int in[MAX];//入度
int ans[MAX];
int n,m,x;
struct node { //链式前向星
	int to,next,w;//u表示当前点到to的距离
} edge[MAX*MAX];
queue <int> q;
int head[MAX];
int dis[MAX],pre[MAX];//dis表示i到这个s的最短路径 ,pre记录前驱结点
void shortest(int s) { //s为源点
	for(int i=0; i<=n; i++) {
		dis[i]=INF;
		pre[i]=i;
	}
	dis[s]=0;//s是出发点 入度为0
	q.push(s);
	while(!q.empty()) {//时间复杂度N+M,while是N的节点,for循环总共M条边
		int u=q.front();
		q.pop();
		ans[x++]=u;			///记录答案
		for(int i = head[u]; ~i; i=edge[i].next) {		///链式向前星存的图
			int to=edge[i].to;
			in[to]--;			///入度减1
			if(dis[to]>dis[u]+edge[i].w)//更新最短路径 松弛
				dis[to]=dis[u]+edge[i].w,pre[to]=u;//s->to 比s->u->to还远,就直接把s->to距离更新成s->u->to,然后记录前驱是u,记录前驱可以还原最短路
			if(in[to]==0) {
				q.push(to);			///入度为0压入队列
			}
		}
	}
}
int main() {
	scanf(" %d%d",&n,&m);
	return 0;
}

单源无负权最短路

Dijkstra

原理详解

模板 

时间复杂度logn(n+m)

#include<cstdio>//优化后外层循环为n,内层循环1为优先队列push的logn,2一共为边数m乘以push的logn ,时间复杂度由n^2降为logn(n+m)
#include<iostream>//数组从1开始而不是0 
#include<cstring>
#include<queue>
using namespace std;
const int INF=0x3f3f3f3f,N=1e5+10;
struct node {
	int  to,next,w;
	friend bool operator < (const node &f1, const node &f2) {
		return f1.w > f2.w;
	}
} edge[N];
int n,m,cnt,s,t;
int head[N],dis[N];
bool vis[N];
priority_queue<node> Q;
inline void addedge(int from, int to, int w) {
	edge[cnt].to = to;
	edge[cnt].next = head[from];
	edge[cnt].w = w;
	head[from] = cnt++;
}
inline void init() {
	cnt = 0;
	memset(head, -1, sizeof(head));
	memset(edge, 0, sizeof(edge));
	memset(dis, 0x3f, sizeof(dis));
}
void dijkstra(int s,int t) { //s为源点 t为终点 
	node z;
	z.to=s,z.next=0,z.w=0;//我们真正需要的是他的to当起点,这里把源点赋予to扔队列里启动算法 
	Q.push(z);
	dis[s] = 0;
	while (Q.size()) {
		z = Q.top();
		Q.pop();
//		if(vis[z.to])	continue;//无向图的话请注释掉 因为这是为了剪枝,而无向图不能这么剪枝 
//		vis[z.to]=true;//无向图的话请注释掉 
		int start = z.to;
		for (int i = head[start]; ~i; i = edge[i].next) {
			int end = edge[i].to;
			if (dis[end] > dis[start] + edge[i].w) {
				dis[end] = dis[start] + edge[i].w;
				Q.push(edge[i]);
			}
		}
	}
	printf("%d\n", dis[t]);
}
int main() {
	while(~scanf(" %d%d%d%d",&n,&m,&s,&t)) {
		init();
		for (int i = 0; i < m; i++) {
			int from, to, w ;
			scanf(" %d%d%d", &from, &to, &w);
			addedge(from, to, w);
			addedge(to, from, w);//无向图的话请加上 
		}
		dijkstra(s,t);
	}
	return 0;
}

单源有负权最短路+判断负环

采用SPFA求解

原理

时间复杂度KM,K为节点平均入队次数

模板

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
	int to,next,w;
} edge[M];
int cnt,head[N];
int cir[N];//如果cir[i]>=n,存在负环
void init() {
	cnt=0;
	memset(head,-1,sizeof(head));
	for(int i=1; i<=n; i++) {
		vis[i]=0;
		dis[i]=INF;
		cir[i]=0;
	}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
	edge[cnt].to=to;   // 该边的终点
	edge[cnt].w=w;    // 权值
	edge[cnt].next=head[from]; //  (指向head[from]后,head[from]又指向了自己)
	head[from]=cnt++;  // 以from结点为起点的边在edge数组中存储的下标
}
bool SPFA(int s) {//s为源点
	init();//初始化涉及参数n,所以必须放在输入n之后!!!!
	queue<int>Q;
	Q.push(s);// 源点进队
	cir[s]++;
	vis[s]=1;
	dis[s]=0;
	while(!Q.empty()) {
		int start=Q.front();
		Q.pop();
		vis[start]=0;  // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
		for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
			int w=edge[i].w;
			int end=edge[i].to;
			if(dis[end]>dis[start]+w) { // 松弛操作
				dis[end]=dis[start]+w;
				if(!vis[end]) { // 末端点不在队列
					Q.push(end); //加入队列
					vis[end]=1;
					cir[end]++;
					if(cir[end]>=n)	return true;//存在负环
				}
			}
		}
	}
	return false;
}
int main() {
	int F;
	scanf("%d",&F);
	while(F--) {
		int q;
		scanf(" %d%d",&n,&m);
		for(int i=1; i<=m; i++) {
			int from,to,w;
			scanf(" %d%d%d",&from,&to,&w);
			addEdge(from,to,w);
		}
		bool t=SPFA(1);
		if(t)	printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

多源有负权最短路+判断负环

采用floyd求解

原理

时间复杂度N^3

模板

bool floyd() {
	for(int k=1; k<=n; k++) //k必须放前面,否则比如说k=3、4,i=1,j=2,那就只有1->3->2,1->4->2,而1->3->4->2丢了
		//f[i][k](k-1)+f[k][j](k-1)<=f[i][j](k),括号表示中转点你要丢掉k-1这一维度并保证无后效性,必须把k-1下所有情况都考虑到,所以k必须放到最外层
		for(int i=1; i<=n; i++) //先丢掉哪一维度,哪一维度套外面
			for(int j=1; j<=n; j++)
				if(dis[i][k]!=INF && dis[k][j]!=INF && dis[i][k]+dis[k][j]<dis[i][j]) {
					dis[i][j]=dis[i][k]+dis[k][j];//dp找到更短路径
				}
	for(int i=1;i<=n;i++)	if(dis[i][i]<0)	return 1;//有负环 
	return 0;//无负环
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值