Dijkstra
Dijkstra 算法用于寻找图上两点的最短路径。根据最短路径经过的点也是最短路径的原理(由矛盾律得出:如果经过点的路径不是最短路径,则整条路径也不是最短),从起点开始,通过一步步寻找到下一个点的最短路径,最终得到到达其他所有点的最短路径。
算法的思路
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dist[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dist数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dist中的值。
然后,又从dist中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
1.朴素版dijkstra
(朴素版dijkstra适合稠密图)
题目:链接 给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出 11 号点到 nn 号点的最短距离,如果无法从 11 号点走到 nn 号点,则输出 −1−1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n≤500
1≤m≤105,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
模板:
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,m;
int g[N][N]; //使用邻接矩阵来储存
int dist[N]; //储存所有点到1号点的距离
int book[N];//用来判断点是否已经被确定
int dijktra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++)//循环n个点
{
int t=-1;
for(int j=1;j<=n;j++)//每次寻找距离1号点最近且还没有被确定的点
{
if(!book[j]&&(t==-1||dist[t]>dist[j]))
t=j;
}
book[t]=1;
for(int j=1;j<=n;j++)//更新每个点的最短值
{
dist[j]=min(dist[j],g[t][j]+dist[t]);
}
}
if(dist[n]==0x3f3f3f3f)return -1;//如果1号点到不了n号点,就return -1
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<<dijktra()<<endl;
}
2.堆优化Dijkstra
适合稀疏图
优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离最短的点O(n^2)可以使用最小堆优化。
- 一号点的距离初始化为零,其他点初始化成无穷大。
- 将一号点放入堆中。
- 不断循环,直到堆空。每一次循环中执行的操作为:
弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
用该点更新临界点的距离,若更新成功就加入到堆中。
题目:链接
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。
接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 nn号点的最短距离。
如果路径不存在,则输出 −1。
数据范围
1≤n,m≤1.5×105≤n,m≤1.5×105,
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 109
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010;
// 稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; // 用来存权重
int dist[N];
bool st[N]; // 如果为true说明这个点的最短路径已经确定
int n, m;
void add(int x, int y, int c)
{
w[idx] = c; // 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
e[idx] = y; // 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),并
ne[idx] = h[x]; // 标记st为true,所以下一次弹出3+x会continue不会向下执行。
h[x] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆
// 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离,其次在从堆中拿出来的时
// 候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。
heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
while(heap.size())
{
PII k = heap.top(); // 取不在集合S中距离最短的点
heap.pop();
int ver = k.second, distance = k.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i]; // i只是个下标,e中在存的是i这个下标对应的点。
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];
}
int main()
{
memset(h, -1, sizeof(h));
scanf("%d%d", &n, &m);
while (m--)
{
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(x, y, c);
}
cout << dijkstra() << endl;
return 0;
}