Dijkstra----图论最短路算法/Dijkstra堆/优先队列优化

Dijkstra是图论最常用的最短路算法,floyed复杂度是O(n^3),显然是超时的,Bellman-Ford也是超时问题,SPFA更不用说,已经死了 菊花图给你拿捏的死死的

所以万众瞩目的Dijkstra单源最短路径你怎么能不会呢,我也不会

先从未优化的dij开始讲起

大概的思路就是讲=将图上的点分为两类,一类是找到最短路的节点,一类是未找到最短路的节点(建议用一个bool数组就行,不同并查集)

那么讲讲准备,首先未优化的dij是通过;邻接矩阵存储的图,所以我们需要一个数组A[][]表示i和j直接连接的距离,一个path数组记录i的前驱且是最短路径上的前驱)

准备的代码如下

const int inf=999999;
const int maxn=101;
int a[maxn][maxn],d[maxn],path[maxn];//a[i][j]的距离 (直接链接) d[i](i到原点的最短距离) path[i](i的前驱并且是最短路的前驱)
bool f[maxn];//分类A,B两类
int n,m,start;

main函数其实是存图的过程,简单讲下

对于n个点m条边的图,操作如下

int main(){
	cin>>n>>m>>start;
	int x,y,w;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		if(j==i)a[i][j]=0;//自己到自己的距离为0
		else a[i][j]=inf;//初始化
	}
	for(int i=1;i<=m;i++){
		cin>>x>>y>>w;
		a[x][y]=w;//无向图
		a[y][x]=w;//无向图
	}
	Dijkstra(start);
	for(int i=1;i<=n;i++){
			bfs(i);//为了倒序输出
			cout<<d[i]<<endl;
	}
	return 0;
}

接下来就是核心—dij了,先初始化然后找起点,跑一遍for更新(d[k]+a[k][j]
void Dijkstra(int s){
	for(int i=1;i<=n;i++){ //初始化
		d[i]=inf;
		f[i]=false;
	}
	d[s]=0;
	for(int i=1;i<=n;i++){
		int mind=inf;
		int k;
		for(int j=1;j<=n;j++){//寻找起点start
			if(!f[j]&&d[j]<mind){
				mind=d[j];
				k=j;
			}
		}
		if(mind==inf)break;//粗略的理解为无起点或起点越界
		f[k]=true;
		for(int j=1;j<=n;j++){
			if(!f[j]&&d[k]+a[k][j]<d[j]){//若当前这个点到原点的距离小于k到原点的距离+k到j的距离---->更新
				d[j]=d[k]+a[k][j];//更新
				path[j]=k;//记录前驱
			}
		}
	}
}

完整代码

#include
using namespace std;
const int inf=999999;
const int maxn=101;
int a[maxn][maxn],d[maxn],path[maxn];//a[i][j]的距离 (直接链接) d[i](i到原点的最短距离) path[i](i的前驱并且是最短路的前驱)
bool f[maxn];//分类A,B两类
int n,m,start;
void Dijkstra(int s){
	for(int i=1;i<=n;i++){ //初始化
		d[i]=inf;
		f[i]=false;
	}
	d[s]=0;
	for(int i=1;i<=n;i++){
		int mind=inf;
		int k;
		for(int j=1;j<=n;j++){//寻找起点start
			if(!f[j]&&d[j]<mind){
				mind=d[j];
				k=j;
			}
		}
		if(mind==inf)break;//粗略的理解为无起点或起点越界
		f[k]=true;
		for(int j=1;j<=n;j++){
			if(!f[j]&&d[k]+a[k][j]<d[j]){//若当前这个点到原点的距离小于k到原点的距离+k到j的距离---->更新
				d[j]=d[k]+a[k][j];//更新
				path[j]=k;//记录前驱
			}
		}
	}
}
int bfs(int i){//倒序输出最短路
	if(i!=start)bfs(path[i]);
	cout<<i<<" ";
}
int main(){
	cin>>n>>m>>start;
	int x,y,w;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		if(j==i)a[i][j]=0;//自己到自己的距离为0
		else a[i][j]=inf;//初始化
	}
	for(int i=1;i<=m;i++){
		cin>>x>>y>>w;
		a[x][y]=w;//无向图
		a[y][x]=w;//无向图
	}
	Dijkstra(start);
	for(int i=1;i<=n;i++){
			bfs(i);
			cout<<d[i]<<endl;
	}
	return 0;
}



#------------------------------------------------------------

然而很容易发现,这他妈的 不是O(n^2)吗

但是,它有优化,先想想,我们为什么这么慢,首先就是存储方式,邻接矩阵的存储方式只适合稠密图,导致查询贼麻烦,每次都找一次起点,对于每一个起点跑一次,艹人麻了

还有我们的分组用一个布尔数组只起到标记的作用,不能查看我排完序的dis最短的节点

所以就有了堆优化的dij(其实存储方式也变了,改成邻接表了)

先从改动较大的地方起

1.邻接表的存储方式带来的极大的方便,不论稀疏还是稠密都能解决

代码

void add_edge(int x,int y,int z){
	e[++tot].u=x,e[tot].v=y,e[tot].w=z;
	e[tot].next=head[x];
	head[x]=tot;
}

优先队列中已经存在一个同样编号的二元组(即第二关键字相同),我们没有办法删去它,也没有办法更新它。那么在我们的队列和程序运行的时候,一定会出现bug。

怎么办呢??

我们在进入循环的时候就开始判断:如果有和堆顶重复的二元组,就直接pop掉,成功维护了优先队列元素的不重复。

完整代码

#include
#include
#include
#include
#define INF  20005
using namespace std;
int dis[INF],vis[INF],s,head[INF],tot=0;
struct edge{
	int u,v,w,next;
}e[INF];
struct node{
	int id,dis;
	bool operator<(const node x)const{
		return dis>x.dis;
	}
};
priority_queue<node> q;
void add_edge(int x,int y,int z){
	e[++tot].u=x,e[tot].v=y,e[tot].w=z;
	e[tot].next=head[x];
	head[x]=tot;
}
void di(int s){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[s]=0;
	q.push(node{s,dis[s]});
	while(!q.empty()){
		int t=q.top().id;
		q.pop();
		if(vis[t])continue;
		vis[t]=true;
		for(int i=head[t];i;i=e[i].next){
			if(dis[e[i].v]>dis[t]+e[i].w){
				di
				s[e[i].v]=dis[t]+e[i].w;
				q.push(node{e[i].v,dis[e[i].v]});
			}
		}
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		if(x==y)continue;
		add_edge(x,y,z);
		add_edge(y,x,z);
		 
	}
	di(1);
	if(dis[n]!=0x3f)cout<<dis[n];
	else cout<<"-1";
	return 0;
}

别问我什么时候更新了艹,我自己都不知道!

你可能感兴趣的:(c++,图,图论,算法,数据结构,c++,数组)