感谢评论,让我知道了我一直以来写的Dijkstra都是“盗版的”。。。。。。
由于在循环过程中我将if(vis[u]) continue;
改成了if(cur.d!=dis[u]) continue;
导致我的点可以多次入堆,与经典的“只入堆一次”不同。不过这样改之后应该并没有多大的损害,毕竟这么多年我都没发现。但是既然知道了,以后还是写原版比较好。
如果只入堆一次的话,那毫无疑问原版Dijkstra不能处理有负边无负环的情况了。
因为如果一开始更新了dis[v],而后来由于负边的缘故dis[v]减少了,我们想要的情况是优先队列里记录的dis[v]也相应减少,并重新更新周围点。然而事实是v已经被标记过,无法再重新更新周围点。从这里就开始产生错误了。
下面的内容请读者把Dijkstra自行前面脑补出“多次入堆的”。
啊,现实总是出乎意料啊,还是说我读书不认真?
大部分人应该都应该都想过这个问题: Dijkstra 在有负边但没负环的情况下能用吗?
我看了很多回答,试了一些博客举的例子,但用代码检验的结果都是:Dijkstra 是对的。 最终,我也没有找到能说服我的博客。
我发现找博客是有极限的,所以,CSDN,我不找了!
当然,我最终也无法回答。
但我做了个有意思的尝试:对拍。
先亮出结果:
多次对拍并没有差异!
下面说出我具体的做法。
目录
1. 代码展示
2. 结果展示
3. 没用的总结
1. 代码展示
下面就放出我的代码啦!各位看官(如果有的话)尽可以检验。
Dijkstra 代码
经过 luogu 考验的!
/*
据说只能在无负边的时候使用 \
此时优于 SPFA \
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define INF (1e9+7)
const int N=1e5+10, M=2e5+10;
struct Edge { int to, next, w; }e[M];
struct Node
{
int v, d;
bool operator<(const Node & a) const{ return d>a.d; }
};
priority_queue<Node> Q;
int n, m, s;
int d[N], head[N];
void add(int u, int v, int w)
{
static int en=0;
e[++en]=(Edge){v,head[u],w};
head[u]=en;
return;
}
void Dijkstra(int s)
{
while(!Q.empty()) Q.pop();
fill(d,d+n+1,INF);
d[s]=0;
Q.push((Node){s,0});
while(!Q.empty())
{
Node cur=Q.top(); Q.pop();
int v=cur.v;
if(cur.d!=d[v]) continue;
for(int h=head[v], vv; h; h=e[h].next)
{
vv=e[h].to;
if(d[vv]>d[v]+e[h].w)
{
d[vv]=d[v]+e[h].w;
Q.push((Node){vv,d[vv]});
}
}
}
return;
}
int main()
{
freopen("data.txt","r",stdin);
freopen("Dijkstra.txt","w",stdout);
scanf("%d%d%d", &n, &m, &s);
for(int i=0, u, v, w; i<m; ++i)
{
scanf("%d%d%d", &u, &v, &w);
add(u,v,w);
}
Dijkstra(s);
for(int i=1; i<=n; ++i) printf("%d ", d[i]);
puts("");
fclose(stdin); fclose(stdout);
return 0;
}
SPFA 的 BFS 版
经过 luogu 考验的!
/*
判负环 \
SPFA_BFS原始版 \
最复杂 O(nm) \
据说可以优化成 SLF、LLL \
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=2010, M=3010, INF=1e9+7;
//edge
struct Edge
{
int to, next, w;
}e[M<<1];
int pn, h[N];
void add(int u, int v, int w)
{
e[++pn]=(Edge){v,h[u],w};
h[u]=pn;
return;
}
//main
int n, m, s;
int d[N];
void init()
{
memset(h,0,sizeof(h));
pn=0;
}
//spfa
queue<int> Q;
bool vis[N];
int cnt[N];
bool spfa_bfs(int s)
{
memset(vis,false,sizeof(vis));
memset(cnt,0,sizeof(cnt));
fill(d+1,d+n+1,INF); d[s]=0;
while(!Q.empty()) Q.pop();
Q.push(s); vis[s]=true;
while(!Q.empty())
{
int u=Q.front(); Q.pop();
vis[u]=false;
for(int h1=h[u]; h1; h1=e[h1].next)
{
int v=e[h1].to;
if(d[v]>d[u]+e[h1].w)
{
d[v]=d[u]+e[h1].w;
if(!vis[v])
{
Q.push(v);
vis[v]=true;
cnt[u]++;
if(cnt[u]>n) return false;
}
}
}
}
return true;
}
int main()
{
freopen("data.txt","r",stdin);
freopen("SPFA_BFS.txt","w",stdout);
init();
scanf("%d%d%d", &n, &m, &s);
for(int i=0, u, v, w; i<m; ++i)
{
scanf("%d%d%d", &u, &v, &w);
add(u,v,w);
}
if(!spfa_bfs(s)) puts("A");
// 对拍需要,若存在能到达的负环,输出 "A"
else
{
for(int i=1; i<=n; ++i) printf("%d ", d[i]);
puts("");
}
fclose(stdin); fclose(stdout);
return 0;
}
随机数据生成
可能数据出得并不科学,但起码是有负边没负环的。
/*
该程序随机出的数据大约有 1/20 是负的。
有最多 n=200 个点,最少 2*n 、最多 2400 条边。
*/
#include<cstdio>
#include<cstdlib>
#include<ctime>
using namespace std;
#define M1 200
#define M2 2000
#define M3 2000
#define RAND(x) (rand()%(x)+1)
int main()
{
freopen("data.txt","w",stdout);
srand((unsigned)time(NULL));
int n, m, s;
n=RAND(M1); m=n*2+RAND(M2); s=RAND(n);
printf("%d %d %d\n", n, m, s);
for(int i=0, u, v, w; i<m; ++i)
{
u=RAND(n);
v=RAND(n);
w=RAND(M3);
if(w%20==0) w=-w/10;
printf("%d %d %d\n", u, v, w);
}
fclose(stdout);
return 0;
}
对拍程序
可以直接当模板,真不错。
#include<cstdio>
#include<cstdlib>
#include<ctime>
using namespace std;
#define getTime(a,b) (double)((b)-(a))/CLOCKS_PER_SEC
int main()
{
int cnt=0;
while(1)
{
system("data.exe");
clock_t startTime1=clock();
system("SPFA_BFS.exe");
clock_t endTime1=clock();
printf("oh,man~\n");
FILE *f1=fopen("SPFA_BFS.txt","r");
char c=fgetc(f1);
fclose(f1);
if(c=='A') continue;
// 先用SPFA判断是否有负环,若有,重新随机数据
clock_t startTime2=clock();
system("Dijkstra.exe");
clock_t endTime2=clock();
printf("run time : %lf %lf\n", getTime(startTime1,endTime1), \
getTime(startTime2,endTime2));
if(system("fc SPFA_BFS.txt Dijkstra.txt"))
{
printf("failed.");
system("data.txt");
return 0;
}
// 小小地记录一下次数
cnt++;
if(cnt%20==0)
{
printf("------------------------------第 %d 次了--------\n\n", cnt);
}
}
}
代码放完了。
感觉身体被掏空
3. 结果展示
首先看看数据出得怎么样:
随便找了其中一个数据,可以看出是有负边的,比较合理。
再看看到各点的最短路如何:
不错,起点到各终点的最短路也是正常的,只有少数是INF,可以接受。
最后的最后:
2020年新年快乐!!!
总结
在我的这次尝试中,Dijkstra 并没有败下阵来!虽然被无数人说道,但并没有屈服!
但又有什么用呢?
无负边时,还是用 Dijkstra,那年那题 1 100 变 60 的悲剧还在一些人脑海里回荡;有负边时,人们还是优先用 SPFA,毕竟久经考验。
一切好像并没有什么改变。
2018 NOI D1T1 ↩︎