最短路常用解法详细介绍附代码

前提须知:本篇博客代码均用于此模版题所写

在每年的比赛里,所有进入决赛的同学都会获得一件很漂亮的 t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

输入格式

输入包括多组数据。

每组数据第一行是两个整数 N、M(N≤100,M≤10000),N 表示大街上有几个路口,标号为 1 的路口是商店所在地,标号为 N的路口是赛场所在地,M则表示在成都有几条路。N=M=0 表示输入结束。接下来 M行,每行包括 3个整数 A,B,C(1≤A,B≤N,1≤C≤1000),表示在路口 A与路口 B 之间有一条路,我们的工作人员需要 C分钟的时间走过这条路。

输入保证至少存在 1 条商店到赛场的路线。

输出格式

对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。

inputoutput
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0

3

2

最短路 - 计蒜客 T1600 - Virtual Judge

Dijkstra算法

算法介绍:

 

//暴力寻找的Dijkstra
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <stack>
#include <queue>
#include <iostream>
#include <cmath>
#include <cstring>
#include <set>
#define ll long long
#define endl '\n'
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1e5+7;
const double pi=acos(-1);
int n,m,a,b,c,flag;
int g[105][105];
int vis[105];
int dis[105];
void Dijkstra(){
    memset(dis, 0x3f, sizeof(dis));
    dis[1]=0;
    //枚举所有的点,找出初始最小值
    for(int i=0;i<n;++i){
        int flag=-1;
        for(int j=1;j<=n;++j){
            if(!vis[j]&&(dis[flag]>dis[j]||flag==-1))
                flag=j;
        }
        vis[flag]=1;
        //以这个点为起点更新其他点的最短路径
        for(int j=1;j<=n;++j){
            dis[j]=min(dis[j],dis[flag]+g[flag][j]);
        }
    }
    cout<<dis[n]<<endl;
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    while(cin>>n>>m){
        if(n==0&&m==0) break;
        memset(g, 0x3f, sizeof(g));
        memset(vis, 0, sizeof(vis));
        for(int i=0;i<m;++i){//存图,无向图
            cin>>a>>b>>c;
            if(g[a][b]>c){
               g[a][b]=c;
               g[b][a]=c;
            }
        }
        Dijkstra();
    }
    return 0;
}


//优先队列优化的Dijkstra
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <stack>
#include <queue>
#include <iostream>
#include <cmath>
#include <cstring>
#include <set>
#define ll long long
#define endl '\n'
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1e5+7;
const double pi=acos(-1);
int n,m,a,b,c;
struct node{
    int point;//点
    int w;//权重
};
int vis[105];
int dis[105];
vector<node>g[105];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int, int>>>q;
void Dijkstra(){
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[1]=0;
    q.push(make_pair(dis[1], 1));//pair's first is distance,pair's second is point
    while(!q.empty()){
        pair<int, int> now=q.top();
        q.pop();
        if(vis[now.second]) continue;//如果这个点已经被访问过了,则直接下一个
        vis[now.second]=1;//标记此时的初始点
        for(int i=0;i<g[now.second].size();++i){//以这个初始点开始寻找最短路径
            int point=g[now.second][i].point;//表示现在这个点通向的点是哪个
            int w=g[now.second][i].w;//表示现在这个点通向的那个点的权值
            if(dis[point]>now.first+w){//如果这个点的最短路径大于现在这个点的最短路径加上通向那个点的路径的和则更新最短路径
                dis[point]=now.first+w;
                q.push(make_pair(dis[point],point));//将更新后的最短路经存入队列,作为新的一个初始点进行遍历
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    while(cin>>n>>m){
        memset(g, 0, sizeof(g));
        if(n==0&&m==0) break;
        while(m--){
            cin>>a>>b>>c;
            g[a].push_back((node){b,c});
            g[b].push_back(node{a,c});
        }
        Dijkstra();
        cout<<dis[n]<<endl;
    }
    return 0;
}

Floyd

算法介绍:

1.用 Floyd 算法解决多源最短路问题

2.复杂度比较高,但常数比较小,容易实现 (三个 for?)

3.适用于任何图,前提是这个图必须存在最短路

4.一般使用邻接矩阵存图

#include<vector>
#include <iostream>
#include<queue>
#define ll long long
const int inf=0x3f3f3f3f;
using namespace std;
int n,m;
int in,to,w;
int g[105][105];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n>>m,n,m){
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                g[i][j]=inf;
            }
            g[i][i]=0;
        }
        while(m--){
            cin>>in>>to>>w;
            g[in][to]=g[to][in]=w;
        }
        for(int k=1;k<=n;++k){//floyd模版
            for(int i=1;i<=n;++i){
                for(int j=1;j<=n;++j){
                    if(g[i][k]==inf||g[k][j]==inf) continue;
                    if(g[i][j]==inf||g[i][k]+g[k][j]<g[i][j])
                        g[i][j]=g[i][k]+g[k][j];
                }
            }
        }
        cout<<g[1][n]<<endl;
    }
    return 0;
}

Bellman_Ford

Bellman-Ford 算法是一种基于松弛 (relax) 操作的最短路算法, 可以求出有负权的图的最短路, 并可以对最短路不存在的情况进行判断

用于解决单源最短路问题

