【算法笔记】最短路问题

最短路问题

  • 单源最短路

    从1号点到其他点的最短路

    • 所有边的权值都是正数,这样的最短路
      • 朴素版的Dijkstra算法 O(n^2)(稠密图尽量使用朴素版本的Dijkstra算法)
      • 堆优化版的Dijkstra算法 o(mlogn)
    • 存在负权值的边
      • Bellman-Ford o(nm)(经过的边数小于等于K)
      • SPFA 平均时间复杂度是线性的。(一般情况下)
  • 多源最短路 Floyd算法 o(n^3)

  • 最短路算法的考点:建图。如何把原来的问题抽象成一个最短路的问题。

请添加图片描述

朴素版的Dijkstra算法:

step1: dis[1]=0,dis[i]=无穷 S表示的是当前以及确定的最短距离的点。

step2:for i = 0~n:其中t表示不在S中的。距离最近的点。

step3:把这个t假如到s中去

step4:用t来更新其他所有的点。

#include<iostream>
#include<algorithm>
#include<cstring> 
using namespace std;
int n;
int m;
const int N = 510;
int g[N][N];
int dis[N];
int s[N];
int dijkstra(){
	for(int i=0;i<n;++i){
		int t=-1;
		for(int j=1;j<=n;++j){
			if(!s[j]&&(t==-1||dis[j]<dis[t])){
				t=j;
			}
		}
		s[t]=1;
		for(int j = 1;j<=n;++j){	
			dis[j]=min(dis[j],dis[t]+g[t][j]);
		}
	}
	if(dis[n]==0x3f3f3f3f)return -1;
	else return dis[n];
}
int main(){
	memset(dis,0x3f,sizeof dis);
	dis[1]=0;
	memset(g,0x3f,sizeof g);
	cin>>n>>m;
	for(int i=0;i<m;++i){
		int a;
		int b;
		cin>>a>>b;
		int c;
		cin>>c;
		g[a][b]=min(g[a][b],c);	
	}
	cout<<dijkstra()<<endl;		
	return 0;
}

bellman-ford算法

算法思路:

bellmanford算法主要是基于边的。对于K个顶点的图,要进行K-1次更新。这里我们如果对k赋予一个含义的话,那就是从起点到各个顶点经过最多k条边的最短路径。这种情况下就要使用到bellman-ford算法,

这里值得注意的是,如果我们赋予了k这样的含义,那么每次用来更新的数组就应该是我们保留了的上一个数组,不能直接在更新后的数组上进行操作,否则会出现串链的问题。

例题:acwing853

以下代码的输入是n,m,k

n表示顶点个数

m表示边数

k表示最多经过的边数,

 #include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int m;
int k;
const int N = 100010;
int dis[N];
int last[N];
struct edge{
	int a;
	int b;
	int w;
}Edge[N];
void BellmFord(){
	for(int i=0;i<k;++i){
		memcpy(last,dis,sizeof dis);
		
		for(int j=1;j<=m;++j){
			edge e = Edge[j];
			dis[e.b]=min(dis[e.b],last[e.a]+e.w);
		}
	}
}

int main(){
	memset(dis,0x3f,sizeof dis);
	dis[1]=0;
	cin>>n>>m>>k;
	for(int i=1;i<=m;++i){
		int a;
		int b;
		int w;
		cin>>a>>b>>w;
		Edge[i].a=a;
		Edge[i].b=b;
		Edge[i].w=w;
	}
	BellmFord();
	if(dis[n]>0x3f3f3f3f/2)cout<<"impossible"<<endl;
	else cout<<dis[n]<<endl; 
	return 0;
}

SPFA算法

根据bellman-ford算法,会出现这样的一个问题。那就是说每次都要对所有的点进行一次更新,显然只有当前点被更行后,那他后续的节点才需要被更新,否则这样的更新是没意义的。争对这个问题,spfa算法引进了一个队列来保存当前更新过后的点。用更新过后的点来更新其后面的点。代码如下:

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int n;
int m;
//这里的idx指的是边的编号。 
int idx=1;
const int N =100010;
int dis[N];
int head[N];
int ne[N];
int e[N];
int val[N];
queue<int>q;
void add(int a,int b,int c){
	e[idx]=b;
	val[idx]=c;
	ne[idx]=head[a];
	head[a]=idx++;
}
int s[N];

