单源最短路径——Dijksrta及其他

142 篇文章 0 订阅
73 篇文章 0 订阅

温馨提示:程序框中的注释比截图中全
原题
https://www.luogu.org/problemnew/show/P3371
https://www.luogu.org/problemnew/show/P4779
无负环用Dijkstra,有负环用SPFA。实现方式有邻接矩阵和邻接表,数据量太大时用邻接表。Dijkstra要比SPFA快。dijkstra(O(nlogn))、SPFA(O(KE))。考场一般不推荐用SPFA
!!!pat撑死就考个迪杰斯特拉,哦!老迪牛逼!
模板参考算法笔记,尽量用邻接表来做,邻接矩阵可能会被卡。
推荐方法零(最快)。
方法零:前向星+优先队列堆优化+Dijkstra
(方法零比方法四少了两个数组,少了初始化及其他步骤的时间,另外最重要的是,方法零的Dijkstra()中的优先队列同时存点号和权值,当该点权值发生变化时continue,这不是一种剪枝,而是在矫正路径,同时避免了废点的计算。方法四用标记数组tag[]标记是否访问,可能会发生错误:在访问u点后不能再次访问该点,但是有一条更短的路径可以到达u但此时已经不能再次访问u点进行计算,即当路径有后续更新时方法四不能再进行更新。方法零用优先队列每次弹出时作比较的方法避免了这种后续更新出错的情况,在队列空之前都可以进行更新。所以在数据更严密时,严格意义上方法四是错误的,方法一、方法二同理错误!)

#include<bits/stdc++.h>
#define re register
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 v,w,nxt;
}e[500010];
int head[100010],cnt=0;
inline void addedge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
//nxt记录的是同起点的之前一条边的cnt值,cnt唯一标识一条边 
	head[u]=cnt;//cnt相当于邻接表的下标 
}
int n,m,s;
int dis[100010];//更新最新的权值,与队列中的权值有可能不同
struct node{
	int u,d;//重载 
	bool operator<(const node&rhs) const{
	//固定格式 用地址进行操作 rhs代表第二个(右边的)数据 
	return d>rhs.d;}//从大到小排序即值大的优先级低,优先级高的值小
	//堆顶优先级最高,序列中靠前(左)的优先级低 
};

inline void Dijkstra(){
	for(re int i=1;i<=n;i++) dis[i]=2147483647;//重要的初始化 
	dis[s]=0;//重要的初始化 
	priority_queue<node> Q;//优先队列 (小根堆) 
	Q.push((node){s,0});// 向队列Q中压入node型元素,该元素中u=s,d=0 
	while(!Q.empty()){
		node fr=Q.top();Q.pop();
		//每次弹出的都是权值最小的点,但是队列中该点会有重复但权值是不同的即存在废点,
		//针对这种情况只有方法零进行了优化而方法四没有
		int u=fr.u,d=fr.d;
		if(d!=dis[u]) continue;//这不是一种剪枝,而是矫正路径,方法四中用的是标记数组tag[]
//到达该u点的路径被优化过,d(fr.d,曾经的dis[d]与现在的dis[d]值不相同)
//不能再用其进行计算 
		for(re int i=head[u];i;i=e[i].nxt){ 
	//nxt记录的是同起点的之前一条边的cnt值,是一个回溯的过程 
			int v=e[i].v,w=e[i].w;
			if(dis[u]+w<dis[v]){
				dis[v]=dis[u]+w;//松弛 
				Q.push((node){v,dis[v]});//入队 
			}
		}
	}
}
int main()
{
	n=read(),m=read(),s=read();
	for(re int i=1;i<=m;i++)
	{
		int x=read(),y=read(),z=read();
		addedge(x,y,z);//前向星 
	}
	Dijkstra();
	for(re int i=1;i<=n;i++) printf("%d ",dis[i]);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
方法一:邻接表+Dijkstra(算法笔记的模板未使用优先队列(堆优化),不能通过P4779)

#include<bits/stdc++.h>
using namespace std;
const int maxv=100010;//点的最大值 
const int INF=2147483647;
struct Node{
	int v,dis;
};
vector<Node>Adj[maxv];//用变长数组来实现邻接表
int n,m,s;
int d[maxv];//起点到达各点的最短距离
bool vis[maxv]={false};//标记某点是否被访问过
inline int read()//读入优化 
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x; 
}
inline void write(int x)//输出优化 
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline void Dijkstra(int s)
{
	//fill(d,d+maxv,INF);
	memset(d,INF,sizeof(d));
	d[s]=0;//重要初始化 
	for(register int i=1;i<=n;i++){
		int u=-1,MIN=INF;
		for(register int j=1;j<=n;j++){//找到未访问的顶点中d[u]最小的 ,就是路径已经最优的
		//方法零方法四在此处利用优先队列进行了堆优化
			if(vis[j]==false&&d[j]<MIN){
				u=j;MIN=d[j];
			}//顶点标号是从1开始的 **********很重要的一点 (考虑实际)
		}
		if(u==-1) return;//找不到小于INF的顶点,说明剩下的顶点和起点s不连通 
		vis[u]=true;
		for(register int j=0;j<Adj[u].size();j++){//vector数组下标是从0开始的 
			int v=Adj[u][j].v;
			if(vis[v]==false&&d[u]+Adj[u][j].dis<d[v]){
				d[v]=d[u]+Adj[u][j].dis;
			}
		}
	}
 } 
