【图论最短路算法总结】

图论_最短路算法:

1. Floyd:

·时间复杂度:O(n3)
·使用范围:无负权回路的稠密图,应用于数据较小的题目,可以求出任意两点之间的最短路
memset(d,0x3f,sizeof d);//初始化
for(int k=1; k<=n; k++){//枚举中间点
     for(int i=1; i<=n; i++){
        if(i!=k)
          for(int j=1; j<=n; j++){
              if(i!=j&&j!=k)
                 d[i][j]=min(d[i][j], d[i][k]+d[k][j]);
         }
     }
 }

https://www.luogu.com.cn/problem/P1119
https://www.luogu.com.cn/problem/P1522

拓展:

1. floyd求最小环
memset(d, 0x3f ,sizeof(d));//d[i][j]表示i到j的最小值,a[i][j]为矩阵存图
int ans=0x3f3f3f3f;
for(int k=1; k<=n; k++){
	for(int i=1; i<k; i++)        
for(int j=i+1; j<k; j++)
        	ans=min(ans, d[i][j]+a[i][k]+a[k][j]);//求最小环
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            d[i][j]=min(d[i][j], a[i][k]+a[k][j]);//更新最短路
 }
2. 求最小环路径
 int d[105][105],a[105][105], pos[105][105],ans=1e9;
vector<int> path;
void dfs(int i,int j){
    int k=pos[i][j];
    if(k==0) return;
    dfs(i, k);
    path.emplace_back(k);
    dfs(k,j);
}
void get_p(int i,int j,int k){
    path.clear();
    path.emplace_back(k);
    path.emplace_back(i);
    dfs(i, j);
    path.emplace_back(j);
}
void floyd_path(){
	for(int k=1; k<=n; k++){
        for(int i=1; i<k; i++)
            for(int j=i+1; j<k; j++)
                if(ans > (d[i][j]+a[i][k]+a[k][j]) ){
                    ans = (d[i][j]+a[i][k]+a[k][j]);
                    get_p(i,j,k);//通过递归寻找i,j之间的点,并记录下来
                }
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                if(d[i][j] > d[i][k]+d[k][j]){
                    d[i][j] = d[i][k]+d[k][j];
                    pos[i][j]=k;
            }
}
}

2. Dijkstra算法:

朴素Dijkstra

时间复杂度:n^2
使用范围:稠密图
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N][N],dis[N],vis[N],n,m,s;
void dijkstra(){
	memset(dis,0x3f,sizeof dis);
	dis[s]=0;
	for(int i=1;i<n;i++){
		int x=0;
		for(int j=1;j<=n;j++)
			if(!vis[j]&&(x==0||dis[j]<dis[x]))x=j;//找出最小的点 
		vis[x]=1;
		for(int j=1;j<=n;j++){
			dis[j]=min(dis[j],dis[x]+a[x][j]);//用最小点更新其他点 
		}	
	} 
}
int main(){
	cin>>n>>m>>s;
	memset(a,0x3f,sizeof a);
	for(int i=1;i<=m;i++){
		int u,v,w;
		scanf("%d %d %d",&u,&v,&w);
		a[u][v]=min(w,a[u][v]);//处理重边 
	}
	dijkstra();
	for(int i=1;i<=n;i++)
		cout<<dis[i]<<" ";
	return 0;
}

·时间复杂度:nlog(n)
·使用范围:稀疏图,不能处理负边权
const int INF=(1<<30);
int n,m,head[MAXN],cnt=0;
struct node1{
	int v,next,w;
}edge[MAXN];
struct node{
	int l,id;
	node(){
	}
	node(int l,int id):l(l),id(id){
	}
	bool operator <(const node &a)const{
	return l>a.l;
	}
};
bool in[MAXN]={0};
long long dis[MAXN],ans=0;//dis[i]表示第i个点到开始点的最短路径,
//找到新的点后将他的(dis[i],i)加入优先队列 ,从优先队列中的顶部也就是
//到开始点距离最短的点开始遍历他的儿子,并且将还未进入队列的点处理后入队 
void add(int u,int v,int w){
	edge[++cnt].v=v;
	edge[cnt].next=head[u];
	edge[cnt].w=w;
	head[u]=cnt;
}
void Dijkstra(){
	for(int i=1;i<=n;i++){
	dis[i]=INF;	
	}
	dis[1]=0;
	priority_queue<node> q;
	q.push(node(0,1));
	while(!q.empty()){
		node x=q.top();q.pop();
//if(x是要找的点)break;因为再处理下去路径只会越来越长,不是最短路
		if(in[x.id])continue;
		in[x.id]=1;
		for(int i=head[x.id];i;i=edge[i].next){
			int v=edge[i].v,w=edge[i].w;
			if(x.l+w<dis[v])
			{dis[v]=x.l+w;
            /*假如需要得到最短路径,则需要加上from[v]=u;
              再调用递归函数 
void out(int u){ 
                  if(u==0)return;
                   out(from[u]);
                   cout<<u<<””; 
}
*/
			q.push({dis[v],v});
			}
		}
	}
}

