Dijkstra算法+堆优化求最短路

前言

由于该篇讲的是Dijkstra算法的优化,所以不会从头讲一遍Dijkstra算法,但会有较为详尽的注释来方便记忆堆优化后的Dijkstra算法。

正文

首先呢,大家都知道Dijkstra算法是用于求最短路的,但是不经优化的Dijkstra算法的时间复杂度是O(n*n)的,可以说是比较容易爆掉,但是呢我们可以用堆这个东西对Dijkstra算法进行优化,从而将时间复杂度降为O((m+n)*logn),可以解决无负边权的最短路问题。而对于有负边权的图,则需要用的SPFA算法。而Floyed算法又非常的好背,并且Floyed算法时间复杂度较高,仅能用于解决多源最短路问题。所以说,对于无负边权的单源最短路问题,使用Dijkstra算法;对于有负边权的单源最短路问题,则使用SPFA算法;对于多源最短路问题,则使用Floyed算法。

Dijksta算法在最短路问题中是应用最为广泛的算法之一,非常值得记忆和理解,需要大家灵活使用。

Dijkstra算法的堆优化自然是要用到堆的,而实现堆自然就要用到优先队列,所以说我们在跑Dijkstra算法之前,需要进行如下操作(顺便把链式前向星加边直接写进来):

struct edge
{
	int to,nxt,dis;
}e[MAXN<<1];
struct node//堆节点
{ 
    int u,d;//u表示当前点的标号,d表示当前点对应的最短距离 
    bool operator < (const node &x) const//重载运算符,把“< ”重新定义,定义为d较大的数更小 
	{
        return d>x.d;
    }
};
int head[MAXN],tt=0;
int dis[MAXN];//dis[u]存储的是源点到点u的最小距离 
void add(int x,int y,int z)
{
	e[++tt].nxt=head[x];
	head[x]=tt;
	e[tt].to=y;
	e[tt].dis=z;
}

其中用结构体实现链式前向星,用优先队列实现一个小根堆(这是显而易见的),dis数组则存储的是一个点到源点的最短距离。

那么该准备的东西准备好后,就可以开始跑Djlstra算法了,不过与以前的一样,我们要先处理dis数组。设源点为s,则有如下操作:

for(int i=1;i<=n;i++)
		dis[i]=INF;//除了初始点外其余点全部设为正无穷 
dis[s]=0;//初始点设为0 

然后呢,由于我们使用的是堆优化,所以我们自然也是要给堆内的元素进行赋值:

priority_queue<node>q;//用优先队列实现堆 
q.push((node){s,0});//s表示起点,0表示赋的初始最短路径 

这里队列q新加进去的{s,0}元素分别对应准备工作中处理堆节点部分的u和d。

总之这两部分就是初始化,将源点s的信息处理一下。

接下来,就要开始跑Dijkstra算法了,注释写的已经比较清楚了:

while(!q.empty())
{
	node first=q.top();//由于队列q为优先队列,所以first即为当前队列中的最小值,也就是正在处理的路径中的最小值 
	q.pop();//处理了当前的最小值,直接出队 
	int u=first.u;
	int d=first.d;
	if(d!=dis[u])//每次松弛操作后都要删掉对应节点,所以需要判断是否删除过一个点来简化操作 
		continue;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;//遍历与点u相连的点 
		int w=e[i].dis;//点u到点v的距离 
		if(dis[v]>dis[u]+w)//dis[u]+w就是一个与u相连的点v与点u间的距离,若当前点v与相连的点的最短距离较大,则将其更新为 
		{
			dis[v]=dis[u]+w;//更新距离 
			q.push((node){v,dis[v]});//由于点v被更新,所以当前的v所在路径更短,那么就让点v和对应的距离入队 
		}
	}
}

所以整个Dijkstra的过程就是这样的:

void Dijkstra()
{
	for(int i=1;i<=n;i++)
		dis[i]=INF;//除了初始点外其余点全部设为正无穷 
	dis[s]=0;//初始点设为0 
	priority_queue<node>q;//用优先队列实现堆 
	q.push((node){s,0});//s表示起点,0表示赋的初始最短路径 
	while(!q.empty())
	{
		node first=q.top();//由于队列q为优先队列,所以first即为当前队列中的最小值,也就是正在处理的路径中的最小值 
		q.pop();//处理了当前的最小值,直接出队 
		int u=first.u;
		int d=first.d;
		if(d!=dis[u])//每次松弛操作后都要删掉对应节点,所以需要判断是否删除过一个点来简化操作 
			continue;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;//遍历与点u相连的点 
			int w=e[i].dis;//点u到点v的距离 
			if(dis[v]>dis[u]+w)//dis[u]+w就是一个与u相连的点v与点u间的距离,若当前点v与相连的点的最短距离较大,则将其更新为 
			{
				dis[v]=dis[u]+w;//更新距离 
				q.push((node){v,dis[v]});//由于点v被更新,所以当前的v所在路径更短,那么就让点v和对应的距离入队 
			}
		}
	}
}