int main()
{
	int x;
	Node xx;
     cin>>n>>m>>s;
	 for(register int i=1;i<=m;i++)
	 {
	 	x=read();xx.v=read();xx.dis=read();
	 	Adj[x].push_back(xx);//Adj[]数组的元素变量类型为Node 
	 }	
	 /*for(i=1;i<=3;i++)
 	{
 		for(j=0;j<Adj[i].size();j++)//vector下标是从0开始的 
 		cout<<i<<Adj[i][j].v<<Adj[i][j].dis<<endl;
	 }
 	*/
    Dijkstra(s);
    for(register int i=1;i<=n;i++) write(d[i]),putchar(' ');//输出 
    return 0;
}

在这里插入图片描述
在这里插入图片描述
方法二:邻接表+SPFA
(SPFA可以处理有负权的情况,Dijkstra不能)

#include<bits/stdc++.h>
using namespace std;
const long long INF=2147483647;
const int maxn=100005;
const int maxm=500005;
int n,m,s,w,num_edge=0;
int dis[maxn],vis[maxn],head[maxm];
struct Edge
{
	int next,to,dis;
}edge[maxm];
inline int read()//读入优化 
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x; 
}
inline void write(int x)//输出优化 
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline void addedge(int from,int to,int dis)
{//前向星 
	edge[++num_edge].next=head[from];
	edge[num_edge].to=to;
	edge[num_edge].dis=dis;
	head[from]=num_edge;
}
inline void spfa()
{//spfa核心就是不停地入队出队 ,不止遍历一次邻接表 
	queue<int>q;
	for(register int i=1;i<=n;i++)
	{
		dis[i]=INF;
		vis[i]=0;
	}//初始化 
	q.push(s);dis[s]=0;vis[s]=1;//重要初始化
	while(!q.empty())
	{
		int u=q.front();
		q.pop();vis[u]=0;//出队标记 
		for(register int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(dis[v]>dis[u]+edge[i].dis)
			{
				dis[v]=dis[u]+edge[i].dis;
				if(vis[v]==0)//未入队 
				{
					vis[v]=1;//入队标记 
					q.push(v);
			}
		 } 
	 } 
}}
int main()
{
	n=read();m=read();s=read(); 
	for(register int i=1;i<=m;i++)
	{
		int f,g,w;
		f=read();g=read();w=read();
		addedge(f,g,w);
	}
	spfa();
	for(register int i=1;i<=n;i++)
	    if(s==i) write(0),putchar(' ');
	    else write(dis[i]),putchar(' ');
	return 0;
}

