【C++】最短路径模板题题答

Floyd算法

题目

HihoCoder-1089
vjudge

提示

小Ho道:“你说的很有道理,我只需要从每个节点开始使用Dijstra算法就可以了!”

小Hi摇摇头道:“解决问题不是关键,学到知识才是关键,而且知识本身也远远没有掌握学习的方法重要!”

小Ho只得答道:“好的好的,听你说便是了!”

于是小Hi便开心道:“这次要说的算法叫做Floyd算法,是一种用于求图结构上任意两点间最短距离的算法!”

小Ho嘀咕道:“你都写标题上了,能不知道么?”

小Hi强行装作没听到,继续说道:“这个算法的核心之处在于数学归纳法——MinDistance(i, j)之间最短路径中可以用到的节点是一点点增加的!”

“你这话每一个字我都听得懂,但是这句话为什么我听不懂呢……”小Ho无奈道。

“那我这么说吧,首先,最开始的时候,MinDistance(i, j)——即从第i个点到第j个点的最短路径的长度,拥有一个限制:这条路径不能经过任何节点。”小Hi道。

“那就是说如果从i个点到第j个点之间没有直接相连的边的话,这个长度就是无穷大咯?”小Ho总结道:“只需要把输入的边填进MinDistance中即可!”

“对!”小Hi满意于小Ho的上道,继续说道:“然后我放开限制,我允许MinDistance(i, j)——从第i个点到第j个点的最短路径的长度,拥有的限制,变为:这条路径仅允许经过1号节点。”

“这个也简单,对于两个节点i, j,我只需要比较MinDistance(i, j)原来的值和MinDistance(i, 1)+MinDistance(1, j)的值,取较小的一个作为新的MinDistance(i, j)就可以了——毕竟原来的MinDistance都是不经过任何节点,那么这样求出来的新的MinDistance(i, j)只有可能经过1号节点。”

“那么接下来就是关键的了,我将限制继续放宽——路径仅允许经过1、2号节点。”小Hi继续说道。

“那其实也没有任何变化吧,对于两个节点i, j,我只需要比较MinDistance(i, j)原来的值和MinDistance(i, 2)+MinDistance(2, j)的值,取较小的一个作为新的MinDistance(i, j),之所以可以这样是因为,原来的MinDistance都是在限制“仅允许经过1号节点”下,求出来的,所以新求出来的MinDistance(i, j)也只有可能经过1、2号节点!“

“那我继续放开限制呢?”小Hi问道。

“也没有什么区别了,每放开一个新的节点k允许作为路径中的节点,就对于任意的i, j,用MinDistance(i, k)+MinDistance(k, j)去更新MinDistance(i, j),直到1…N号节点都被添加进限制,此时也就等于没有限制了,那么这个时候的MinDistance(i, j)就是我们所想要求的值,写成伪代码就是这样!”

for k = 1 .. N
    for i = 1 .. N 
        for j = 1 .. N
            若i, j, k各不相同
                MinDistance[i, j] = min{
    
    MinDistance[i, j], MinDistance[i, k] + MinDistance[k, j]}

“看来你已经很明白了呢!”小Hi嘿嘿一笑,往鬼屋深处跑了去——那么接下来就是小Ho利用求出的最短路径,去找到小Hi的时候了!

代码

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

const int N=1005;
const int inf=0x3f3f3f;

int n,m;
int f[N][N];

int main() {
    
    
	scanf("%d%d",&n,&m);
	memset(f,inf,sizeof(f));
	for(int i=1; i<=m; i++) {
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		f[u][v]=MIN(f[u][v],w);
		f[v][u]=MIN(f[v][u],w);
	}
	for(int k=1; k<=n; k++) 
		for(int i=1; i<=n; i++) 
			for(int j=1; j<=n; j++) 
				f[i][j]=MIN(f[i][j],f[i][k]+f[k][j]);
	for(int i=1; i<=n; i++) {
    
    
		for(int j=1; j<=n; j++) 
			printf("%d ",(i==j) ? 0 : f[i][j]);
		printf("\n");
	}
	return 0;
}

Dijkstra算法

题目

HihoCoder-1089
vjudge

提示

小Ho想了想说道:“唔……我觉得动态规划可以做,但是我找不到计算的顺序,如果我用f[i]表示从S到达编号为i的节点的最短距离的话,我并不能够知道f[1]…f[N]的计算顺序。”

“所以这个问题不需要那么复杂的算法啦,我就稍微讲讲你就知道了!”小Hi道:“路的长度不可能为负数对不对?”

“那是自然,毕竟人类还没有发明时光机器……”小Ho点点头。

