Dijkstra 算法是一种用来解决单源最短路径的算法。参考资料。
例
给定一张 N N N 个节点、 M M M 条边的有向图,求从 1 号节点到每一个节点的最短路径长度。 N ≤ 1 0 5 , M ≤ 2 × 1 0 5 N≤10^5,M≤2×10^5 N≤105,M≤2×105。
一种很显然的思路是,如果 a->b 的最短路径经过 c,那么 a->b 的最短路径一定是 a->c 的最短路径和 c->b 的最短路径。也就是说,我们可以先求出较为好求的节点的最短路径(c),再根据这些数据来计算其他节点的最短路径(b)。
什么节点的最短路径最好求?显然是被 1 号点直接指向的节点。当我们求出 1 号点及其直接指向的节点的最短路径以后,我们就可以使用他们来计算其他节点的最短路径。
不难想到,1号点(a)到另一个不与 1 号点直接相连的点(b)的路径有很多条。如果我们对每条 a->b 的路径都遍历并更新,显然会超时(这也就是spfa算法不完美的原因)。考虑贪心,如果每次都尽量从与 1 号点最近的点出发开始遍历,那么遍历结束以后,新的与 1 号点最近的点的最短路径就已经被找到。
请思考加粗句为什么正确。
假设这句话不正确,即新的与 1 号点最近的点( P P P)的最短路径( 1 → P 1 → P 2 → . . . → P n → P 1→P_1→P_2→...→P_n→P 1→P1→P2→...→Pn→P)仍未被找到。假设上次遍历的节点是 P 0 P_0 P0。
若 n = 0 n=0 n=0,说明 1 → P 1→P 1→P 的距离比 P 0 → P P_0→P P0→P 更短,则上次遍历不会更新后者而会更新前者,与更新规则矛盾;
若
n
≠
0
n≠0
n=0,说明
n
−
1
n-1
n−1(坑)。
那么
P
0
P_0
P0
如何寻找与 1 号点最近的点呢?用 set 维护,可以参考我的另一篇文章。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>
using namespace std;
const int MAXN=100010;
const int MAXM=200010;
struct edge{
int x, y, d, next;
}e[MAXM];
int len=0;
int first[MAXN];
int n, m;
int s1, s2, s3;
inline int read (){
int x=0; char c;
do c=getchar (); while ('0'>c||'9'<c);
while ('0'<=c&&'9'>=c)
x=x*10+c-48, c=getchar ();
return x;
}
void ins (int x, int y, int d){
e[++len].x=x; e[len].y=y; e[len].d=d;
e[len].next=first[x]; first[x]=len;
}
struct pir{
int x, dis;
pir (){
x=dis=0;
}
};
bool operator< (const pir a, const pir b){
if (a.dis!=b.dis) return a.dis<b.dis?1:0;
return a.x<b.x?1:0;
}
set<pir> s;
int dis[MAXN], tf[MAXN];
int main (){
n=read (); m=read (); read ();
for (int i=1; i<=m; ++i){
s1=read (); s2=read (); s3=read ();
ins (s1, s2, s3);
}
memset (dis, 63, sizeof (dis)); dis[1]=0;
memset (tf, 0, sizeof (tf));
pir p; p.x=1; p.dis=0;
s.clear (); s.insert (p);
for (int i=1; i<n; ++i){
pir nw=*s.begin (); int x=nw.x;
tf[x]=1; s.erase (nw);
for (int i=first[x]; i; i=e[i].next){
int y=e[i].y;
if (tf[y]) continue;
p.x=y; p.dis=dis[y]; s.erase (p);
dis[y]=min (dis[y], dis[x]+e[i].d);
p.dis=dis[y]; s.insert (p);
}
}
for (int i=1; i<=n; ++i)
printf ("%d ", dis[i]);
}