Dijkstra算法求最短路
给定一个 \(n\) 个点 \(m\) 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。
请你求出 \(1\) 号点到 \(n\) 号点的最短距离,如果无法从 \(1\) 号点走到 \(n\) 号点,则输出 \(-1\)。
输入格式
第一行包含整数 \(n\) 和 \(m\)。
接下来 \(m\) 行每行包含三个整数 \(x,y,z\),表示存在一条从点 \(x\) 到点 \(y\) 的有向边,边长为 \(z\)。
输出格式
输出一个整数,表示 \(1\) 号点到 \(n\) 号点的最短距离。
如果路径不存在,则输出 \(-1\)。
数据范围
\(1 \le n,m \le 1.5 \times 10^5\),
图中涉及边长均不小于 \(0\),且不超过 \(10000\)。
数据保证:如果最短路存在,则最短路的长度不超过 \(10^9\)。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
分析
迪杰斯特拉(Dijkstra)算法是一种用于解决单源最短路径问题的经典算法,用于找到一个图中从一个起始节点到所有其他节点的最短路径。该算法的核心思想是逐步确定从起始节点到其他节点的最短路径,每次选择当前路径中距离起始节点最近的一个节点,并更新其他节点的距离值。
以下是迪杰斯特拉算法的基本过程:
初始化:创建一个距离数组用来存储从起始节点到每个节点的最短距离估计值,以及一个集合用来存储已经确定最短路径的节点。
将起始节点的距离设为0,其余节点的距离设为无穷大(或一个很大的值),将起始节点加入集合中。
从未确定最短路径的节点中选取距离起始节点最近的节点,将该节点标记为已确定最短路径,并且更新其相邻节点的距离估计值。遍历该节点的所有邻居,计算通过当前节点是否可以获得更短的路径,如果可以则更新距离估计值。
重复步骤3,直到所有节点都被标记为已确定最短路径,或者所有未确定节点的距离估计值都是无穷大(表示无法从起始节点到达这些节点)。
最终得到的距离数组就包含了从起始节点到所有其他节点的最短路径距离。
迪杰斯特拉算法的核心在于贪心策略,每次选择当前距离起始节点最近的节点,然后通过该节点来更新其他节点的距离估计值。算法保证了每个节点在被确定为最短路径节点后,其最短路径距离就是实际的最短路径距离。
需要注意的是,迪杰斯特拉算法要求图中的边权重必须为非负值,否则算法可能会产生错误的结果。此外,算法复杂度为 O(V^2)(V 为节点数),在稠密图中可能效率较低。可以使用优先队列(最小堆)来优化算法,使复杂度降低到 O(E + V log V),其中 E 为边数。
代码实现
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510;
int n,m;
int g[N][N];//g[ 1 ][ 2 ]是指从1节点指向2节点的距离,也可以表示不存在
int dist[N];//distance(距离)的缩写,代表每一个点到源点的距离
bool st[N];//state(状态)的缩写,当st[n]为true时说明这个点到源点的距离最小值就已经确定了
int dijkstra(){
memset(dist,0x3f,sizeof(dist));//存储每一个点到源点的距离
dist[1]=0;//源点到自己的距离为0
for(int i=0;i<n-1;i++){//其实这条语句唯一的作用就是循环n-1次(优化了)
//所以写成for(int i=0;i<n;i++)也可以,因为如果下面的语句循环了n-1次的话,那么所有点都能得到最小值了
//可以这么理解,每次循环都会确定一个最小值,还会再创造一个最小值(留给下一次循环去确定)
//当循环n-1次时,情况是已经确定了n-1个点的最小值了,还创造了一个最小值(此时还有1个点等着下一次去确定)
//那么就不需要下一次循环了,毕竟剩下的就一个点,在1个点的集合中知道有一个点是最小值,顺理成章了
//当然你想写成for(int i=0;i<n;i++)也能AC~~小声说~~
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j] and (t==-1 or dist[t]>dist[j])){
t=j;//!st[j]指的是最近距离还没有确定的点,and后面就是找符合!st[j]条件的距离最小的点
//这一个操作就是找到未确定最小值的 `点集`中的最小点,t==-1是当第一次遇到未确定~的点时能够被初始化
}
}
//(1)
for(int j=1;j<=n;j++){//现在找到t了,遍历一遍所有点,有一下几种情况
//1.j点和t点之间无连接,那么g[t][j]=0x3f3f3f3f,特别大,会被pass
//2.dist[j]<=dist[t]+g[t][j],源点到j点的距离,如果经过t后距离更长了,那么不考虑
//3.dist[j]<=dist[t]+g[t][j],,~~,经过t点距离更短了,那么修改dist[j]的值
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
st[t]=true;//当前t点已经把其余点全部遍历了一遍,此点变成确定距离为最小的点了,这条语句放在(1)处也能AC
}
if(dist[n]==0x3f3f3f3f){//当前点n没被修改,说明到不了点n,输出-1
return -1;
}else{
return dist[n];//易证
}
}
int main(){
cin>>n>>m;//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;
return 0;
}
优化版
在朴素版dijkstra中时间复杂度最高的寻找距离
最短的点O(n^2)可以使用最小堆优化。优化后的时间复杂度为O(mlogn)。重边会产生冗余但不影响。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010; // 把N改为150010就能ac
// 稀疏图用邻接表来存
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)
{
// 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
// 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),
// 并标记st为true,所以下一次弹出3+x会continue不会向下执行。
w[idx] = c;
e[idx] = y;
ne[idx] = h[x];
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;
}
以上代码源于acwing

浙公网安备 33010602011771号