于是小Hi问道:“那么如果就看与S相邻的所有节点中与S最近的那一个S’,并且从S到S’的距离为L,那么有可能存在另外的道路使得从S到S’的距离小于L么?”

“不能,因为S’是与S相邻的所有节点中与S最近的节点,那么从S到其他相邻点的距离一定是不小于L的,也就是说无论接下来怎么走,回到L点时总距离一定大于L。”小Ho思考了一会,道。

“也就是说你已经知道了从S到S’的最短路径了是么?”小Hi继续问道。

“是的,这条最短路径的长度是L。”小Ho答道。

小Hi继续道:“那么现在,我们不妨将S同S’看做一个新的节点?称作S1,然后我就计算与S相邻或者与S’相邻的所有节点中,与S最近的哪一个节点S’’。注意,在这个过程中,与S相邻的节点与S的距离在上一步就已经求出来了,那么我要求的只有与S’相邻的那些节点与S的距离——这个距离等于S与S’的距离加上S’与这些结点的距离,对于其中重复的节点——同时与S和S’相邻的节点,取两条路径中的较小值。”

小Ho点了点头:“那么同之前一样,与S1(即S与S’节点)相邻的节点中与S’距离最近的节点如果是S’‘的话,并且这个距离是L2,那么我们可以知道S到S’'的最短路径的长度便是L2,因为不可能存在另外的道路比这个更短了。”

于是小Hi总结道:“接下来的问题不就很简单了么,只需要以此类推,每次将与当前集合相邻(即与当前集合中任意一个元素)的所有节点中离S最近的节点(这些距离可以通过上一次的计算结果推导而出)选出来添加到当前集合中,我就能够保证在每一个节点被添加到集合中时所计算的离S的距离是它与S之间的最短路径!”

“原来是这样!但是我的肚子更饿了呢!”言罢,小Ho的肚子咕咕叫了起来。

代码

Dijkstra:

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

const int N=1005;
const int inf=0x3f3f3f;

int n,m,s,t;
int d[N],vis[N];
int a[N][N];

void dijkstra() {
    
    
	memset(vis,0,sizeof(vis));
	for(int i=1; i<=n; i++) d[i]=inf;
	d[s]=0;
	for(int i=1; i<=n; i++) {
    
    
		int k=-1;
		for(int j=1; j<=n; j++)
			if(!vis[j] && (k==-1 || d[k]>d[j])) k=j;
		vis[k]=1;
		for(int j=1; j<=n; j++)
			if(!vis[j] && d[k]+a[k][j]<d[j])
				d[j]=d[k]+a[k][j];
	}
}

int main() {
    
    
	scanf("%d%d%d%d",&n,&m,&s,&t);
	memset(a,inf,sizeof(a));
	for(int i=1; i<=m; i++) {
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u][v]=MIN(a[u][v],w);
		a[v][u]=MIN(a[v][u],w);
	}
	dijkstra();
	printf("%d\n",d[t]==inf ? -1 : d[t]);
	return 0;
}

堆优化Dijkstra:

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

const int N=1005;
const int M=10005;
const int inf=0x3f3f3f;

struct Edge {
    
    
	int to,nt,w;
} e[M<<1];

struct Node {
    
    
	int index,dist;
	
	bool operator < (const Node &rhs) const {
    
    
		return dist>rhs.dist;
	}
};

int n,m,s,t,cnt;
int d[N],vis[N],h[N];

priority_queue<Node> q;

inline void add(int u,int v,int w) {
    
    
	e[++cnt].to=v;
	e[cnt].nt=h[u];
	e[cnt].w=w;
	h[u]=cnt;
}

void dijkstra() {
    
    
	memset(vis,0,sizeof(vis));
	for(int i=1; i<=n; i++) d[i]=inf;
	d[s]=0;
	q.push((Node){
    
    s,0});
	while(!q.empty()) {
    
    
		Node x=q.top();
		q.pop();
		int u=x.index;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=h[u]; i; i=e[i].nt) {
    
    
			int v=e[i].to;
			if(d[v]>d[u]+e[i].w) {
    
    
				d[v]=d[u]+e[i].w;
				q.push((Node){
    
    v,d[v]});
			}
		}
	}
}

int main() {
    
    
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1; i<=m; i++) {
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	dijkstra();
	printf("%d\n",d[t]==inf ? -1 : d[t]);
	return 0;
}

Spfa算法

题目

HihoCoder-1089

vjudge

提示

“唔……地点很多,道路很少,这个鬼屋是一个稀疏图,既然这一点被特地标注出来,那么想来有其作用的咯?”小Ho道。