在这里插入图片描述
在这里插入图片描述
方法三:普通贪心

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005,maxm=500005,INF=2147483647;
long long dis[maxn];
int u[maxm],v[maxm],w[maxm],n,m,s,check;
//maxn是点的最大个数,maxm是边的最大个数 
int read()//读入优化 
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x; 
}
void write(int x)//输出优化 
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int main()
{
	int s,check;
	int x,y,z;
	n=read(),m=read(),s=read();
	for(int i=1;i<=m;i++)
	{
		u[i]=read(),v[i]=read(),w[i]=read();
	}
	memset(dis,INF,sizeof(dis);
	//for(int j=1;j<=n;j++)dis[j]=INF;//重要赋值 
	dis[s]=0;
	for(int k=1;k<=n-1;k++)//k-1条边 
	{
		check=0;
		for(int i=1;i<=m;i++)//遍历所有边 
		{
			if(dis[v[i]]>dis[u[i]]+w[i])
			{
				dis[v[i]]=dis[u[i]]+w[i];
				check=1;
			}
		}
		if(check==0) break;//没有边了,边的个数小于n-1 
	}
	for(int i=1;i<=n;i++) write(dis[i]),putchar(' ');
	return 0;
}

在这里插入图片描述
方法四(隐蔽性错误,洛谷P4779无法通过):前向星+优先队列堆优化+Dijkstra
(本方法用标记数组tag[]标记是否访问,可能会发生错误:在访问u点后不能再次访问该点,但是有一条更短的路径可以到达u但此时已经不能再次访问u点进行计算,方法零避免了这种情况)

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005,maxm=500005,inf=2147483647;
//maxn是点的最大个数,maxm是边的最大个数 
int n,m,cnt;
bool  tag[maxn];//访问标记 
int head[maxm],nxt[maxm],v[maxm];//v是对应边可以到达的点 
int w[maxm],dist[maxn];//w是权值,dist是源点到每个点的最短距离 
int read()//读入优化 
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x; 
}
void write(int x)//输出优化 
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
struct cmp//优先队列排序的一种固定格式 
{
	bool operator()(int x,int y)//重载操作符
    {
    	return dist[x]>dist[y];//>表示越往下越大即小根堆,故<是大根堆 
		}	
};
void addline(int x,int y,int z)//前向星就是邻接表 
{
	v[cnt]=y;//v存后驱 
	w[cnt]=z;//w存权值 
	nxt[cnt]=head[x];//nxt存前驱 
	head[x]=cnt++;
	return;
}
void dijkstra(int s)
{
	priority_queue<int,vector<int>,cmp>Q;//固定格式  Q存放点号 
	//Q中存的是点号,但是比较的是dist[]的值 
	while(!Q.empty()) Q.pop();//队列清空 初始化 
    dist[s]=0;//记录花费
	Q.push(s); //源点入队 
    while(!Q.empty())
    {
    	int x=Q.top();
    	Q.pop();
    	if(!tag[x])//剔除队列中的废点
    	{
    		tag[x]=true;//每个节点只访问一次,检索以该节点为起点的所有边 
    		for(int i=head[x];i!=-1;i=nxt[i])
    		{
    			int y=v[i];//y代表对应边可以到达的点
    			dist[y]=min(dist[y],dist[x]+w[i]);
    			Q.push(y);//类似于层序遍历 
			}
		}
    	
	}
}
int main()
{
	int s;
	n=read(),m=read(),s=read();
	memset(head,-1,sizeof(head));//重要的初始化 
	memset(tag,0,sizeof(tag));
	for(int i=1;i<=m;i++)//链式前向星 
	{
		int x,y,z;
		x=read(),y=read(),z=read();
		addline(x,y,z);
	}
	for(int j=1;j<=n;j++)dist[j]=inf;//重要赋值 
	dijkstra(s);
	for(int i=1;i<=n;i++) write(dist[i]),putchar(' ');
	return 0;
}

在这里插入图片描述
在这里插入图片描述

离字典,将起始节点的距离设为0,其他节点的距离设为无穷大 distances = {node: sys.maxsize for node in graph} distances[start] = 0 # 初始化已访问节点的集合和未访以下是使用问节点D的集ijkstra合 visited = set() unvisited算法求解最短路径的Python = set(graph) while unvisited: # 代码示例: ```python class D选择当前ijkstra距: def __init__(self, graph离最小的节点 , start, current goal): self.graph = graph # 邻接表_node = min(unvisited, key=lambda self node: distances[node]) # 更新.start = start当前节点的 # 起邻居节点点 self.goal =的距离 goal # 终点 for neighbor in graph self.open[current_node]: _list = {} if neighbor in # open 表 self.closed_list unvisited: new_distance = distances[current_node] + = {} graph[current_node][neighbor # closed 表 self.open_list[start] if new_distance] = < distances[neighbor]: 0.0 # 将 distances[neighbor] = new_distance # 将当前起点放入 open_list 中 self.parent = {节点标记start:为已访 None} 问,并从未访问集合中移除 visited.add # 存储节点的父子关系。键为(current_node) 子节点, unvisited值为父.remove(current_node) return节点。方便做最 distances def print后_path(dist路径的ances,回 start溯 self.min, end): _dis = None # 根 # 最短路径的长度 def shortest_path据距离字典和终点节点(self): while True: ,逆向 if self打印路径.open_list is path = [end None: ] print('搜索 current_node =失败 end while current_node !=, 结束!') break distance start: , min_node = for neighbor in graph min(zip[current_node]: if(self.open_list distances[current.values(), self_node] ==.open_list.keys distances[neighbor())) #] + graph 取出距[neighbor][current_node]: 离最小的节点 self path.open_list.pop.append(min_node)(neighbor) current_node = neighbor break path.reverse() # 将其从 open_list 中去除 self print.closed("_list[minShortest_node] = path from", distance # 将节点加入 closed start, "to", end,_list ":", "->".join(path)) # 示例 中 if min_node == self.goal: # 如果节点为图的邻接矩阵终点 self.min_dis = distance 表示 graph shortest = { _path = [ 'Aself.goal]': {'B': # 5, 'C 记录从': 终1}, 点回溯的路径 'B
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值