之前说,这星期想要结束掉acwing上算法基础课的图论与数学知识两个章节,所以今天来整理一下最近学的dijkstra算法,文中部分图片取自acwing。其中的每一个算法我都会附上acwing中对应的模板题目,大家也可以去那边熟悉一下模板的写法
前言
大家好啊,这里是幸麟
一名普通的大学牲,最近在学算法
本文栏目:幸麟同学的算法笔记
希望我的笔记对你算法学习有一些帮助
如果有错误欢迎各位同学批评指正
目录
算法介绍
朴素版本dijkstra
算法中主要的步骤
各个部分的代码
逐张图解析算法过程
完整代码
堆优化dijkstra算法
算法中的主要步骤
各个部分的代码
完整代码
1.什么是dijkstra算法
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
同时dijkstra算法主要用于解决单源最短路问题(边权为正数),其可以分为两种版本,两种版本各有用处,并不存在好坏之分,接下来我们用n代表点的数量,用m代表边的数量
朴素版本dijkstra 时间复杂度 O(n^2) 用于解决稠密图
堆优化dijkstra 时间复杂度O(mlogn) 用于解决稀疏图
可能这里有同学会问,什么是稀疏图,什么是稠密图
这里解答一下,稀疏图指的是图中边的数量远远小于点的数量,稠密图反之,边的数量远远大于点的数量
当然具体的我们也可以用时间复杂度来判断到底是运用哪一种算法
如果你不太了解时间复杂度是什么可以来看看下面这篇文章
手把手教你时间复杂度的计算方法
(图源自acwing)
首先我们来介绍一下朴素版本的dijkstra
该算法主要用于点远远小于边的稠密图中
模板题链接:849. Dijkstra求最短路 I
在此题中
我们可以运用dijkstra算法,主要的步骤有下面几步
①遍历每一个点,找到一个第一次被遍历的,距离1号点最近的一个点,我们把这个点的编号记为t
②运用t点去尝试更新1到其他点的距离
尝试更新的大致操作
假设我们有一条边,这个边的起点是t,终点是b,这条边有一个边权
如果点t到1号点的位置+边权<点b到1号点的距离,那么我们就把1号点到b的距离更新成更小的值
dist[t]+边权
③继续遍历其他没有被遍历过的点
我自己做了个动图,这样看起来更加直观一些,如果觉得快的话可以在下面找到逐张的图
(抱歉啊,因为不太熟悉软件就只能自己一张一张画出来,画的不太好,不知道有没有小伙伴知道比较好的作图软件,如果有的话,希望可以在评论区或者私信分享一下,谢谢你了)
接下来是代码实现
由于是稠密图,点的数量只有不到500,所以采用邻接矩阵p来存储各点到其他点的距离
例如p[1][2]存储的就是1号点到2号点的距离
#include
#include
using namespace std;
int n,m;
const int N=520;
int p[N][N];
int dist[N];
int st[N];
同时我们要对初始化一下我们的邻接矩阵p,把其中每一个元素初始化成一个我们认定的最大值
0x3f3f3f3f,如果边权这个值就不可能对距离进行更新,所以其的实际意义可以看成这两个点之间没有边连接
memset(p,0x3f,sizeof(p));
接下来的对输入的数据进行处理,因为可能有重边所以这里只取最小的重边作为唯一的边
cin>>n>>m;
int a,b,c;
for(int i=0;i>a>>b>>c;
p[a][b]=min(p[a][b],c);
}
dijkstra();
接下来是核心:朴素dijkstra算法
①将1到所有的点的距离dist全部初始化为最大值0x3f3f3f3f
如何有点没有任何一条边可以直接或者间接与1号点相连,则会一直保持这个最大值
所以如果dist[n]==0x3f3f3f3f的话那么表明1不可能到达n点,这时候我们就可以输出-1结束程序了
②开始对每一个点n的遍历,找到t点后更新距离(详情可以参考上面的动图)
不过好像有一点快
我在代码下面放一下动图的每一张图
void dijkstra()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i
t需要是距离1最近的点,3距离1为4,2距离1为2,所以选择编号2的点作为t
这边写错了,应该是t到3的边权为1
好了朴素版的dijkstra算法到这里结束了,下面是完整代码
#include
#include
using namespace std;
int n,m;
const int N=520;
int p[N][N];
int dist[N];
int st[N];
void dijkstra()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;i>n>>m;
int a,b,c;
for(int i=0;i>a>>b>>c;
p[a][b]=min(p[a][b],c);
}
dijkstra();
return 0;
}
我们通过上面的算法可以发现,朴素的dijkstra算法是要遍历每一条边,找到最短的第一次遍历到的点t,然后对每一个点距离进行更新,其的时间复杂度为O(n^2),如果出现点众多的情况
例如10000个点,那么使用朴素的dijkstra算法必然会超时
所以人们针对了这种有众多点的情况用堆优化了dijkstra算法,使其的时间复杂度为O(mlogn)
这在解决有众多点而边数量偏少的稀疏图有奇效
题目链接:850. Dijkstra求最短路 II
题目图片:
这边同学们可以看到点的数量最高为150000,朴素版必然超时
所以这里要用堆优化版本
①遍历每一条边,或者说优先遍历距离编号为1的点最近的点的边
②如果可以更新边的终点到1的距离,那么就把这个点加入优先队列中
优先队列会自动把距离1号点最近的点放在队顶
例如我们把一条边的起点叫做a,终点叫做b,这条边直接连接了a,b两点
如果1号点的点到a点的距离+这条边的权值<1号点到b点距离。
即dist[a]+边权
我们把b点和新的距离加入优先队列中
③优先队列遍历完成,程序结束
如果画图模拟的话是与朴素版一模一样的
接下来看看代码吧
准备阶段
创建类型为pii的小根堆,其中pair中的first用来存点到1号点的距离,second用来存点的编号
我们运用优先队列就是为了直接找到距离1最近的点,然后遍历其边
dist用来存距离
st用来判断该点是否被遍历过
#include
#include
#include
#include
using namespace std;
typedef pair pii;
priority_queue, greater > q;
const int N = 150010;
int e[N], ne[N], w[N], h[N], idx;
int n;
int m;
int dist[N];
int st[N];
因为点的数量很多,不可能开二维数组,所以这里的边权我们用邻接表来存
不太清楚邻接表的话可以去网上搜一搜其他同学的博客,这里就先不介绍了哈~
void add(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
处理接收的数据
这里给h数组初始化是对邻接表处理的必要步骤
memset(h, -1, sizeof(h));
cin >> n >> m;
int a, b, c;
for (int i = 0; i < m; i++)
{
cin >> a >> b >> c;
add(a, b, c);
}
dijkstra();
最为核心的堆优化版本
其实具体的运行是与朴素版差不多的
同样初始化dist数组为最大值
朴素版是遍历点寻找最近的距离1号点最近的点,然后尝试更新各点距离
堆优化版是找到距离1号点最近的点,然后遍历以这点为起点的所有的边,然后尝试更新该边终点的点的距离
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
q.push({ 0,1 });
while (q.size() > 0)
{
int ver = q.top().second;
int distance = q.top().first;
q.pop();
if (st[ver] != 0)
{
continue;
}
st[ver] = 1;
for (int i = h[ver]; i != -1; i = ne[i])
{
int t = e[i];
if (dist[t] > distance + w[i])
{
dist[t] = distance + w[i];
q.push({ distance + w[i],t });
}
}
}
if (dist[n] == 0x3f3f3f3f)
{
cout << -1;
return;
}
cout << dist[n];
}
#include
#include
#include
#include
using namespace std;
typedef pair pii;
priority_queue, greater > q;
const int N = 150010;
int e[N], ne[N], w[N], h[N], idx;
int n;
int m;
int dist[N];
int st[N];
void add(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
void dijkstra()
{
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
q.push({ 0,1 });
while (q.size() > 0)
{
int ver = q.top().second;
int distance = q.top().first;
q.pop();
if (st[ver] != 0)
{
continue;
}
st[ver] = 1;
for (int i = h[ver]; i != -1; i = ne[i])
{
int t = e[i];
if (dist[t] > distance + w[i])
{
dist[t] = distance + w[i];
q.push({ distance + w[i],t });
}
}
}
if (dist[n] == 0x3f3f3f3f)
{
cout << -1;
return;
}
cout << dist[n];
}
int main()
{
memset(h, -1, sizeof(h));
cin >> n >> m;
int a, b, c;
for (int i = 0; i < m; i++)
{
cin >> a >> b >> c;
add(a, b, c);
}
dijkstra();
return 0;
}
本篇文章到这里就结束了
创作不易,希望可以收获一个赞