洛谷【模板】单源最短路径

在这里插入图片描述
具体题目见洛谷 P3371

方法一:Bellman–Ford

思路:从ij点最多经过n-1条边,因此对这n-1条边进行遍历,对于每条边找能更新的,即每次对当前的m条边进行松弛

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
int n, m, s;
int u[maxn], v[maxn], w[maxn];//u,v,w分别记录一条边的起点、终点和权值
int d[maxn];//距离数组,维护起点到每个点的距离
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) cin >> u[i] >> v[i] >> w[i];
	
	//初始化
	fill(d, d + maxn, inf);
	d[s] = 0;

	for (int i = 1; i <= n - 1; ++i) {
		int flag = 0;//若某次遍历没有更新数组d,则后面也不会更新d(因为是用当前的d更新d的其他部分),直接break
		for (int j = 1; j <= m; ++j)
			if (d[v[j]] > d[u[j]] + w[j]) {
				flag = 1;
				d[v[j]] = d[u[j]] + w[j];
			}
		if (!flag) break;
	}
	//输出
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

方法二:SPFA(Shortest Path Faster Algorithm)

1. 朴素的 SPFA

思路:对 Bellman–Ford 进行改进,方法一搜寻边时过于盲目。只有在上一次数组d改变的点才会造成下一次d的改变,因此使用队列仅将队首相邻的结点入队列,防止盲目搜索。

SPFA 与 BFS 十分相似,区别是 SPFA 可以重复入队列,在出队列后要将inq数组置0,表明可以重新入队列。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
vector<pair<int, int> >E[maxn];//存每条边的边集,first为边终点,second为边权
int n, m, s;
int d[maxn];
bool vis[maxn];
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		E[u].push_back({v, w});
	}
	//初始化
	fill(d, d + maxn, inf);
	queue<int>q;
	q.push(s), d[s] = 0, vis[s] = 1;
	
	while (!q.empty()) {
		int now = q.front(); q.pop(); vis[now] = 0;//可重复入队列
		for (int i = 0; i < E[now].size(); ++i) {
			int v = E[now][i].first;//边终点
			if (d[v] > d[now] + E[now][i].second) {//需要松弛
				d[v] = d[now] + E[now][i].second;
				if (vis[v]) continue;//已在队列则不入队
				vis[v] = 1; q.push(v);
			}
		}
	}
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

2. SPFA(SLF优化)

思路:使用 SLF(Small Label First) 对 SPFA 进行优化。将队列改为双端队列,对要加入队列的点now,如果d[now]<d[q.front()],则将其插入到队头,否则插入到队尾。

代码与 SPFA 相同,仅在第 30 行有改动

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
vector<pair<int, int> >E[maxn];//存每条边的边集,first为边终点,second为边权
int n, m, s;
int d[maxn];
bool vis[maxn];
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		E[u].push_back({v, w});
	}
	//初始化
	fill(d, d + maxn, inf);
	deque<int>q;
	q.push_back(s), d[s] = 0, vis[s] = 1;

	while (!q.empty()) {
		int now = q.front(); q.pop_front(); vis[now] = 0;//可重复入队列
		for (int i = 0; i < E[now].size(); ++i) {
			int v = E[now][i].first;//边终点
			if (d[v] > d[now] + E[now][i].second) {//需要松弛
				d[v] = d[now] + E[now][i].second;
				if (vis[v]) continue;//已在队列则不入队
				vis[v] = 1;

				//SLF优化:若当前结点距离<队首距离,将当前结点放在队首
				if (!q.empty() && d[v] < d[q.front()]) q.push_front(v);
				else q.push_back(v);
			}
		}
	}
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

3.SPFA(LLL优化)

