【代码超详解】洛谷 P3371 P4779 单源最短路径(Dijkstra 算法 · 模板题)
程序员文章站
2022-05-20 20:02:52
...
一、题目描述
P3371 【模板】单源最短路径(弱化版)
题目背景
本题测试数据为随机数据,在考试中可能会出现构造数据让SPFA不通过,如有需要请移步 P4779。
题目描述
如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
输入格式
第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。
接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。
输出格式
一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)
输入输出样例
输入 #1
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出 #1
0 2 4 3
说明/提示
时空限制:1000ms,128M
数据规模:
对于20%的数据:N<=5,M<=15;
对于40%的数据:N<=100,M<=10000;
对于70%的数据:N<=1000,M<=100000;
对于100%的数据:N<=10000,M<=500000。保证数据随机。
对于真正 100% 的数据,请移步 P4779。请注意,该题与本题数据范围略有不同。
样例说明:
图片1到3和1到4的文字位置调换
P4779 【模板】单源最短路径(标准版)
二、算法分析说明与代码编写指导
Dijkstra算法
1、用途:求连通图的定点到其余点的最短距离。
2、条件限制:要求不存在负边。
3、算法内容:
设带权连通图G(V, E),边权代表边长(直接相连两点的通路长度),定点(源点)s,数组d[|V|]代表定点s到各点的通路的估计最短距离。
记d[s] = 0,d的其余元素记为一个较大值M,设集合U代表已经估计最短距离的顶点集合,
也可以用一个bitset来标记顶点是否已被估计过最短距离。
【1】首先找出定点s的邻接点中通路距离最短的点x,并记d[x] = |sx|,将定点s先加入集合U。
【2】对点x,对该点的任意邻接点y,更新d[y] = min(d[y], d[x] + weight(x, y)) = min(|sy|, |sx| + |xy|)。
更新完毕后,将x加入集合U。这一步也叫做“松弛”。
【3】对未加入集合U的其它顶点,选出d值最小的顶点x’,更新其任意邻接点y’:
d[y’] = min(d[y’], d[x’] + weight(x’, y’)) = min(|sy’|, |sx’| + |x’y’|),
将x’放入集合U。直到U = V(|U| = |V|)。此时d[|V|]代表定点s到各点的通路的最短距离。
容易发现,步骤【1】【2】是需要重复执行的步骤【3】的特殊情况,选出来的点x恰好是s的邻接点,且通路最短。
由于每次将新的顶点加入集合U之后,都要查找下一个未估计最短距离的顶点,因此总的时间复杂度按O(|V|2)估计。
证明:/
4、算法优化:
下面通过priority_queue将复杂度降低至O(|E| log |V|)。
设带权连通图G(V, E),边权代表边长(直接相连两点的通路长度),定点(源点)s,数组d[|V|]代表定点s到各点的通路的估计最短距离。
用一个edge结构体表示邻接关系和边(至少含有终点和权两个成员),并额外标记顶点是否已被估计d值。
设优先队列q,用于保存顶点和对应的d值(可以构造一个有序对来存储距离和对应的顶点)。记d[s] = 0,d的其余元素记为一个较大值M。
【1】将定点s压入优先队列q。
【2】循环:取出q的队首x,如果已经被估计过,则continue(跳到循环开头继续)。
否则,对队首的顶点的全部邻接点,更新d[y] = min(d[y], d[x] + weight(x, y)) = min(|sy|, |sx| + |xy|),
并将该邻接点压入优先队列q(为了在全部顶点中取得d最小的顶点)。队列空时,循环结束。
证明:/
一个具有|V|个顶点的图G(V, E)最多可以含有|V|(|V|-1)≈|V|2条边(无重边时)。
而|V|较大时,O(|E| log |V|)比O(|V|2)的复杂度高,此时不应该进行堆优化。
对这样的稠密图,直接采用邻接矩阵存图,套用O(|V|2)的Dijkstra算法即可。
5、补充说明
特殊情形:当所有边的边权相等,可以直接采用BFS求得最短路。
当图不是连通图时,选定的定点无法到达的点的距离保持M未更新。
当图存在负边时,算法会给出错误结果。
三、AC 代码
P3371:
#include<cstdio>
#include<algorithm>
#include<queue>
#include<bitset>
#include<map>
#pragma warning(disable:4996)
using namespace std;
const unsigned M = 2147483647;
unsigned n, m, s, u, x, d[10001];
struct edge { unsigned to, dis; };
vector<edge> G[10001]; bitset<10001> visited; edge e; pair<unsigned, unsigned> p;
priority_queue<pair<unsigned, unsigned>, vector<pair<unsigned, unsigned>>, greater<pair<unsigned, unsigned>>> q;
int main() {
scanf("%u%u%u", &n, &m, &s);
for (unsigned i = 0; i < m; ++i) {
scanf("%u%u%u", &u, &e.to, &e.dis);
G[u].emplace_back(e);
}
fill(d + 1, d + n + 1, M); d[s] = 0; p.first = d[s]; visited.reset(); p.second = s; q.emplace(p);
while (q.empty() == false) {
x = q.top().second; q.pop();
if (visited[x])continue;
visited[x] = true;
for (unsigned long long y = 0; y < G[x].size(); ++y) {
e = G[x][y];
if (d[e.to] > d[x] + e.dis) {
d[e.to] = d[x] + e.dis; p.first = d[e.to]; p.second = e.to; q.emplace(p);
}
}
}
for (unsigned i = 1; i <= n; ++i)printf("%u ", d[i]);
//system("pause");
return 0;
}
P4779:
#include<cstdio>
#include<algorithm>
#include<queue>
#include<bitset>
#include<map>
#pragma warning(disable:4996)
using namespace std;
const unsigned M = 2147483647;
unsigned n, m, s, u, x, d[100001];
struct edge { unsigned to, dis; };
vector<edge> G[100001]; bitset<100001> visited; edge e; pair<unsigned, unsigned> p;
priority_queue<pair<unsigned, unsigned>, vector<pair<unsigned, unsigned>>, greater<pair<unsigned, unsigned>>> q;
int main() {
scanf("%u%u%u", &n, &m, &s);
for (unsigned i = 0; i < m; ++i) {
scanf("%u%u%u", &u, &e.to, &e.dis);
G[u].emplace_back(e);
}
fill(d + 1, d + n + 1, M); d[s] = 0; p.first = d[s]; visited.reset(); p.second = s; q.emplace(p);
while (q.empty() == false) {
x = q.top().second; q.pop();
if (visited[x])continue;
visited[x] = true;
for (unsigned long long y = 0; y < G[x].size(); ++y) {
e = G[x][y];
if (d[e.to] > d[x] + e.dis) {
d[e.to] = d[x] + e.dis; p.first = d[e.to]; p.second = e.to; q.emplace(p);
}
}
}
for (unsigned i = 1; i <= n; ++i)printf("%u ", d[i]);
//system("pause");
return 0;
}