最后呢放一道模板题P4779 【模板】单源最短路径(标准版),直接套优化后的Dijkstra即可。下面放上我的AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<stack>
#include<queue>
#include<vector>
#include<map>
#include<cstdlib>
using namespace std;
#define ll long long
#define ull unsigned long long
int read()
{
	int now=0,nev=1; 
	char c=getchar();
	while(c<'0' || c>'9') 
	{ 
		if(c=='-') 
			nev=-1; 
		c=getchar();
	}
	while(c>='0' && c<='9') 
	{ 
		now=(now<<1)+(now<<3)+(c&15); 
		c=getchar(); 
	}
	return now*nev;
}
const int MAXN=1e5+10;
const int INF=1e9;
int n,m,s; 
int a[MAXN];
struct edge
{
	int to,nxt,dis;
}e[MAXN<<1];
struct node//堆节点
{ 
    int u,d;//u表示当前点的标号,d表示当前点对应的最短距离 
    bool operator < (const node &x) const//重载运算符,把“< ”重新定义,定义为d较大的数更小 
	{
        return d>x.d;
    }
};
int head[MAXN],tt=0;
int dis[MAXN];//dis[u]存储的是源点到点u的最小距离 
void add(int x,int y,int z)
{
	e[++tt].nxt=head[x];
	head[x]=tt;
	e[tt].to=y;
	e[tt].dis=z;
}
void Dijkstra()
{
	for(int i=1;i<=n;i++)
		dis[i]=INF;//除了初始点外其余点全部设为正无穷 
	dis[s]=0;//初始点设为0 
	priority_queue<node>q;//用优先队列实现堆 
	q.push((node){s,0});//s表示起点,0表示赋的初始最短路径 
	while(!q.empty())
	{
		node first=q.top();//由于队列q为优先队列,所以first即为当前队列中的最小值,也就是正在处理的路径中的最小值 
		q.pop();//处理了当前的最小值,直接出队 
		int u=first.u;
		int d=first.d;
		if(d!=dis[u])//每次松弛操作后都要删掉对应节点,所以需要判断是否删除过一个点来简化操作 
			continue;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;//遍历与点u相连的点 
			int w=e[i].dis;//点u到点v的距离 
			if(dis[v]>dis[u]+w)//dis[u]+w就是一个与u相连的点v与点u间的距离,若当前点v与相连的点的最短距离较大,则将其更新为 
			{
				dis[v]=dis[u]+w;//更新距离 
				q.push((node){v,dis[v]});//由于点v被更新,所以当前的v所在路径更短,那么就让点v和对应的距离入队 
			}
		}
	}
}
int main()
{
	memset(head,0,sizeof(head));
	n=read(),m=read(),s=read();
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		x=read(),y=read(),z=read();
		add(x,y,z);
	}
	Dijkstra();
	for(int i=1;i<=n;i++)
		printf("%d ",dis[i]);
	return 0;
}

总结

Dijkstra算法极为重要,需要也应该记忆堆优化后的Dijkstra算法。堆优化后的Dijkstra算法流程大致如下:先预处理出来dis数组,再定义一个优先队列来实现堆,并将源点的信息加入优先队列中,从源点开始通过bfs的方式来处理最短路。处理时,由于我们实现的堆是小根堆,所以说队首元素的路径一定是最小的,那么我们就可以先将队首元素给取出来,并单独处理这个元素,比如我们定义这个队首元素为first,接下来我们再将这个元素的点和最小距离定义出来,方便一会儿的使用。另外我们可以做一个剪枝,由于每次处理完一个点后我们要把它删掉,所以说我们可以特判一下点u的最短路径是否不等于堆中维护的最短路径。接下来就是直接对以该点开始遍历相邻的几个顶点,并看看需不需要更新dis数组所记录的最短路径。bfs部分的简化表述如下:先处理当前点并记录对应信息,再遍历相邻的几个点,并处理相邻几个点的信息,来更新最短路径。

其实Dijkstra算法在理解后是非常容易记忆的。完结撒花(逃

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值