思路:使用 LLL(Large Label Last) 对 SPFA 进行优化。对每个要出队的队首元素u,比较d[u] 和队列中点的d的平均值,如果 d[u] 更大,将其弹出放到队尾,再取队首元素进行相同操作,直到队首元素的 d 平均值。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
vector<pair<int, int> >E[maxn];//存每条边的边集,first为边终点,second为边权
int n, m, s;
int d[maxn];
bool vis[maxn];
int sum;//队列中点的d数组之和
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		E[u].push_back({v, w});
	}
	//初始化
	fill(d, d + maxn, inf);
	queue<int>q;
	q.push(s), d[s] = 0, vis[s] = 1;
	sum = d[s];

	while (!q.empty()) {
		int now = q.front();

		//LLL优化
		while (q.size() * d[now] > sum) {//队首元素的d高于平均值,将其放到队尾
			q.pop(); q.push(now);
			now = q.front();
		}

		q.pop(); vis[now] = 0;//可重复入队列
		sum -= d[now];
		for (int i = 0; i < E[now].size(); ++i) {
			int v = E[now][i].first;//边终点
			if (d[v] > d[now] + E[now][i].second) {//需要松弛
				d[v] = d[now] + E[now][i].second;
				if (vis[v]) continue;//已在队列则不入队
				vis[v] = 1; q.push(v); sum += d[v];
			}
		}
	}
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

4.SPFA(SFL+LLL优化)

思路:这两种优化方法并不相互干扰,因此可以同时使用

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
vector<pair<int, int> >E[maxn];//存每条边的边集,first为边终点,second为边权
int n, m, s;
int d[maxn];
bool vis[maxn];
int sum;//队列中点的d数组之和
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		E[u].push_back({v, w});
	}
	//初始化
	fill(d, d + maxn, inf);
	deque<int>q;
	q.push_back(s), d[s] = 0, vis[s] = 1;
	sum = d[s];

	while (!q.empty()) {
		int now = q.front();

		//LLL优化
		while (q.size() * d[now] > sum) {//队首元素的d高于平均值,将其放到队尾
			q.pop_front(); q.push_back(now);
			now = q.front();
		}

		q.pop_front(); vis[now] = 0;//可重复入队列
		sum -= d[now];
		for (int i = 0; i < E[now].size(); ++i) {
			int v = E[now][i].first;//边终点
			if (d[v] > d[now] + E[now][i].second) {//需要松弛
				d[v] = d[now] + E[now][i].second;
				if (vis[v]) continue;//已在队列则不入队
				vis[v] = 1; sum += d[v];

				//SLF优化
				if (!q.empty() && d[v] < d[q.front()]) q.push_front(v);
				else q.push_back(v);
			}
		}
	}
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

四种 SPFA 的性能比较:

以洛谷 P3371 【模板】单源最短路径(弱化版)为例比较优化结果。

朴素的 SPFA:
在这里插入图片描述
SLF 优化的 SPFA:
在这里插入图片描述
LLL 优化的 SPFA:
在这里插入图片描述
SLF+LLL 优化的 SPFA:
在这里插入图片描述

方法三:二叉堆(优先队列)优化的 Dijkstra 算法

思路:用优先队列优化 SPFA,不需要vis队列,将队列修改为优先队列。注意 Dijkstra 算法每次遍历找到的都为最短路径,则已经更新过的点无需更新。时间复杂度为 O ( m l o g n ) O(mlogn)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
const int inf = 0x7fffffff;
vector<pair<int, int> >E[maxn];//存每条边的边集,first为边终点,second为边权
int n, m, s;
int d[maxn];
bool vis[maxn];
int main() {
	cin >> n >> m >> s;
	for (int i = 1; i <= m; ++i) {
		int u, v, w; cin >> u >> v >> w;
		E[u].push_back({v, w});
	}
	fill(d, d + maxn, inf);
	priority_queue<pair<int,int> >q;
	d[s] = 0; 
	q.push({-d[s], s});
	while (!q.empty()) {
		int now = q.top().second; q.pop();
		if (vis[now]) continue;
		vis[now] = true;
		for (int i = 0; i < E[now].size(); ++i) {
			int v = E[now][i].first;//边终点
			if (d[v] > d[now] + E[now][i].second) {//需要松弛
				d[v] = d[now] + E[now][i].second;
				q.push({-d[v],v});
			}
		}
	}
	for (int i = 1; i <= n; ++i) cout << d[i] << ' ';
	return 0;
}

方法四:斐波那契堆优化的 Dijkstra 算法

猜你喜欢

转载自blog.csdn.net/weixin_44413191/article/details/106996490