Dijkstra算法

单源最短路问题

给定一张 \(n\) 个点的有向图,边权均为正数。给定起点 \(st\) ,要求出 \(st\) 到其他所有点的最短路。

算法简介

\(\text{Dijkstra}\) 算法是典型的单源最短路径算法,朴素算法的时间复杂度为 \(O(n^2)\) ,加上堆优化后可以达到 \(O((n+m)\log n)\) 的时间复杂度,并且在实际应用中较为稳定,需要在各种题目中灵活应用!

算法流程

\(\text{Dijkstra}\) 算法的主要思想是贪心,但它只适用于不含负权边的图。

它的主要思想是:以起始点为中心向外层层扩展,直到扩展到终点为止。

  1. 初始化

\(d[i]\) 表示起点 \(st\)\(i\) 点的最短距离,并把初始值为无穷大 \(+∞\)\(d[st]=0\) 。把点集 \(V\) 分成两部分:已经确定最短路的点集 \(S\) 与未确定最短路的点集。我们先将 \(S\) 初始化为空集。

  1. 具体过程

先找到一个不在 \(S\)\(d\) 值最小的点 \(u\) ,然后将它加入到 \(S\) 中。

再扫描节点 \(u\) 所有出边 \((u,v,w)\) ,对所有 \(v\) 做一次松弛操作,即若 \(d[v]>d[u]+w\) ,则用 \(d[u]+w\) 更新\(d[v]\)

重复上述步骤,直到所有节点被加入到 \(S\) 中。

正确性的简要证明

我们知道,当所有边长都是非负数的时候,全局最小值不可能再被其他节点更新。即在第 \(2\) 步中找出的 \(u\) 一定不会存在其它的点再来更新 \(d[u]\) 此时的 \(d[u]\) 已经是从起点到 \(u\) 的最短路径。我们不断选择确定为最小值的点进行标记和拓展,最终就可以得到起点到每个节点的最短路径。

朴素算法

void dijkstra()
{
	bool vis[N];
	memset(d,0x3f,sizeof d);
	memset(vis,0,sizeof vis);
	d[st]=0;
	while(1)
	{
		int u=-1;
		for(Re int i=1;i<=n;i++)
		{
			if(!vis[i]&&(u==-1||d[i]<d[u]))
			{
				u=i;
			}
		}
		if(u==-1) break;
		vis[u]=1;
		for(Re int i=0;i<g[u].size();i++)
		{
			int to=g[u][i].id;
			int Len=g[u][i].d;
			if(d[to]>d[u]+Len)
			{
				d[to]=d[u]+Len;
			}
		}
	}
}

算法优化

然而,我们发现该程序的时间复杂度虽然只有 \(O(n^2)\) ,但仍然无法满足大多数题目的要求。

于是,我们便考虑用一些操作来优化上述步骤。

优先队列 ( priority_queue )

这就是所谓的堆优化 \(\text{Dijkstra}\) ,是较为常用的优化方式。

我们考虑用堆对 \(d\) 数组进行维护,共进行的 \(n\) 次操作,每次用 \(O(\log n)\) 的时间取出堆顶元素并删除;共用 \(O(m\log n)\) 遍历每条边。故总时间复杂度为 \(O((n+m)\log n)\)

struct Node {
	int id,d;
	bool operator < (const Node &a) const{
		return d>a.d;
	}
};

void dijkstra()
{
	bool vis[N];
	memset(d,0x3f,sizeof d);
	memset(vis,0,sizeof vis);
	d[st]=0;
	priority_queue<Node> q;
	q.push((Node){st,0});
	while(!q.empty())
	{
		int u=q.top().id;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(Re int i=0;i<g[u].size();i++)
		{
			int to=g[u][i].id;
			int Len=g[u][i].d;
			if(d[to]>d[u]+Len)
			{
				d[to]=d[u]+Len;
				q.push((Node){to,d[to]});
			}
		}
	}
}

但是上述代码需要事先写一个结构体,有没有办法可以避免呢?

于是我们考虑使用 \(\text{pair}\) 数组。

pair 数组

由于 \(\text{pair}\) 数组有自带的比较函数,因此,我们可以省去上述的结构体。

但是我们需要注意一点,就是在入队的时候需要加入的是 \(-d[v]\) 而不是 \(d[v]\)

void dijkstra()
{
	bool vis[N];
	memset(d,0x3f,sizeof d);
	memset(vis,0,sizeof vis);
	d[st]=0;
	priority_queue<pair<int,int> > q;
	q.push(make_pair(0,st));
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(Re int i=0;i<g[u].size();i++)
		{
			int to=g[u][i].id;
			int Len=g[u][i].d;
			if(d[to]>d[u]+Len)
			{
				d[to]=d[u]+Len;
				q.push(make_pair(-d[to],to));
			}
		}
	}
}
posted @ 2020-11-21 14:49  kebingyi  阅读(347)  评论(0编辑  收藏  举报