首先我们可以用一个一维数组 dis[] 来储存每个点到起点 s的距离,即 dis[i] 代表点 s 到点 i 的距离

定义对于边 (u,v) 的松弛操作dis[v] = min(dis[v], dis[u] + w(u, v)),去判断 S v 的最短路能否通过边 (u,v) 去缩短

Bellman-Ford 算法其实就是对所有边不断的松弛

每进行一轮循环, 其实就是对所有边都进行了一次松弛尝试。当某轮循环中没有一条边可以被松弛, 这就意味着当前的dis 数组就是最短路, 然后结束算法即可

最多几次循环?

一个很显然的结论:n 个点的图中, 任意一条最短路的边数最多为 n-1。由于每轮松弛都会让最短路的边数至少 +1, 那么至多进行 (n -1) 轮松弛即可

时间复杂度 O(n × m)

前面提到 Bellman-Ford 还可以判断是否存在最短路

不存在最短路意味着什么?

即你在图上可以一直跑, 并且的路径长度会不断缩小, 这就意味着存在一个负环

那也就很好判断, 对于存在最短路的图, 我们最多松弛(n-1) 轮, 那么如果当我们在松弛第 n 轮时还在变化, 也就意味着不存在最短路

#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <stack>
#include <queue>
#include <iostream>
#include <cmath>
#include <cstring>
#include <set>
#define ll long long
#define endl '\n'
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1e5+7;
const double pi=acos(-1);
struct node{
    int to;//通向的那个点
    int w;//权重
};
vector<node>g[105];//邻接表;
int n,m,a,b,w;
int dis[105];
int vis[105];
void Bellman_Ford(){
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    for(int k=1;k<n;++k){//Bellman_Ford算法最多进行n-1轮,如果第n轮仍在变化证明不存在最短路即有负环存在
        for(int i=1;i<=n;++i){
            for(int j=0;j<g[i].size();++j){
                int to=g[i][j].to;
                int w=g[i][j].w;
                if(dis[to]>dis[i]+w) dis[to]=dis[i]+w;
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    while(cin>>n>>m){
        if(n==0&&m==0) break;
        memset(g, 0, sizeof(g));
        while(m--){
            cin>>a>>b>>w;
            g[a].push_back((node){b,w});
            g[b].push_back((node){a,w});
        }
        Bellman_Ford();
        cout<<dis[n]<<endl;
    }
    return 0;
}

SPFA

Bellman-Ford 的队列优化, 即 SPFA

其本质就是由于 Bellman-Ford 每一轮的松弛可能可以被更新的点都是确定的, 即上一轮被成功松弛的边的终点,再有这些改变过的点去更新和其相关的点,就会大大减少无谓的运算。

在这个性质的基础上, 就可以通过类似于 bfs 的方式去不断松弛, 直到队列为空

 

随机数据下期望时间复杂度 O(m + nlogn) ,但复杂度不稳定,最坏情况可以被卡成 Bellman-Ford

需要去判断图中是否有负环,可以用 spfa,即用一个数组记录每个顶点入队次数,当一个顶点进队超过 n 次时,说明存在负环

#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <queue>
#include <stack>
#include <queue>
#include <iostream>
#include <cmath>
#include <cstring>
#include <set>
#define ll long long
#define endl '\n'
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1e5+7;
const double pi=acos(-1);
struct node{
    int to,w;
};
int n,m,a,b,w;
int cnt[105];//存储该边进了几次队列
int dis[105];
bool vis[105];
vector<node>g[105];
void spfa(){
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, false, sizeof(vis));
    dis[1]=0;
    vis[1]=true;
    queue<int>q;
    q.push(1);
    while(q.size()){//队列不为空
        int t=q.front();//取出队首元素
        q.pop();//弹出
        vis[t]=false;//这个点标记为false
        for(int i=0;i<g[t].size();++i){
            int to=g[t][i].to;
            int w=g[t][i].w;
            if(dis[to]>dis[t]+w){//更新最短路径
                dis[to]=dis[t]+w;
                if(!vis[to]){//如果这个点没有在队列里则将其存入队列
                    vis[to]=true;
                    q.push(to);
                }
            }
        }
    }
}
bool fuhuan(){
    memset(cnt, 0, sizeof(cnt));
    queue<int>q;
    for(int i=1;i<=n;++i){//因为不确定负环所连接的点,所以所有的点都要入队
        vis[i]=true;
        q.push(i);
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        vis[t]=false;
        for(int i=0;i<g[t].size();++i){
            int to=g[t][i].to;
            int w=g[t][i].w;
            if(dis[to]>dis[t]+w){
                dis[to]=dis[t]+w;
                cnt[to]=cnt[t]+1;
                if(cnt[to]>=n) return true;
                if(!vis[to]){
                    q.push(to);
                    vis[to]=true;
                }
            }
        }
    }
    return false;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    while(cin>>n>>m,n,m){
        memset(g, 0, sizeof(g));
        while(m--){
            cin>>a>>b>>w;
            g[a].push_back((node){b,w});
            g[b].push_back((node){a,w});
        }
       if(!fuhuan()) spfa();
        cout<<dis[n]<<endl;
    }
    return 0;
}

萌新一个,如有错误希望指出,谢谢🙏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值