int spfa(){
	
	while(!q.empty()){
		//cout<<"死循环了吗"<<endl;
		int  p = q.front();
		q.pop();
		s[p]=0;
		for(int i = head[p];i!=-1;i=ne[i]){
			int j = e[i];
			if(dis[j]>dis[p]+val[i]){
				dis[j]=dis[p]+val[i];
				if(!s[j])
				{
				
				q.push(j);
				s[j]=1;
			}
			}
		}
	}
	
 return dis[n];	
} 

int main(){
	memset(dis,0x3f,sizeof dis);
	memset(head,-1,sizeof head);
//	s[1]=1;
	dis[1]=0;
	cin>>n;
	cin>>m;
	for(int i=0;i<m;++i){
		int a;
		int b;
		cin>>a>>b;
		int c;
		cin>>c;
		add(a,b,c);
	}
	q.push(1);
	int t = spfa();
	if(t==0x3f3f3f3f){
		cout<<"impossible"<<endl;
	}else{
		cout<<t<<endl;
	}

	return 0;
} 

SPFA求负权回路

bellman-ford算法和spfa都可以用来求负权回路。基本思想如下:如果从A到B之间的最短距离在第k次遍历得到更新,说明有一条从A到B的最短距离只需要经过k条边。如果第n次(n为节点数) 更新后,仍然有节点被更新了,那么说明有环,且是负环。(因为n个节点最多有n-1个边,否则就有环,有环的情况下如果最短路得到了更新,那一定是有负环)

这里开辟一个数组,用于记录从起点到当前点需要经过的边数。当这个边数大于等于n的时候,就说明一定有负权回路。

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int n;
int m;
//这里的idx指的是边的编号。 
int idx=1;
const int N =300010;
int dis[N];
int head[N];
int ne[N];
int e[N];
int val[N];
int cnt[N];
queue<int>q;
void add(int a,int b,int c){
	e[idx]=b;
	val[idx]=c;
	ne[idx]=head[a];
	head[a]=idx++;
}
int s[N];
int spfa(){
	while(!q.empty()){
		//cout<<"死循环了吗"<<endl;
		int  p = q.front();
		q.pop();
		s[p]=0;
		for(int i = head[p];i!=-1;i=ne[i]){
			int j = e[i];
			if(dis[j]>dis[p]+val[i]){
				dis[j]=dis[p]+val[i];
				cnt[j]=cnt[p]+1;
				if(cnt[j]>=n){
					
					return 1;
				}
				if(!s[j])
				{
				q.push(j);
				s[j]=1;
			}
			}
		}
	}
 return 0;	
} 
int main(){
	memset(dis,0x3f,sizeof dis);
	memset(head,-1,sizeof head);
	dis[1]=0;
	cin>>n;
	cin>>m;
	for(int i =1;i<=n;++i){
		q.push(i);
	}
	for(int i=0;i<m;++i){
		int a;
		int b;
		cin>>a>>b;
		int c;
		cin>>c;
		add(a,b,c);
	}
	q.push(1);
	if(spfa()){
		cout<<"Yes"<<endl;
	} else{
		cout<<"No"<<endl; 
	}
	return 0;
} 

弗洛伊德算法求最短路径(多源最短路径)

考研党都明白的哈。k是中间节点,dij可以看成dik+dkj

这里有点基于动态规划的意思。dkij的含义为:从i出发,只经过从1~k这几个节点中的任意一个点作为中间点,到j的最短距离。

dp的讨论为:加入k和不加k的最小值。(具体等我把dp问题学完了在单独做一个笔记)

代码如下:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int m;
int k;
const int N = 210;
int dis[N][N];
void Floyd(){
	for(int k=1;k<=n;++k){
		for(int i = 1;i<=n;++i){
			for(int j =1;j<=n;++j){
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
}
int main(){
	cin>>n>>m>>k;
	memset(dis,0x3f,sizeof dis);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(i==j)
			dis[i][j]=0;
		}
	}
	
	for(int i = 0;i<m;++i){
		int a;
		int b;
		int c;
		cin>>a>>b>>c;
		dis[a][b]=min(dis[a][b],c);
		
	}
	
	Floyd();
	for(int i=0;i<k;++i){
		int a;
		int b;
		cin>>a>>b;
		if(dis[a][b]>0x3f3f3f3f/2)cout<<"impossible"<<endl;
		else cout<<dis[a][b]<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值