文章目录
- 一、Dijkstra 算法
- 1. 简介
- 2. 基本思想
- 3. 朴素 Dijkstra 算法(重点)
- 3.1 朴素 Dijkstra 算法实现步骤
- 3.2 朴素 Dijkstra 算法伪代码
- 4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。
- 5. 堆优化朴素 Dijkstra 算法
- 6. 堆优化 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 II 。
- 7. 堆优化 Dijkstra 算法和朴素 Dijkstra 算法时间复杂度
- 二、Dijkstra 算法例题——Dijkstra 求最短路 I
- 具体实现
- 1. 样例演示
- 2. 实现思路
- 3. 代码注解
- 4. 实现代码
- 三、Dijkstra 算法例题——Dijkstra求最短路 II
- 具体实现
- 1. 样例演示
- 2. 实现思路
- 3. 代码注解
- 4. 实现代码
一、Dijkstra 算法
1. 简介
- Dijkstra(迪杰斯特拉)算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。
- 这是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。
- 迪杰斯特拉算法主要特点是从起始点开始,采用 贪心算法 的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
- 此算法不能用于求负权图,要求所有边的权重都为非负值。
2. 基本思想
- (1) 首先假定源点为 u ,顶点集合 V 被划分为两部分:集合 S 和 V-S。 初始时 S 中仅含有源点 u ,其中 S 中的顶点到源点的最短路径已经确定。
- (2) 集合 S 和 V-S 中所包含的顶点到源点的最短路径的长度待定,称从源点出发只经过 S 中的点到达 V-S 中的点的路径为特殊路径,并用 dist[] 记录当前每个顶点对应的最短特殊路径长度。
3. 朴素 Dijkstra 算法(重点)
3.1 朴素 Dijkstra 算法实现步骤
- (1) 各个点状态初始化,各点之间距离初始化。
- 用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离。初始时,dist 数组的各个元素为无穷大。
- 用一个状态数组 state 记录是否找到了源点到该节点的最短距离,state[i] 如果为真,则表示找到了源点到节点 i 的最短距离,state[i] 如果为假,则表示源点到节点 i 的最短距离还没有找到。初始时,state 各个元素为假。
- (2) 源点到源点的距离为 0,即 dist[1] = 0。
- (3) 遍历 dist 数组,找到一个节点,这个节点是:没有确定最短路径的节点中距离源点最近的点。假设该节点编号为 i 。此时就找到了源点到该节点的最短距离,state[i] 置为 1 。
- (4) 遍历 i 所有可以到达的节点 j,如果 dist[j] 大于 dist[i] 加上 i -> j 的距离,即 dist[j] > dist[i] + w[i][j](w[i][j] 为 i -> j 的距离) ,则更新 dist[j] = dist[i] + w[i][j] 。
- (5) 重复 3 4 步骤,直到所有节点的状态都被置为 1。
- (6) 此时 dist 数组中,就保存了源点到其余各个节点的最短距离。
3.2 朴素 Dijkstra 算法伪代码
int dist[n],state[n];
dist[1] = 0, state[1] = 1;
for(i:1 ~ n)
{
t <- 没有确定最短路径的节点中距离源点最近的点;
state[t] = 1;
更新 dist;
}
4. 朴素 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 I 。
5. 堆优化朴素 Dijkstra 算法
- 朴素 Dijkstra 算法的主要耗时的步骤是从 dist 数组中选出,没有确定最短路径的节点中距离源点最近的点 t 。只是找个最小值而已,没有必要每次遍历一遍 dist 数组。
- 在一组数中每次能很快的找到最小值,很容易想到使用小根堆。可以使用库中的小根堆(推荐)或者自己编写。
- 具体思路如下:
- (1) 起始点的距离初始化为零,其他点初始化成无穷大。
- (2) 将起始点放入堆中。
- (4) 不断循环,直到堆空。每一次循环中执行的操作为:弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定),用该点更新临界点的距离,若更新成功就加入到堆中。
6. 堆优化 Dijkstra 算法具体实现详见例题 Dijkstra 求最短路 II 。
7. 堆优化 Dijkstra 算法和朴素 Dijkstra 算法时间复杂度
- 朴素 Dijkstra 算法时间复杂度 O(n2) 。
for (int i = 0; i < n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)
{
if (!st[j] && (t == -1 || dist[j] < dist[t]))
t = j; // O(n) * O(n) -> O(n^2)
}
st[t] = true; // O(n) * O(1) -> O(n)
for (int j = 1; j <= n; j++)
{
dist[j] = min(dist[j], dist[t] + g[t][j]); //O(n) * O(n) -> O(n^2)
}
}
- 堆优化 Dijkstra 算法时间复杂度 O(mlog(n)) 。
//遍历大部分的边
while (heap.size())
{
auto t = heap.top(); //O(m) * O(1) -> O(m)
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver])
{
continue;
}
st[ver] = true; //O(m) * O(1) -> O(m)
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
// 堆的插入操作时间复杂度是 O(log(n))
// O(m) * O(log(n)) -> O(mlog(n))
}
}
}
二、Dijkstra 算法例题——Dijkstra 求最短路 I
题目描述
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1 ≤ n ≤ 500
1 ≤ m ≤ 1e5
图中涉及边长均不超过10000 。
输入样例
3 3
1 2 2
2 3 1
1 3 4
输出样例
3
具体实现
1. 样例演示
- 输入 n = 3,m = 3,表示求从 1 号点到 n = 3 号点的最短距离,共有 m = 3 条边。
- 从 1 号点到 2 号点的边长为 2 。
- 从 2 号点到 3 号点的边长为 1 。
- 从 1 号点到 3 号点的边长为 4 。
- 显然,1 + 2 小于 4 ,因此最短路径存在,且为 3 。
2. 实现思路
- 在此只作简单叙述,具体可见朴素 Dijkstra 算法实现步骤和样例演示。
- (1) 初始化各点到起点距离为正无穷。
- (2) 将 1 号点到起点的距离更新为 0 。
- (3) 2 号点到 1 号点的距离为 2 ,小于正无穷,将其更新为 2 ; 3 号点同理,将距离更新为 4 。
- (4) 然后,2 和 4 的最小值是 2 ,便将 2 加入集合 s ,此时,2 号点到 3 号点的距离 1 。可得,2 + 1 < 4,便将 3 号点到起点的距离更新为 3 。
3. 代码注解
- int g[N][N];稠密图一般使用邻接矩阵。
- int dist[N];记录每个节点距离起点的距离。
- bool st[N];true 表示已经确定最短路 属于 s 集合。
- memset(dist, 0x3f, sizeof dist);所有节点距离起点的距离初始化为无穷大。
- dist[1] = 0;起点距离自己的距离为零。
- int t = -1;一开始 t 的赋值是 -1 ,如果 t 没有被更新,那么要更新一下 t 。
- memset 按字节赋值,所以 memset 0x3f 就等价与赋值为 0x3f3f3f3f 。
- 其他代码注解已标注在实现代码当中,详见实现代码。
4. 实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
//迭代n次,每次可以确定一个点到起点的最短路
for (int i = 0; i < n - 1; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
{
//不在s集合,并且
//如果没有更新过,则进行更新, 或者发现更短的路径,则进行更新
if (!st[j] && (t == -1 || dist[t] > dist[j]))
{
t = j;
}
}
//找到了距离最小的点t,并用最小的点t去更新其他的点到起点的距离
for (int j = 1; j <= n; j ++ )
{
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
//加入到s集合中
st[t] = true;
}
// 如果起点到达不了n号节点,则返回-1
if (dist[n] == 0x3f3f3f3f)
{
return -1;
}
// 返回起点距离n号节点的最短距离
return dist[n];
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
cout << dijkstra() << endl;
system("pause");
return 0;
}
三、Dijkstra 算法例题——Dijkstra求最短路 II
题目描述
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1 ≤ n,m ≤ 1.5×1e5
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 1e9。
输入样例
3 3
1 2 2
2 3 1
1 3 4
输出样例
3
具体实现
1. 样例演示
- 同 Dijkstra 算法例题——Dijkstra求最短路 I 。
2. 实现思路
- 不需要手写堆,使用 C++ 当中的优先队列。
- 小根堆使用 greater 实现。
3. 代码注解
- typedef pair<int, int> PII;<离起点的距离, 节点编号>。
- memset(dist, 0x3f, sizeof dist);所有距离初始化为无穷大。
- dist[1] = 0;1 号节点距离起点的距离为 0 。
- priority_queue<PII, vector, greater> heap;建立一个小根堆。
- 其他代码注解已标注在实现代码当中,详见实现代码。
4. 实现代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;//<离起点的距离, 节点编号>
const int N = 1e6 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
//在 a 节点之后插入一个 b 节点,权重为 c
void add(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx ++ ;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
//1号节点插入堆
heap.push({0, 1});
while (heap.size())
{
//取出堆顶顶点
auto t = heap.top();
//并删除
heap.pop();
//取出节点编号和节点距离
int ver = t.second;
int distance = t.first;
//如果节点被访问过则跳过
if (st[ver])
{
continue;
}
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
//取出节点编号
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f)
{
return -1;
}
return dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
cout << dijkstra() << endl;
system("pause");
return 0;
}