“它死了…”——王队长
最短路算法有很多很多种,我所知的有三种:
1.DIJ
2.SPFA
3.Bellman-ford
由于第三种算法的复杂度是 O(nm) O ( n m ) ,且实现过于简单,可以用上限也是 O(nm) O ( n m ) 的SPFA替换,所以就不提了。
这篇博客主要是简略分析最短路算法的时间以及小用法。
朴素SPFA:
没什么好讲的。
SPFA判负环:
BFS:
一个点进队超过n次则有负环。
DFS:
初始化dis为0。
若一个点重复走了两次,则有负环。
实测DFS快很多。
优化1:
循环队列:
针对空间限制小,时间限制大的最短路题目。
一般用不上,你能用上你也TLE了。
优化2(来自JBY09年集训队论文):
连续增广。
若x更新y,且y不在队列里,要把y加入队列。
把y和当前的队列头对比一下,若y的dis更小,则把y提前。
优化3(来自JBY09年集训队论文):
随机调整边的顺序。
然而这个并没有什么卵用。
因为如果我的数据就是类随机造的,你再怎么随机也没用。
优化4:
受JBY启发,我想了一个随机算法。
随机队列中的一个点,把它于当前队列头比较,若它的dis更小,则交换。
实测效果良好。
优化5:
为什么要随机啊,直接堆不行吗?
然后你发现它就变成类DIJ了。
假优化:
递归版SPFA。
慎用!可以卡到 O(2n) O ( 2 n ) ,这个得看图而定了。
“稳定!稳定!稳定!我们ACMer追求的是稳!”
这是GDOI复评时凡喵对我说的(不听老人言,吃亏在后来)。
如何造数据卡掉SPFA:
显然把整个图拉长,这样的话每次让你白更新,你就变成 O(nm) O ( n m ) 的了。
比如说每个点只往序号±10的点连边。
Code:
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int rand(int x, int y) {
return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}
int n, m, c;
void write(int x) {
if(x == 0) {putchar('0'); return;}
int d[10]; d[0] = 0;
for(; x; x /= 10) d[++ d[0]] = x % 10;
for(; d[0]; d[0] --) putchar(d[d[0]] + '0');
}
void put(int x, int y, int z) {
write(x); putchar(' '); write(y); putchar(' '); write(z); putchar('\n');
}
int main() {
srand((unsigned) time (0));
freopen("a.in", "w", stdout);
n = 40000; m = 1000000;
printf("%d %d\n", n, m);
fo(i, 1, n - 1) put(i, i + 1, rand(1, 2e9));
fo(i, 1, m - (n - 1)) {
int x = rand(1, n);
int y = rand(max(1, x - 10), min(n, x + 10));
put(x, y, rand(1, 2e9 / n));
}
}
对比:
代码放最后。
朴素SPFA:上天。
连续增广SPFA:4s
随机换点SPFA:0.8s
set优化DIJ:0.7s
Code(朴素):
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int rand(int x, int y) {
return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}
const int N = 2e6 + 5;
int n, m, x, y, z;
int final[N], to[N], next[N], v[N], tot;
void link(int x, int y, int z) {
next[++ tot] = final[x], to[tot] = y, v[tot] = z, final[x] = tot;
next[++ tot] = final[y], to[tot] = x, v[tot] = z, final[y] = tot;
}
void read(int &x) {
char c = ' ';
while(c < '0' || c > '9') c = getchar();
x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}
int dis[N], bz[N], d[N * 10];
int main() {
srand((unsigned) time (0));
freopen("a.in", "r", stdin);
scanf("%d %d", &n, &m);
fo(i, 1, m) {
read(x); read(y); read(z);
link(x, y, z);
}
fo(i, 1, n) dis[i] = 2e9;
dis[1] = 0; bz[1] = 1; d[d[0] = 1] = 1;
fo(i, 1, d[0]) {
int x = d[i];
for(int j = final[x]; j; j = next[j]) {
int y = to[j], z = v[j];
if(dis[x] + z < dis[y]) {
dis[y] = dis[x] + z;
if(!bz[y]) {
d[++ d[0]] = y, bz[y] = 1;
}
}
}
bz[x] = 0;
}
}
Code(连续增广):
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int rand(int x, int y) {
return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}
const int N = 2e6 + 5;
int n, m, x, y, z;
int final[N], to[N], next[N], v[N], tot;
void link(int x, int y, int z) {
next[++ tot] = final[x], to[tot] = y, v[tot] = z, final[x] = tot;
next[++ tot] = final[y], to[tot] = x, v[tot] = z, final[y] = tot;
}
void read(int &x) {
char c = ' ';
while(c < '0' || c > '9') c = getchar();
x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}
int dis[N], bz[N], d[N * 10];
int main() {
srand((unsigned) time (0));
freopen("a.in", "r", stdin);
scanf("%d %d", &n, &m);
fo(i, 1, m) {
read(x); read(y); read(z);
link(x, y, z);
}
fo(i, 1, n) dis[i] = 2e9;
dis[1] = 0; bz[1] = 1; d[d[0] = 1] = 1;
fo(i, 1, d[0]) {
int x = d[i];
for(int j = final[x]; j; j = next[j]) {
int y = to[j], z = v[j];
if(dis[x] + z < dis[y]) {
dis[y] = dis[x] + z;
if(!bz[y]) {
d[++ d[0]] = y, bz[y] = 1;
if(dis[y] < dis[d[i + 1]]) swap(d[i + 1], d[d[0]]);
}
}
}
bz[x] = 0;
}
}
Code (随机换点):
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int rand(int x, int y) {
return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}
const int N = 2e6 + 5;
int n, m, x, y, z;
int final[N], to[N], next[N], v[N], tot;
void link(int x, int y, int z) {
next[++ tot] = final[x], to[tot] = y, v[tot] = z, final[x] = tot;
next[++ tot] = final[y], to[tot] = x, v[tot] = z, final[y] = tot;
}
void read(int &x) {
char c = ' ';
while(c < '0' || c > '9') c = getchar();
x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}
int dis[N], bz[N], d[N * 10];
int main() {
srand((unsigned) time (0));
freopen("a.in", "r", stdin);
scanf("%d %d", &n, &m);
fo(i, 1, m) {
read(x); read(y); read(z);
link(x, y, z);
}
fo(i, 1, n) dis[i] = 2e9;
dis[1] = 0; bz[1] = 1; d[d[0] = 1] = 1;
fo(i, 1, d[0]) {
int num = rand(i, d[0]);
if(dis[d[num]] < dis[d[i]]) swap(d[i], d[num]);
int x = d[i];
for(int j = final[x]; j; j = next[j]) {
int y = to[j], z = v[j];
if(dis[x] + z < dis[y]) {
dis[y] = dis[x] + z;
if(!bz[y]) {
d[++ d[0]] = y, bz[y] = 1;
}
}
}
bz[x] = 0;
}
}
Code(set优化DIJ):
#include<set>
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define P pair<int, int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
int rand(int x, int y) {
return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}
const int N = 2e6 + 5;
int n, m, x, y, z;
int final[N], to[N], next[N], v[N], tot;
void link(int x, int y, int z) {
next[++ tot] = final[x], to[tot] = y, v[tot] = z, final[x] = tot;
next[++ tot] = final[y], to[tot] = x, v[tot] = z, final[y] = tot;
}
void read(int &x) {
char c = ' ';
while(c < '0' || c > '9') c = getchar();
x = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}
int dis[N], bz[N];
multiset<P> s;
int main() {
srand((unsigned) time (0));
freopen("a.in", "r", stdin);
scanf("%d %d", &n, &m);
fo(i, 1, m) {
read(x); read(y); read(z);
link(x, y, z);
}
memset(bz, 0, sizeof bz);
fo(i, 1, n) dis[i] = 2e9; dis[1] = 0;
fo(i, 1, n) s.insert(mp(dis[i], i));
fo(i, 1, n) {
P z = *s.begin(); s.erase(s.begin());
int x = z.se;
for(int j = final[x]; j; j = next[j]) {
int y = to[j], z = v[j];
if(dis[x] + z < dis[y]) {
s.erase(s.find(mp(dis[y], y)));
dis[y] = dis[x] + z;
s.insert(mp(dis[y], y));
}
}
}
}