算法 | Dijkstra 算法
解决赋权图的单源最短路径问题:Dijkstra (/ˈdaɪkstrəz/, 迪杰斯特拉) 算法
- 不能解决负边
Dijkstra 算法
朴素版本
复杂度
时间复杂度:$O(\left|V\right|^2)$
此为最坏情况,$\left|E\right|$ 为边数,$\left|V\right|$ 为顶点数。该版本适合稠密图,即 $\left|E\right|接近\left|V\right|^2$ 的图。
分析
核心思想
以下图为例,该图为一个有向图,箭头上的数字为权值,且权值均为非负数。
假设我们已经确定了 $A\rightarrow A=0$,$A\rightarrow C=1$,称 $A,C$ 为确定点。那么我们计算出可以从确定点一步到达的点的最短距离,即 $A\rightarrow B=2,A\rightarrow D=3,A\rightarrow E=4$,称 $B,D,E$ 为待定点。
接下来,我们用贪心思想还能将待定点中距离最小的那个点转化为确定点,即 $B$ 点的距离 $2$ 可以确定是最短的。然后我们就可以用该确定点更新可到达的点,称为松弛操作。再循环上述整个过程。
以上便是 Dijkstra 算法的核心思想,其正确性证明可见文章:https://zhuanlan.zhihu.com/p/340708110。
伪代码表示
dijkstra (G, dist[])
初始化: dist[1~v]=+INF, dist[1]=0
for (循环v次)
t=待定点的集合中dist[t]最小的顶点
记t已确定
for (j : 从t出发能到达的顶点的集合)
if (以t为中介点到j的距离更短)
更新dist[j];
代码实现
const int MAXN = 510, INF = 0x3f3f3f3f; // INF代表无穷大
int g[MAXN][MAXN]; // 邻接矩阵存图
int dist[MAXN]; // 最短距离
bool vis[MAXN]; // 访问情况
int v, e; // v顶点数 e边数
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
for (int i = 1; i <= v; i++)
{
int t = -1;
for (int j = 1; j <= v; j++)
if (!vis[j] && (t == -1 || dist[t] > dist[j]))
t = j;
vis[t] = true;
for (int j = 1; j <= v; j++)
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
堆优化版本
复杂度
时间复杂度:$O((\left|E\right|+\left|V\right|)\log\left|V\right|)$
此为最坏情况,$\left|E\right|$ 为边数,$\left|V\right|$ 为顶点数。该版本适合稀疏图,即 $\left|E\right|\ll\left|V\right|^2$ 的图。
分析
朴素版本中,每次迭代都有一个查找距离最小顶点的过程,并且对所有点尝试进行了松弛操作,共需要 $2\left|V\right|$ 次循环。
针对查找最小值这一操作,二叉堆是一个很好的数据结构。我们建立一个小顶堆,即可在 $O(1)$ 时间内取得最小值。
针对松弛操作,我们将存图方式改为邻接表,所以我们就可以只更新与该点相连的顶点,效率更高。
代码实现
使用 STL 库中的优先队列作为堆,由于其没有更新元素的功能,所以节点距离更新后,无法更新堆中的数据。但可以用冗余的方法,节点距离更新后直接插入新距离,旧距离仍然储存在堆中。由于堆顶最小,我们可以保证新距离最先取到,之后若发现取到了旧距离,直接忽略即可(参见循环中的 continue 语句)。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int MAXN = 100010, INF = 0x3f3f3f3f; // INF代表无穷大
int E, V, S; // E边数,V顶点数,S起点
vector<PII> edge[MAXN]; // 储存连接关系,二元组为(权值,终点)
priority_queue<PII, vector<PII>, greater<PII>> pque; // 储存节点,节点距离小的在堆顶
int dist[MAXN]; // 储存节点距离
bool vis[MAXN]; // 是否已经访问过该节点的标志
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
pque.push({dist[1], 1});
while (!pque.empty())
{
PII cur = pque.top();
pque.pop();
if (vis[cur.second])
continue;
vis[cur.second] = true;
for (auto next : edge[cur.second])
{
if (dist[next.second] > dist[cur.second] + next.first)
{
dist[next.second] = dist[cur.second] + next.first;
if (!vis[next.second])
pque.push({dist[next.second], next.second});
}
}
}
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi