洛谷P4779 【模板】单源最短路径(标准版) [重载运算符,dijsktra堆优化]

链接:P4779
题目就不放了,就是最短路嘛(懒癌患者)


一道模板最短路题蓝色难度着实令人害怕,因为这道题要用邻接表+堆优化的dijsktra才能过
我也专门学习了一下堆优化的dijsktra。
在dijsktra的朴素算法中,我们要先循环每个点先找出最小的点再把与找出的这个点相连的点的最短路更新一下,这样的话我们加入了太多的冗余判断了,而堆优化就省去了循环每个点先找出最小的点 这个过程。
我们可以设置一个小根堆,堆中每个节点放置当前dis[i]和i,使得每次的根节点就是最小值,我们就可以直接把这个根节点直接取出,直接更新与这个点i相连的点最短路了。
(但是我懒
所以我选择使用优先队列这个算法,但优先队列我们在使用时,需要注意我们优先队列里每个队列空间里需要放两个值:dis[i]和i,所以这里要学习一种新的设置优先队列一个队列空间可以存放多个值的语法
我们可以把它想象成还是一个结构体类型
这里要首先要学习:重载运算符
对于网站博客上的重载运算符的定义实在是太深奥看不懂,我也是通过yj大佬的耐心回答下理解了这个玩意是干啥的,也就是字面的意思
我们可以重新定义一个原本存在的运算符新的运算形式
例如加号“+”
本来+号两边只能加两个相同类型的,但是我们可以自己重新定义
比如 定义成 long long + double
这样就定义了一个新的形式
一开始我有个问题,就是觉得这个运算符你定义新的运算形式之后,那计算机会怎么样去执行你的运算符到底是他原本编译器所定义的运算符运算形式还是你自己新定义的呢?
嗯,计算机会自己帮你判断的~

看一下这里使用优先队列的定义

    struct node
    {
    	int dis;
    	int pos;  //dis存储这个点的最短路,pos存的是这个点的下标
    	bool operator <(const node &x)const
    	{
    		return x.dis < dis;  //利用重载运算符
    		}
    };
    priority_queue <node> q;

那么这里的重载运算符是什么意思呢?
我们可以这样理解,在原本的优先队列内部操作里,就是使用"<"来定义大根堆小根堆,但是在原本优先队列中仅仅指的在每个队列空间只有一个元素时一个元素和另外一个元素的比较。那么这里我们使用了重载运算符,就是将这个优先队列内部操作里面的原本那样的操作变成现在每个队列空间里面有两个元素的时候根据其中一个元素的大小决定优先队列里顺序判断。
这里的操作有点类似于sort函数的写cmp函数为结构体排序的思想吧!

这里就是优先队列的定义问题,那么还有在每次的存储问题,这时候我们已经有了不止一个元素需要存储在一个空间里,所以存储时也需要有新的语法格式:(设当前这个点为s,dis[s]=0)

q.push(( node ) {dis[s],s});

注意!我们这里利用大括号中间的数必须按照定义时的顺序存储,如果反过来{s,dis[s]}那么这个时候计算机就会认为s是这个点的最短路长度,dis[s]是这个点的下标了。

在进行堆优化的dijsktra中,我们也需要进行改变,在一开始我们直接取出队头元素,判断这个元素是否已经在之前找到过标记过,如果没有则根据邻接表直接更新与其相连的点的最短路的值,并且把与其相连的点全部都放入队列中,这样就可以直接做到原本循环每个点进行找当前最小dis值的过程了。

代码:

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
int b[200001],n,m,s,top,dis[200001],h[200001];
struct CZP
{
	int next,to,di;
}a[200001]; 
struct node
{
	int dis;
	int pos;
	bool operator <(const node &x)const
	{
		return x.dis < dis;
		}
}; //定义新的优先队列
void cun(int from,int to,int dis)
{
	a[++top].next=h[from];
	a[top].to=to;
	a[top].di=dis;
	h[from]=top;
}  //邻接表
priority_queue <node> q;
int main()
{
	scanf("%d%d%d",&n,&m,&s);
	for (int i=1;i<=n;i++)
	h[i]=-1;
	for (int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		cun(x,y,z);
	}
	for (int i=1;i<=n;i++)
	dis[i]=1e9;  //这里不需要把dis[i]的值定义成起点到i点的路径长度,直接最大值即可(但是应该定义成路径长度没啥太大问题)
	dis[s]=0;
	q.push(( node ) {0,s});  //先把起点放入队列中
	while (!q.empty())
	{
		node x=q.top();
		q.pop();
		int k=x.pos;
		if (b[k]==1)
		continue;  //巧妙过程,省去了while语句,如果这个点已经被标记过了,那么就不会执行下面的语句继续执行上方三行语句直到找到没有标记过的
		b[k]=1;
int v=h[k];
		while (v!=-1)
		{
			if (dis[a[v].to]>dis[k]+a[v].di)
			{
				dis[a[v].to]=dis[k]+a[v].di;  //更新与这个点相连的点的dis最短路
				if (!b[a[v].to])
				q.push( ( node ) { dis[a[v].to],a[v].to} );  //并且加入队列,必须每次更新完都加一次,因为dis值在改变,可能在更新之前在优先队列中排名不在前面,但是更新之后优先队列排在第一位了
			}
			v=a[v].next;
		}
	}
	for (int i=1;i<=n;i++)
	printf("%d ",dis[i]);
	return 0;
}

还是有很多细节需要注意的,我在复习这道题目的时候,打的几乎完全一样,但是就是满不了分,百思不得其解的时候,发现打入队列那几行语句跟第一次打的空格少了几个,我就把小括号中括号任何间隙都加了空格=-=然后就过了awa

可能有很多是我自己理所当然了解的想法不太对,但是我觉得在当下,这种想法也还是可以支撑我能够知道堆优化的dijsktra每行啥意思吧=-


后期补充:
在hzwer大佬的教授下,知道了一种更轻松的
定义priority_queue <pair<int,int>,vector<pair <int ,int> >,greater<pair <int ,int > > > q;
这里使用了pair使得优先队列中的数据成对出现
其他的跟正常优先队列一样
但是在存入优先队列的过程中
改成这个样子
q.push(make_pair ( x, 0 ));
其他都一样了=-=%%%%

#include<queue>
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
priority_queue < pair< int,int >, vector<pair<int,int> > ,greater <pair<int,int> > >q;
int h[1000001],m,n,dis[1000001],top,b[1000001],sx;
struct CZP
{
	int next,to,dis;
}a[10000001];
void cun(int from,int to,int dis)
{
	a[++top].next=h[from];
	a[top].to=to;
	a[top].dis=dis;
	h[from]=top;
}
int main()
{
	scanf("%d%d%d",&n,&m,&sx);
	for (int i=1;i<=n;i++)
	h[i]=-1;
	for (int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		cun(x,y,z);
	}
	for (int i=1;i<=n;i++)
	dis[i]=1e9;
	dis[sx]=0;
	q.push( make_pair ( 0,sx ) );
	while (!q.empty())
	{
		int v=q.top().second,z=q.top().first;
		q.pop();
		if (b[v])
		continue;
		b[v]=1;
		int k=h[v];
		while (k!=-1)
		{
			if (dis[a[k].to]>z+a[k].dis)
			{
				dis[a[k].to]=z+a[k].dis;
				q.push(make_pair(dis[a[k].to],a[k].to));
			}
			k=a[k].next;
		}
	}
	for (int i=1;i<=n;i++)
	printf("%d ",dis[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值