介绍
Dijkstra算法是一种用于求解单源最短路径问题的经典算法。它可以在给定有向加权图(无向是特殊的有向)中,从一个起点到其他所有节点的最短路径。
作用:
在给定的加权图中,求一个点到其余点的最短路径
基本思想:
- 初始化一个距离数组dist[],表示起点到各个节点的当前最短距离,初始时起点的最短距离为0,其余节点的最短距离为无穷大。
- 创建一个集合S,用于存放已经确定最短路径的节点,初始时集合S为空。
- 重复以下步骤,直到集合S包含了所有节点:
- 在未确定最短路径的节点中,选择一个节点v,选择的是dist[v]值最小,将该节点加入集合S。
- 更新节点v的所有邻接节点u的最短距离,如果通过节点v到达节点u的距离比当前最短路径更短,则更新最短距离dist[u]为新的最短距离。
- 表示为: dist[u] = min(dist[u],dist[v]+edge[v][u])
Dijkstra算法的关键是如何选择节点v,这里使用了贪心策略,每次选择距离起点最近的未确定最短路径的节点,确保每次加入的节点都是当前最短距离确定的。
朴素模板如下:
int dijkstra(int b)//求b到各个点的距离
{
memset(dist, 0x3f, sizeof dist);
dist[b] = 0;
for (int i = 0; i < n - 1; i++)
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
//从t这个点更新到其他点的距离。
//虽然是遍历了每个点,但并不代表t可以到任意点,
//因为会把到不了的边的距离设为inf,就避免了考虑t点可以到哪些点的问题
for (int j = 1; j <= n; j++)
dist[j] = min(dist[j], dist[t] + v[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
堆优化版
可以看到,朴素版有一个遍历找dist最小的操作,如果用小根堆维护dist的话,就可以一下子找到最小了,无需遍历
插入优先队列的知识点
可以使用标准库中的
priority_queue
来实现小根堆和大根堆。priority_queue
默认实现的是大根堆,如果需要实现小根堆,可以通过自定义比较器来改变默认行为。priority_queue<int, vector<int>, greater<int>> minHeap;
int dijkstra(int b)//求b到各个点的距离
{
memset(dist, 0x3f, sizeof dist);
dist[b] = 0;
priority_queue<node, vector<node>, cmp> q;
q.push({ b,0 });
while (q.size()) {
node t = q.top();
q.pop();
int ver = t.v, distance = t.w;
//此时dist[ver] == distance,是最小的
if (st[ver]) continue;
st[ver] = true;
for (int i = 0; i < edge[ver].size();i++) {
//更新ver可以到达的点
if (dist[i] > edge[ver][i] + distance) {
dist[i] = edge[ver][i] + distance;
q.push({ i,dist[i] });
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
邻接表写法
Dijkstra求最短路 II
const int N = 150010;
typedef pair<int,int> PII;
int h[N],w[N],ne[N],e[N],idx;
void add(int u,int v,int we){
w[idx] = we,ne[idx] = h[u],e[idx] = v;
h[u] = idx++;
}
int dist[N];bool st[N];
int n,m;
int dijkstra(int b){
memset(dist,0x3f,sizeof dist);
dist[b] = 0;
//PII的第一个元素是点x到b点的距离,第二个元素是点x
priority_queue<PII,vector<PII>,greater<PII>> heap;
heap.push({0,b});
while(heap.size()){
auto t = heap.top();
heap.pop();
int ver = t.second,distance = t.first;
if(st[ver]) continue;
st[ver] = true;
//访问ver能到达的点,更新ver到这些点的距离
for(int i = h[ver];i!=-1;i = ne[i]){
//切记了,i只是编号!
int j = e[i];//j才是ver能到达的点
if(dist[j] > distance + w[i]){
dist[j] = distance + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
void solve() {
cin>>n>>m;
memset(h,-1,sizeof h);
//memset(st,false,sizeof st);
//idx = 1;
while(m--){
int a,b,c;cin>>a>>b>>c;
add(a,b,c);
}
int t = dijkstra(1);
cout<<t<<endl;
}
Dijkstra序列
输入样例:
5 7
1 2 2
1 5 1
2 3 1
2 4 1
2 5 2
3 5 1
3 4 1
4
5 1 3 4 2
5 3 1 2 4
2 3 4 5 1
3 2 1 5 4
输出样例:
Yes
Yes
Yes
No
分析:还是要先弄目标dijkstra序列的含义是什么,这个困扰我了很久。
我们知道,在构建dist数组时,每次是选 还没有选过 且 当前dist最小 的点,在这个点的基础上进行更新。然后要选n次,我们这n次选的点的序列就称作dijkstra序列(第一个点就是起点)。
例如:5,1,3,4,2。代表点5是起点,然后选点1,点3,点4,点2。
设每次选的点为dist[i],那么dist[i]一定小于当前所有还没选过的点。以这个为判断条件,如果不满足,则不是一个dijkstra序列。
#include<assert.h>
#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N =1010;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
//long long MAX(long long a, long long b) { return a < b ? b : a; }
int n, m;
int v[N][N];//存每条边,v[i][j]代表i到j的距离。如果i到j没有直接的路径,那么设为inf
int dist[N];//存i号点到各个点的最短距离
int st[N];//存到达该点该点是否已经确定最短路径
int q[N];
int dijkstra() {//从b开始,到各个点的距离
memset(dist, inf, sizeof(dist));
memset(st, 0, sizeof(st));
dist[q[0]] = 0;//到达自己的距离是0
for (int i = 0; i < n; i++) {
int cur = q[i];
for (int j = 1; j <= n; j++) {
if (!st[j] && dist[j]<dist[cur]) {
return false;
}
}
//从cur这个点更新到其他点的距离。
//虽然是遍历了每个点,但并不代表cur可以到任意点,
//因为会把到不了的边的距离设为inf,就避免了考虑cur点可以到哪些点的问题
for (int j = 1; j <= n; j++) {
dist[j] = min(dist[j], dist[cur] + v[cur][j]);
}
st[cur] = 1;
}
return 1;
}
signed main() {
cin >> n >> m;
memset(v, inf, sizeof(v));
for (int i = 0; i < m; i++) {
int a, b, d; cin >> a >> b >> d;
//因为是无向图
v[a][b] = d;
v[b][a] = d;
}
int k; cin >> k;
while (k--) {
vector<int> t;
t.push_back(0);
for (int i = 0; i < n; i++) {
cin >> q[i];
}
if (dijkstra()) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
奶牛回家
输入样例:
5
A d 6
B d 3
C e 9
d Z 8
e Z 3
输出样例:
B 11
分析:简单题。命名为大写字母的牧场才有牛,可以逆向思考,从牛棚Z开始往这些牧场走,那条路最短。
易错点,两个牧场间可能有多条路,这些路长度可能不一样,注意判断。
#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#define inf 0x3f3f3f3f
#define int long long
const int N =1010;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
//long long MAX(long long a, long long b) { return a < b ? b : a; }
int n;
int v[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
int get(char x) {
if (x >= 'A' && x <= 'Z') return x - 'A' + 1;
return x - 'a' + 26 + 1;
}
void dijkstra() {
memset(dist, inf, sizeof(dist));
memset(st, 0, sizeof(st));
dist[26] = 0;//Z到Z距离是0
for (int i = 1; i <= 52; i++) {
int cur = -1;
for (int j = 1; j <= 52; j++) {
if (!st[j] && (cur == -1 || dist[cur] > dist[j])) {
cur = j;
}
}
for (int j = 1; j <= 52; j++) {
dist[j] = min(dist[j], dist[cur] + v[cur][j]);
}
st[cur] = 1;
}
}
signed main() {
cin >> n;
memset(v, inf, sizeof(v));
while (n--) {
char a, b; int c;
cin >> a >> b >> c;
int d = get(a);
int e = get(b);
v[d][e] = v[e][d] = min(v[d][e], c);
}
dijkstra();
int index = 0, len = inf;
for (int i = 1; i <= 25; i++) {
if (len > dist[i] && dist[i] <= 10000 * 1000) {
//如果大于这个距离就是没有到i的路,距离为inf
len = dist[i];
index = i;
}
}
printf("%c %lld", index + 'A' - 1, len);
return 0;
}