“是的,正好有一种最短路径算法,它的时间复杂度只和边的条数有关,所以特别适合用来解决这种边的数量很少的最短路问题!”小Hi点了点头道:“它就是SPFA算法,即Shortest Path Faster Algorithm。”

“听上去很厉害的样子,但是实际上怎么做的呢?”小Ho问道。

“你会用宽度优先搜索写这道题么?”小Hi反问道。

“这个当然会啊,构造一个队列,最开始队列里只有(S, 0)——表示当前处于点S,从点S到达该点的距离为0,然后每次从队首取出一个节点(i, L)——表示当前处于点i,从点S到达该点的距离为L,接下来遍历所有从这个节点出发的边(i, j, l)——表示i和j之间有一条长度为l的边,将(j, L+l)加入到队尾,最后看所有遍历的(T, X)节点中X的最小值就是答案咯~”小Ho对于搜索已经是熟稔于心,张口便道。

“SPFA算法呢,其实某种意义上就是宽度优先搜索的优化——如果你在尝试将(p, q)加入到队尾的时候,发现队列中已经存在一个(p, q’)了,那么你就可以比较q和q’:如果q>=q’,那么(p, q)这个节点实际上是没有继续搜索下去的必要的——算是一种最优化剪枝吧。而如果q&ltq’,那么(p, q’)也是没有必要继续搜索下去的——但是它已经存在于队列里了怎么办呢?很简单,将队列中的(p, q’)改成(p, q)就可以了!”

“那我该怎么知道队列中是不是存在一个(p, q’)呢?” <额,维护一个position[1…n]的数组就可以了,如果不在队列里就是-1,否则就是所在的位置!”< p>
“所以说这本质上就是宽度优先搜索的剪枝咯?”小Ho问道。

小Hi笑道:“怎么可能!SPFA算法其实是BELLMAN-FORD算法的一种优化版本,只不过在成型之后可以被理解成为宽度优先搜索的!这个问题,我们会在之后好好讲一讲的!”

代码1

用vector实现。

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

const int N=1e5+5;
const int inf=1e9;

int n,m,s,t;
int v[N],d[N];

vector<int> a[N],b[N];
queue<int> q;

int spfa() {
    
    
	for(int i=1; i<=n; i++) d[i]=inf;
	q.push(s);
	v[s]=1;
	d[s]=0;
	while(!q.empty()) {
    
    
		int x=q.front();
		q.pop();
		v[x]=0;
		for(int i=0; i<a[x].size(); i++) {
    
    
			int tp=a[x][i];
			if(d[tp]>d[x]+b[x][i]) {
    
    
				d[tp]=d[x]+b[x][i];
				if(!v[tp]) {
    
    
					q.push(tp);
					v[tp]=1;
				}
			}
		}
	}
	if(d[t]==inf) d[t]=-1;
	return d[t];
}

int main() {
    
    
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1; i<=m; i++) {
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u].push_back(v);
		b[u].push_back(w);
		a[v].push_back(u);
		b[v].push_back(w);
	}
	printf("%d\n",spfa());
	return 0;
}

代码2

用链式前向星实现。

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>

#define RI                 register int
#define re(i,a,b)          for(RI i=a; i<=b; i++)
#define ms(i,a)            memset(a,i,sizeof(a))
#define MAX(a,b)           (((a)>(b)) ? (a):(b))
#define MIN(a,b)           (((a)<(b)) ? (a):(b))

using namespace std;

typedef long long LL;

const int N=1e5+5;
const int M=1e6+5;
const int inf=1e9;

struct Edge {
    
    
	int to,nt,w;
} e[M<<1];

int n,m,s,t,cnt;
int v[N],d[N],h[N];

queue<int> q;

inline void add(int a,int b,int c) {
    
    
	e[++cnt]=(Edge){
    
    b,h[a],c};
	h[a]=cnt;
}

int spfa() {
    
    
	for(int i=1; i<=n; i++) d[i]=inf;
	q.push(s);
	v[s]=1;
	d[s]=0;
	while(!q.empty()) {
    
    
		int x=q.front();
		q.pop();
		v[x]=0;
		for(int i=h[x]; i; i=e[i].nt) {
    
    
			int tp=e[i].to;
			if(d[tp]>d[x]+e[i].w) {
    
    
				d[tp]=d[x]+e[i].w;
				if(!v[tp]) {
    
    
					q.push(tp);
					v[tp]=1;
				}
			}
		}
	}
	if(d[t]==inf) d[t]=-1;
	return d[t];
}

int main() {
    
    
	scanf("%d%d%d%d",&n,&m,&s,&t);
	memset(h,-1,sizeof(h)); 
	for(int i=1; i<=m; i++) {
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	printf("%d\n",spfa());
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ljnoit/article/details/106153630