Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)

之前说,这星期想要结束掉acwing上算法基础课的图论与数学知识两个章节,所以今天来整理一下最近学的dijkstra算法,文中部分图片取自acwing。其中的每一个算法我都会附上acwing中对应的模板题目,大家也可以去那边熟悉一下模板的写法

 前言 

大家好啊,这里是幸麟

 一名普通的大学牲,最近在学算法

本文栏目:幸麟同学的算法笔记

希望我的笔记对你算法学习有一些帮助

如果有错误欢迎各位同学批评指正

目录

算法介绍

朴素版本dijkstra 

 算法中主要的步骤

 各个部分的代码

逐张图解析算法过程

完整代码

堆优化dijkstra算法

算法中的主要步骤

各个部分的代码

完整代码


算法介绍

1.什么是dijkstra算法

迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

同时dijkstra算法主要用于解决单源最短路问题(边权为正数),其可以分为两种版本,两种版本各有用处,并不存在好坏之分,接下来我们用n代表点的数量,用m代表边的数量

朴素版本dijkstra     时间复杂度 O(n^2)     用于解决稠密图

堆优化dijkstra         时间复杂度O(mlogn) 用于解决稀疏图

可能这里有同学会问,什么是稀疏图,什么是稠密图

这里解答一下,稀疏图指的是图中边的数量远远小于点的数量,稠密图反之,边的数量远远大于点的数量

当然具体的我们也可以用时间复杂度来判断到底是运用哪一种算法

如果你不太了解时间复杂度是什么可以来看看下面这篇文章

手把手教你时间复杂度的计算方法

(图源自acwing)

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第1张图片

朴素版本dijkstra 

首先我们来介绍一下朴素版本的dijkstra

该算法主要用于点远远小于边的稠密图中

模板题链接:849. Dijkstra求最短路 I

 题目图片(图中便是一个稠密图)
Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第2张图片

 算法中主要的步骤

在此题中

我们可以运用dijkstra算法,主要的步骤有下面几步

①遍历每一个点,找到一个第一次被遍历的,距离1号点最近的一个点,我们把这个点的编号记为t

②运用t点去尝试更新1到其他点的距离

尝试更新的大致操作

假设我们有一条边,这个边的起点是t,终点是b,这条边有一个边权

如果点t到1号点的位置+边权<点b到1号点的距离,那么我们就把1号点到b的距离更新成更小的值

dist[t]+边权

③继续遍历其他没有被遍历过的点

我自己做了个动图,这样看起来更加直观一些,如果觉得快的话可以在下面找到逐张的图

(抱歉啊,因为不太熟悉软件就只能自己一张一张画出来,画的不太好,不知道有没有小伙伴知道比较好的作图软件,如果有的话,希望可以在评论区或者私信分享一下,谢谢你了)

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第3张图片

 各个部分的代码

接下来是代码实现

由于是稠密图,点的数量只有不到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

逐张图解析算法过程

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第4张图片

 Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第5张图片

 Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第6张图片

 Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第7张图片

 Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第8张图片

 t需要是距离1最近的点,3距离1为4,2距离1为2,所以选择编号2的点作为t

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第9张图片

 这边写错了,应该是t到3的边权为1

 Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第10张图片

好了朴素版的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算法

我们通过上面的算法可以发现,朴素的dijkstra算法是要遍历每一条边,找到最短的第一次遍历到的点t,然后对每一个点距离进行更新,其的时间复杂度为O(n^2),如果出现点众多的情况

例如10000个点,那么使用朴素的dijkstra算法必然会超时

所以人们针对了这种有众多点的情况用堆优化了dijkstra算法,使其的时间复杂度为O(mlogn)

这在解决有众多点而边数量偏少的稀疏图有奇效

题目链接:850. Dijkstra求最短路 II

题目图片:

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第11张图片

 这边同学们可以看到点的数量最高为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;

}

本篇文章到这里就结束了

创作不易,希望可以收获一个赞

Acwing算法笔记:求解最短路问题1(详细介绍朴素版和堆优化dijkstra算法,建议收藏)_第12张图片

 

你可能感兴趣的:(幸麟同学的算法笔记,算法,c++)