(图论)Dijkstra及其堆优化的写法和应用

(图论)Dijkstra算法及其堆优化的写法和应用

  Dijkstra(以下简称为dij),是最短路算法中最为重要的算法(相比于O(n3)的弗洛伊德和莫名死掉的SPFA),dij的优点在于快且大多数情况下比较稳定(尤其是单源最短路)。

  以下是有关图解

  如图,一开始,所有的点均为圆点,此时dis[1]=0,其余为MAX_INT

第一轮找到dis[1]最小,所以将1标为方点,得到新的dis[2]=2,dis[3]=4,dis[4]=7

第二轮找到dis[2]最小,所以将2标为方点,得到新的dis[3]=3,dis[5]=4

第三轮找到dis[3]最小,所以将3标为白点,得到新的dis[4]=4

最后两轮循环再将点4和点5变成白点即可。
所以最后dis数组

i       	1  2  3  4  5 
dis[i]      0  2  3  4  4

例题:
题目描述
给定一个 N 个点,M 条有向边的带非负权图,请你计算从 S 出发,到每个点的距离。
数据保证你能从 S 出发到任意点。
输入格式
第一行为三个正整数 N, M, SN,M,S。 第二行起 MM 行,每行三个非负整数 ui, vi, wi,表示从 ui到 vi有一条权值为 wi的边。
输出格式
输出一行 N 个空格分隔的非负整数,表示 S 到每个点的距离。
输入输出样例
输入 #1                 输出 #1

4 6 1                    0 2 4 3
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

说明/提示
1≤N≤100000;
1≤M≤200000;
S=1;
1≤ui,vi≤N;
wi≤109
∑wi≤109
  乍一看,有固定的的起点和终点,dij是个不错的选择,小蒟蒻偷笑
  于是小蒟蒻开始动手写dij,代码如下(朴素的dij,本题仅供参考)

int s=begin;
	dis[s]=0;
	while(b[s]){
		int minn=2147483647;
		b[s]=false;
		for(int i=pre[s];i!=0;i=ed[i].en){
			if(b[ed[i].be]&&dis[ed[i].be]>dis[s]+ed[i].w)
				dis[ed[i].be]=dis[s]+ed[i].w;
		}
		for(int i=1;i<=n;i++){
			if(dis[i]<minn&&b[i]){
				minn=dis[i];
				s=i;
			}
		}

  确实没毛病,毕竟是写(chao)了(guo)这么多遍的代码,写错了岂不是很尴尬。于是乎我兴致高昂的提交了这份代码(当然这不是完整代码),等待评测姬的回复。过了大概十几秒,评测姬告诉我:TLE(超时),而且十个测试点,每一个都超时。果然还是小蒟蒻,写的代码都那么弱。
  那还有什么改进方案呢,这个数据范围,弗洛伊德和 死去的 SPFA肯定是不行的了,那还能怎么办???
  不急,我们的主角还没有登场呢:堆优化。
  堆优化的本质就是用一个优先队列维护当前所有点离源点的距离,每次弹出最短的,直到出现圆点为止。这样就省去了O(n)的寻找,总时间复杂度O(n+mlogn)(优先队列的复杂度为mlogn)。 至于一些dalao说的稠密图,蒟蒻在这里暂且不考虑了(手动滑稽)
  代码如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#define MAX 0x7fffffff
using namespace std;

inline int read() {//读入优化 
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
    return X*w;
}

struct edge{ 
	int son,bro,w;
}ed[200005];//边 

int pre[100005],temp;

inline void add(int a,int b,int c){//建图 
	ed[++temp].son=b;
	ed[temp].w=c;
	ed[temp].bro=pre[a];//兄弟节点的位置 
	pre[a]=temp;
}

int dis[100005],s;

struct node{
	int dis,pos;//pos点的位置 ,dis到该点的最短距离 
	bool operator <(const node &a)const{//重载小于号,用于优先队列 
		return a.dis<dis;
	}
};

priority_queue<node> que;//优先队列,堆 
int n,m;
bool b[100005];//判断是否走过 
int begin;

inline void work(){
	s=begin;
	dis[s]=0;//第一个点到第一个点的距离为零 
	que.push((node){0,s});
	while(!que.empty()){
		node temp=que.top();
		que.pop();
		s=temp.pos;
		if(b[s]) continue;//走过,这句是优化的关键 
		b[s]=true;//走过了 
		for(register int i=pre[s];i;i=ed[i].bro){//访问兄弟节点 
			int y=ed[i].son;//子节点的位置 
			if(dis[y]>dis[s]+ed[i].w){
				dis[y]=dis[s]+ed[i].w;//最短路 
				if(!b[y])//没走过 
				que.push((node){dis[y],y});//入队 
			}
		}
	}
}

int main(){
	n=read();m=read();begin=read();
	for(int i=1;i<=n;i++) dis[i]=MAX;//置为最大值 
	memset(b,false,sizeof(b));//没有访问过 
	int x,y,v;
	for(int i=1;i<=m;i++){
		x=read();y=read();v=read();
		add(x,y,v);
		//add(y,x,v)如果是无向图的话,此题为有向图 
	}
	work();//进行dij 主程序
	for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
	return 0;
}

  这是蒟蒻第一次写解题报告,希望dalao们勿喷鸭。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值