⭐--单源最短路的基础模板--⭐


#写在前面

https://blog.csdn.net/lafea/article/details/107811927

#dijkstra

##一

----c++版

https://www.acwing.com/problem/content/851/

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=510;
int n,m;
int g[N][N];
int dist[N];//从1走到n号点的当前最短距离
bool st[N];//确定了最短路的点的集合

int dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0;
    for(int i=0;i<n;i++){//迭代n次
        int t=-1;//每次找出没确定最短路的点之中到 确定最短路的点的集合 的距离最小的点
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
                t=j;
        st[t]=true;//将找到的点加入确定最短路的点的集合
        if(t==n)break;
        for(int j=1;j<=n;j++)//实际上有m次
            dist[j]=min(dist[j],dist[t]+g[t][j]);//用新的点的最短路去尝试更新所有当前最短路(也包括曾确定的)
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    return dist[n];
}

int main(){
    scanf("%d%d",&n,&m);
    memset(g, 0x3f, sizeof g);
    while(m--){
        //由于有重边和自环
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        g[a][b]=min(g[a][b], c);//不用管自环,用不到
    }
    int t=dijkstra();
    printf("%d",t);
    return 0;
}

##二 堆优化版

在朴素版中有【找出没确定最短路的点之中到 确定最短路的点的集合 的距离最小的点】这个操作,
朴素版用循环实现,堆优化直接用优先队列实现,省去了循环的过程

----c++版

你可以手写堆,这样堆里可以维持n个数,也可以使用c++提供的优先队列,但队列里可能有m个数
个人觉得堆优化版的dijkstra更好理解,更直接
https://www.acwing.com/problem/content/852/

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
//稀疏图用邻接表存
const int N=1e6+10;
int n,m;
int h[N],e[N],w[N],ne[N],idx;
int dist[N];
bool st[N];
typedef pair<int, int>pll;//由于我们还需要知道节点编号

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

int dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0;
    priority_queue<pll, vector<pll>, greater<pll>>heap;//小根堆
    heap.push({0,1});//1到1最短距离是0
    while(heap.size()){
        auto t=heap.top();
        heap.pop();
        int ver=t.second,distance=t.first;
        if(st[ver])continue;st[ver]=true; //在集合内就丢掉
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];//更新这个点能走到的所有点的最短距离
            if(dist[j]>distance+w[i]){
                dist[j]=distance+w[i];
                heap.push({dist[j],j});//把更新过的最短路加到堆里,虽然更短路会被堆pop掉,但没关系,因为distj是确实更新了,且堆只是为了找还在集合外的点
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    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=dijkstra();
    printf("%d",t);
    return 0;
}

#bellman-ford

bellman-ford 的存边方式比较简单,只要能遍历到所有边就行,可以用最简单的开个结构体的方式
基本是两重循环

for 遍历n次:
    for 从a到b权值w的所有边:
        dist[b]=min(dist[b], dist[a]+w);

每次判断1到b的距离能不能用这条边来更新
遍历完保证对所有边满足dist[b]<=dist[a]+w, 这是松弛操作
bellman-ford可以处理负权边
如果有负权回路,最短路不一定存在

外层循环迭代k次,dist数组的含义是:不超过k条边到各点的最短路距离
如果第n次迭代还有边被更新,说明存在一条最短路上面有n条边,由于最多才n个点,抽屉原理,所有必然有两个点编号一样,存在负环。

有可能有负环又存在最短路的,
如果用spfa,则一定要求图中没有负环

##有边数限制的最短路

这个只能用bellman-ford,一般spfa比bellman-ford好

----c++版

https://www.acwing.com/problem/content/855/

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=510,M=10010;
int n,m,k;
int dist[N],backup[N];
struct edge{
    int a,b,w;
}edges[M];

int bellmanford(){
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0;
    for(int i=0;i<k;i++){
        //备份dist数组,因为可能出现串联
        //即在此次更新过程中用此次更新的距离去更新其他距离,这样限制不了步数
        //需要在每次更新时都使用上一次迭代的结果
        memcpy(backup, dist, sizeof dist);
        for(int j=0;j<m;j++){
            int a=edges[j].a,b=edges[j].b,w=edges[j].w;
            dist[b]=min(dist[b],backup[a]+w);
        }
    }
    //大于一个比较大的数,因为到不了的点之间的负权边可能会把无穷大更新了
    if(dist[n]>0x3f3f3f3f/2)return -1;
    return dist[n];
}

int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<m;i++){
        int a,b,w;scanf("%d%d%d",&a,&b,&w);
        edges[i]={a,b,w};
    }
    int t=bellmanford();
    if(t==-1)puts("impossible");
    else printf("%d", t);
    return 0;
}

#spfa

网格型图易卡spfa

##spfa求最短路

对bellman-ford的优化
针对松弛操作的优化,如果dist[b]变小了,一定是因为dist[a]变小了,
如果有变小的距离,针对这个距离把之后的全部更新就得
更新过谁,再拿谁来更新别人

----c++版

实现和dijkstra很像
https://www.acwing.com/problem/content/853/

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int ,int> pll;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
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;//存当前点是否在队列中
    while(q.size()){
        int t=q.front();
        q.pop();
        st[t]=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;
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    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==-1)puts("impossible");
    else printf("%d",t);
    return 0;
}

##spfa判断负环

----c++版

https://www.acwing.com/problem/content/854/

// 思路和bellmanford差不多,抽屉原理,时间复杂度比较高
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

typedef pair<int ,int> pll;
const int N=100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N], cnt[N];//cnt记录步数,
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(){
    //不需要初始化了
    queue<int>q;
    for(int i=1;i<=n;i++){
        st[i]=true;
        q.push(i);
    }//不固定起点
    while(q.size()){
        int t=q.front();
        q.pop();
        st[t]=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];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)return true;
                if(!st[j]){//如果已经在就不用再加了
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}
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);
    }
    if(spfa())puts("Yes");
    else puts("No");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值