图论-最短路径综合

最短路问题分类 有向图无向图无所谓(无向图建两条边就ok) 重边和自环只有朴素dij和floyd的邻接矩阵需要特判

难点在建图
先确定是哪种图:
稠密图:m~n^2
稀疏图:m~n

在这里插入图片描述
补:floyd可以处理负权

边全正

朴素迪杰斯特拉O(n2) 稠密图

用邻接矩阵g[][]存图 初始化0x3f
开一个st[ ]记录确定点的集合
开一个d[ ]记录最短距离 初始化0x3f

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N=510;
int g[N][N];
int d[N];
int st[N];  是否在集合内 
int n,m;

int dij(){  // 1到n最短路长度
	memset(d,0x3f,sizeof(d));
	d[1]=0;                        先把源点距离初始化0
	
	for(int i=0;i<n;i++){          循环n次 
	 
		int t=-1;                  每次找不在集合内的最近距离点 
		for(int j=1;j<=n;j++){
			if(!st[j]&&(t==-1||d[j]<d[t])) t=j;
		}
		st[t]=1;     			   确定此点
		
		for(int j=1;j<=n;j++){     根据此点更新 (松弛)
			d[j]=min(d[j],d[t]+g[t][j]);
		} 
	}
	
	if(d[n]==0x3f3f3f3f) return -1;   不可达
	else return d[n];
}

int main(){
	cin>>n>>m;
	memset(g,0x3f,sizeof(g));    // 邻接数组初始化为最大值 
	
	for(int i=0;i<m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		g[x][y]=min(g[x][y],z);     考虑重边 
	} 
	
	cout<<dij();
}

堆优化迪杰斯特拉O(mlogn) 稀疏图 用邻接表 不用处理重边

注意邻接表的边长w[N]

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int N=1000010;
int head[N],e[N],ne[N],w[N],idx;      记得memset h[]
int st[N];
int d[N];
typedef pair<int,int> PII;

void add(int x,int y,int z){              邻接表带边长w[n]
	e[idx]=y; w[idx]=z; ne[idx]=head[x]; head[x]=idx++;
}

int dij(){
	memset(d,0x3f,sizeof(d));							d[]数组初始化
	d[1]=0;
	priority_queue<PII,vector<PII>,greater<PII>> hp;   <距离,点>优先队列
	hp.push({0,1});
	
	while(!hp.empty()){                                 写法类似BFS
		auto t=hp.top();
		hp.pop();										快pop
		int father=t.second;  
		if(st[father]) continue;                         在集合内了 continue
		st[father]=1;								     放入集合
		
		for(int i=head[father];i!=-1;i=ne[i]){
			int son=e[i];
			if(d[son]>d[father]+w[i]){                    需要更新(松弛)的才入队
				d[son]=d[father]+w[i];
				hp.push({d[son],son});
			}
		}
	}
	if(d[n]==0x3f3f3f3f) return -1;
	return d[n];
}

int main(){
	memset(head,-1,sizeof(head));        邻接表注意初始化
	cin>>n>>m;
	while(m--){
		int x,y,z; cin>>x>>y>>z;
		add(x,y,z);
	}	
	cout<<dij();
}

有负边

在这里插入图片描述

Bellman-Ford 算法 外层循环限制最短路的边数

外层循环的k代表从起点经过不超过k条边的最短路距离
在这里插入图片描述

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, M = 10010;

struct Edge
{
    int a, b, c;
}edges[M];

int n, m, k;
int dist[N];
int last[N];										backup

void bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);

    dist[1] = 0;
    for (int i = 0; i < k; i ++ )					边数在k以内
    {
        memcpy(last, dist, sizeof dist);
        for (int j = 0; j < m; j ++ )				逐边松弛
        {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);   源点用last
        }
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }

    bellman_ford();

    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");   不存在是 > 0x3f3f3f3f / 2!!!!
    else printf("%d\n", dist[n]);

    return 0;
}

负权图 一般用spfa 代码和堆优化dij很像 把握重点在于st数组维护是否在队列中

在这里插入图片描述

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], w[N], e[N], ne[N], idx;  记得memset h[]
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;							普通队列
    q.push(1);								存入下标		
    st[1] = true;							在队列中 则true!!!!

    while (q.size())						类似BFS
    {
        int t = q.front();
        q.pop();							立即pop()

        st[t] = false;						不在队列中了 false

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])   只考虑需要松弛的!!!!
            {
                dist[j] = dist[t] + w[i];	
                if (!st[j])					需要松弛的如果不在队列里 才入队!!!!!!!!!
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    int t = spfa();

    if (t == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", t);

    return 0;
}

判断有无负环 就用spfa! 此时不再需要typically初始化d[N]; d[1]=0; 并且一开始所有点都要入栈 用cnt记录最短路上的边数 若cnt[i]>=n 那么有负环

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N=2010;
const int M=10010;
int h[N],e[M],ne[M],w[M],idx;      记得memset h
int st[N],cnt[N],d[N];			   多了一个cnt存边数
int n,m;

void add(int a,int b,int c){
	e[idx]=b; w[idx]=c; ne[idx]=h[a]; h[a]=idx++;
}

int spfa(){											无需初始化d d[1]
	queue<int> Q;
	for(int i=1;i<=n;i++){							所有点都入队
		st[i]=1;
		Q.push(i);
	}
	while(Q.size()){
		int father=Q.front();
		Q.pop();
		st[father]=0;
		
		for(int i=h[father];i!=-1;i=ne[i]){
			int son=e[i];
			if(d[son]>d[father]+w[i]){
				
				d[son]=d[father]+w[i];
				cnt[son]=cnt[father]+1;				松弛时更新边数
				if(cnt[son]>=n) return 1;			有边数>=n 则有负环
				
				if(!st[son]){
					Q.push(son);
					st[son]=1;
				}
			}
		}
	}
	return 0;										没用
}

int main(){
	memset(h,-1,sizeof(h));
	cin>>n>>m;
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
	}
	if(spfa()) cout<<"Yes";
	else cout<<"No";
}

多源汇 Floyd O(n3) 可以处理负边 用邻接矩阵存图 注意不存在路径的条件

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N=210;
int INF=1e9;									INF=1e9
int d[N][N];
int n,m,k;

void floyd(){									简单的三重循环
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
			}
		}
	}
}

int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){						最需要注意的是初始化 对角线和非对角线
		for(int j=1;j<=n;j++){
			if(i==j) d[i][j]=0;
			else d[i][j]=INF;
		}
	}
	
	while(m--){
		int a,b,c;
		cin>>a>>b>>c;
		d[a][b]=min(d[a][b],c); 
	}
	
	floyd();
	
	while(k--){
		int a,b; cin>>a>>b;
		int t=d[a][b];
		if(t>INF/2) cout<<"impossible"<<endl;         t>INF/2 说明不存在
		else cout<<t<<endl;
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值