Dijkstra算法略解

Dijkstra 算法是一种用来解决单源最短路径的算法。参考资料

给定一张 N N N 个节点、 M M M 条边的有向图,求从 1 号节点到每一个节点的最短路径长度。 N ≤ 1 0 5 , M ≤ 2 × 1 0 5 N≤10^5,M≤2×10^5 N105,M2×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 1P1P2...PnP)仍未被找到。假设上次遍历的节点是 P 0 P_0 P0

n = 0 n=0 n=0,说明 1 → P 1→P 1P 的距离比 P 0 → P P_0→P P0P 更短,则上次遍历不会更新后者而会更新前者,与更新规则矛盾;

n ≠ 0 n≠0 n=0,说明 n − 1 n-1 n1(坑)。
那么 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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值