最短路模板:dij,spfa与floyd

图论技能get!
一个超强大的建图网站

最短路问题

1.dij算法

用于单源最短路
仅适用于没有负边权的情况
初始化dis数组为inf,dis【起点】=0;
tool:priority-queue(按dis升序)
先把起点放进队列
每次取出排头now,枚举它能去的地方v;
如果——

dis[v]>dis[now]+p[i].w

说明目前从now走到v更优,就更新它,并入队
最后now出列,并永远不要回来(用人话说就是判重)

模板

void dij(){
	for(int i=1;i<=n;i++) dis[i]=INT_MAX;
	memset(jd,0,sizeof(jd));
	dis[s] = 0;
	priority_queue<pr,vector<pr>,greater<pr> >q;
	q.push(make_pair(0,s));
	int tot=0;
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(jd[now]) continue;
		jd[now]=1;
		for(int i=fi[now];~i;i=p[i].nxt){
			int v=p[i].to;
			//printf("#%d %d %d\n",p[i].nxt,now,v);
			if(dis[v]>dis[now]+p[i].w){
				dis[v]=dis[now]+p[i].w;
				q.push(make_pair(dis[v],v)); 
			}
		}
	}
}

(关于链式前向星存图,请移步这里

证明

因为没有负权
所以当前dis最小的值以后不可能从别的地方再更新
所以每次都取最小的,每个就只需取一次(n)

备注

因为优先队列操作复杂度带个log,所以
总复杂度为:O(nlogn)

从证明也可以看出,dij只适用于正权,那么有负权是怎么办?
可以使用——

2.SPFA

“spfa已经死了”
和dij其实很类似,只是他不用优先队列,也不判重,只要枚举出度满足上面那个关系式就可以进队(当然,已经在的不要再进了)
当队列为空结束

代码

void spfa(double x){
	queue<int>q;
	mem(jd,1);
	for(int i=1;i<=n;i++) dis[i]=(double)inf;
	mem(nm,0);
	for(int i=0;i<m;i++){
		p[i].w -= x;
	}
	for(int i=1;i<=n;i++){
		q.push(i);
	}
	while(!q.empty()){
		int now=q.front();q.pop();
		jd[now]=0;
		for(int i=fi[now];~i;i=p[i].nxt){
			int v=p[i].to;
			if(dis[v]>=dis[now]+p[i].w){
				dis[v]=dis[now]+p[i].w;
				if(jd[v]==0) q.push(v);
			}
		}
	}
	for(int i=0;i<m;i++) p[i].w +=x;
	return;
}

证明

因为不去重,队列还是空了,说明已经没有任何两对满足更新的关系式~~(暴力有什么好证明的)~~

备注

复杂度十分 玄学 不稳定
在特殊构造和稠密图可能卡成O(mn)
直接裂开(2018NOIP血的教训)
所以尽量还是用dij吧

floyd

多源!!!!
代码《过于冗长》,直接看着代码解释吧。。。

代码

for(int k = 1; k <= n; ++k)
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

证明

(想不明白直接背)
dp[i][j][k]是从i到j经过中转编号最大值为k的最短路
那么dp[i][j][k]更新到dp[i][j][k+1]就是看看经过k+1会不会更优呗
所以就是上面那个状态转移方程
最后dp[i][j][n]就是最终的最短路

踩蛋

(这个不是王建国写的)

负环

如果存在一个总权值为负值的环,那么所有能碰到该环的两点的最短路都会是无穷小(大风车吱吖吱悠悠的转
此时spfa就会出现死循环
所以需要特殊判断负环
如果一条路径经过的点大于n,显然它是经过了环
而你算着算着最短路它为啥溜号去跑环了呢?一定是出现了负环
从而进行判断
关于代码实现,我们可以在更新时加一行转移:

nm[v]=nm[now]+1

代码

bool spfa(){
	queue<int>q;
	mem(jd,0);
	for(int i=1;i<=n;i++) dis[i]=inf;
	mem(nm,0);
	q.push(1);
	jd[1]=1;
	dis[1]=0;
	while(!q.empty()){
		int now=q.front();q.pop();
		jd[now]=0;
		for(int i=fi[now];~i;i=p[i].nxt){
			int v=p[i].to;
			if(dis[v]>dis[now]+p[i].w){
				dis[v]=dis[now]+p[i].w;
				nm[v]=nm[now]+1;
				if(jd[v]==0) q.push(v);
				if(nm[v]>=n){
					return true;
				}
			}
		}
	}
	return false;
}

觉得明白了可以来洛谷水道模板

就酱!拜拜~

上一篇:面试需准备什么


下一篇:下载安装jd-gui(Mac)