模板题:https://www.luogu.com.cn/problem/P3371
模板题:https://www.luogu.com.cn/problem/P4779

  1. 还可以用于有条件的最短路:https://www.luogu.com.cn/problem/P1266
  2. dijkstra算法是可以解决多源多汇的问题:求集合A中的点到集合B中的点的距离的最小值,保证集合A、B没有相交的点,我们把集合A中的点(也就是与超级源点S相连的点)全部塞到堆中,同时在dis数组中把对应的值置为0,再新开一个数组标记集合B中的点(也就是与超级汇点相连的点)为true,那么在跑dijkstra算法的过程中,第一次访问到了标记为true的点,就得到了我们想要的答案。对于无向图来说,我们只需跑一次dijkstra,即集合A或集合B做起始点都可以;对于有向图来说,我们需要跑两次dijkstra,第一次A为源点B为汇点,第二次A为汇点B为源点。HDU 6166
  3. 正反跑一次dijkstra,染色:https://www.luogu.com.cn/record/75634396

3.Bellman-Ford算法:

·时间复杂度:O(nm)
·适用范围:作用于负权边无环图求最短路
const int MAXN=10001;
int dis[MAXN],v[MAXN],u[MAXN],w[MAXN],n,m;//dis[i]表示 点i到s的最短距离 
int main(){
	cin>>n>>m>>s>>t;//s表示起点,t表示终点 
	for(int i=1;i<=m;i++){
		cin>>u[i]>>v[i]>>w[i];
	}
	memset(dis,0x7f,sizeof(dis));
	dis[s]=0;
	for(int i=1;i<=n-1;i++){
		for(int j=1;j<=m;j++){
			if(dis[v[j]]>dis[u[j]]+w[j])
			dis[v[j]]=dis[u[j]]+w[j];
		}
	}
	cout<<dis[t]; 
} 

4.SPFA

·时间复杂度 :O(nlog(n))
·适用范围:当给定的图存在负 权边,而Bellman-Ford算法的复杂度又过高,SPFA算法便成了解题利器。因为原理和Bellman-Ford相同,所以依然可以判断负环是否存在。唯一不足是会被网格图卡
queue<int> q;
bool inque[N];//记录是否在队列中 避免重复
int cnt[N];//记录入队次数 判断负环
bool SPFA(int s){
  for(int i=1;i<=n;i++)inque[i]=0,cnt[i]=0,d[i]=INF;
  d[s]=0;cnt[s]=1;
  q.push(s);
  while(!q.empty()){
    int u=q.front();q.pop();
    inque[u]=false;
    for(int i=head[u];i;i=E[i].next){
      int v=E[i].v,w=E[i].w;
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        if(!inque[v]){
          inque[v]=true;
          if(++cnt>n)return false;//判断负环
          q.push(v);
        }
      }
    }
  }
  return true;
}

5. Johnson 全源最短路

·时间复杂度: O(nmlog m)
·适用范围:求出无负环图上任意两点间最短路径的算法。用 Bellman-Ford 算法求出从 0 号点到其他所有点的最短路,记为 hi。假如存在一条从u点到v点,边权为 w 的边,则我们将该边的边权重新设置为w+hu-hv。接下来以每个点为起点,跑 n轮 Dijkstra 算法即可求出任意两点间的最短路了。

[https://www.luogu.com.cn/problem/solution/P5905]

const int inf=1e9;
int  n,m,cnt=0,head[5010],dis[5010],h[3010],t[3010];
bool vis[3010]={0},in[3010]={0};
struct node1{
	int v,next,w;
}edge[10010];
struct node{
	int l,id;
	node(){
	}
	node(int l,int id):l(l),id(id){
	}
	bool operator <(const node &a)const{
	return l>a.l;
	}
};
inline void add(int u,int v,int w){
	edge[++cnt].v=v;
	edge[cnt].w=w;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
bool spfa(int s){
	queue<int>q;
	memset(h,63,sizeof(h));
	h[s]=0,vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=edge[i].next){
			int v=edge[i].v;
			if(h[v]>h[x]+edge[i].w){
				h[v]=h[x]+edge[i].w;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
					t[v]++;
					if(t[v]==n+1)
					return 0;
				}
			}
		}
	}
	return 1;
}
void Dijkstra(int s){
	memset(in,0,sizeof(in));
	for(int i=1;i<=n;i++)
	dis[i]=inf;
	dis[s]=0;
	priority_queue<node> q;
	q.push(node(0,s));
	while(!q.empty()){
	    node x=q.top();q.pop();
		if(in[x.id])continue;
		in[x.id]=1;
		for(int i=head[x.id];i;i=edge[i].next){
			int v=edge[i].v,w=edge[i].w;
			if(dis[v]>x.l+w)
			dis[v]=x.l+w,q.push(node(dis[v],v));
		}
	} 
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++){
	cin>>u>>v>>w;
	add(u,v,w);
}
for(int i=1;i<=n;i++)
add(0,i,0);
if(!spfa(0))
{cout<<"-1"<<endl;
return 0;
}
for(int u=1;u<=n;u++)
for(int i=head[u];i;i=edge[i].next)
edge[i].w+=h[u]-h[edge[i].v]; 
for(int i=1;i<=n;i++){ 
Dijkstra(i); 
int ans=0;
for(int j=1;j<=n;j++)
{if(dis[j]==inf)
ans+=j*inf;
else
ans+=j*(dis[j]+h[j]-h[i]);
}
cout<<ans<<endl;
}
}

总结:

1>非全源最短路:

(1)正权图:当数据范围小时,使用floyd,数据范围大且为稠密图(n^2<m)使用朴素dijkstra,稀疏图用dijkstra+堆优化。
(2)负权图:使用SPFA算法

2>全源最短路:

当数据范围小时,使用floyd,当存在负权图时,使用Johnson 全源最短路,其他情况跑n次dijkstra。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值