最短路算法

目录

Dijkstra算法

适用情形:

算法步骤:

为什么要求没有负权边?

松弛操作

松弛操作的正确性保证

模板&&例题

Floyd算法(纯暴力)

适用情形:

算法步骤:

模板&&例题

Bellman-Ford算法

SPFA算法

适用情形:

负环:

模板&&例题

练习题:


最短路算法是图论中的核心问题之一,旨在寻找图中两点之间路径权重之和最小的路径。其应用广泛,包括导航系统、网络路由、物流规划等领域。根据图的性质(如带权/无权、有无负权边)和需求(单源/多源),需选择不同算法。

Dijkstra算法

适用情形:

Dijkstra算法适用于没有负权边的单源最短路问题

算法步骤:

1:先初始化距离数组,都设置成最大值,为后续松弛操作做好准备

2:将起始点入优先队列

3:遍历优先队列,取优先队列顶点,遍历与顶点直接相连的边

4:对点逐一进行松弛操作后将其入优先队列

重复进行3,4,并且要注意用状态数组st标记每个点是否已经访问,已经访问过的点就无需二次松弛了

为什么要求没有负权边?

首先我们要明确Dijkstra算法的原理思想:Dijkstra算法是基于贪心的思想进行的

我们来动脑思考一下,因为边权全是正值,那么与起始点直接相连的各个点的边权中最小的那个,定然是起始点与该点的最短路!因为边权全是正值经过一条边权最小的边到达,必然比经过两条边到达要短!(默读三遍)

基于此,我们就可以通过这个点不断往外拓展,最终求出所有点距起始点的最短距离

但是如果有负权边的话就不一定了,经过多条边到达可能会比只经过那条边权最小的边到达更短。

松弛操作

松弛操作是Dijkstra算法的核心步骤,用于更新从起点到其他顶点的最短路径估计。当发现更短的路径时,通过松弛操作调整当前的最短路径值。

简要来说松弛操作就是通过判断经过某点中转后的距离会不会与之前相比而减少。

cpp 复制代码
if (!st[a]&&d[a]>d[w]+b)
    d[a]=d[w]+b;

松弛操作的正确性保证

Dijkstra算法要求所有边权重非负,这一条件保证了:

  • 每次松弛操作不会产生更长的路径
  • 已标记为已访问的顶点不需要再次松弛
  • 算法最终能得到所有顶点的最短路径

模板&&例题

P4779 【模板】单源最短路径(标准版)https://www.luogu.com.cn/problem/P4779

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+10;
const int mod = 998244353;
struct node {
    int u,w;
};
bool st[N];
vector<node>mp[N];
int d[N];
void dd(int start) {
    memset(d,0x3f,sizeof(d));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0,start});
    d[start]=0;
    while (pq.size()!=0) {
        int u = pq.top().first;
        int w = pq.top().second;
        pq.pop();
        if (st[w])continue;
            st[w]=1;
        for (auto x:mp[w]) {
            int a=x.u,b=x.w;
            if (!st[a]&&d[a]>d[w]+b)
                d[a]=d[w]+b;
            pq.push({d[a],a});
        }
    }
}
void solve() {
    int n,m,s;
    cin >> n >> m >> s;
    for (int i=0;i<m;i++) {
        int u,v,w;
        cin >> u >> v >> w;
        mp[u].push_back({v,w});
    }
    dd(s);
    for (int i=1;i<=n;i++) {
        cout << d[i] << ' ';
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

Floyd算法(纯暴力)

Floyd算法(Floyd-Warshall算法)是一种解决图中所有顶点对之间最短路径的动态规划算法。它适用于带权有向图或无向图,允许边的权值为负(但不能存在负权回路)。算法通过逐步优化路径长度矩阵,最终得到所有顶点之间的最短路径。

适用情形:

多源最短路,能求得任意两点间的最短路。它适用于带权有向图或无向图,允许边的权值为负(但不能存在负权回路)

算法步骤:

三重循环,暴力的遍历任意两点以及这两点的全部中转点,不断更新最小值

cpp 复制代码
    for (int k=1;k<=n;k++) {
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) {
                if (i==j) {
                    d[i][j]=0;
                    continue;
                }
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                d[j][i]=d[i][j];
            }
        }
    }

需要注意,如果是相同点,那距离肯定是0

模板&&例题

P2419 [USACO08JAN] Cow Contest Shttps://www.luogu.com.cn/problem/P2419

这道题不是直接的图论问题,需要建模构造出图论模型,然后再用Floyd算法解决

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200;
const int mod = 998244353;
int d[N][N];
void solve() {
    int n,m;
    cin >> n >> m;
    for (int i=0;i<m;i++) {
        int x,y;
        cin >> x >> y;
        d[x][y]=1;
    }
    for (int k=1;k<=n;k++) {
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) {
                d[i][j]=d[i][j]||(d[i][k]&&d[k][j]);
            }
        }
    }
    int ans=0;
    for (int i=1;i<=n;i++) {
        int a=1;
        for (int j=1;j<=n;j++) {
            if (i==j)continue;
            a=a&&(d[i][j]||d[j][i]);
        }
        ans+=a;
    }
    cout << ans << endl;

}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

Bellman-Ford算法

很暴力,假设有n个点,对于每一个点都进行n-1次松弛操作

SPFA算法

SPFA算法是Bellman-Ford算法的队列优化

首先我们需要先理解这句话:只有一个点在上一轮被松弛成功时,这一轮从这个点连出的点才有可能被成功松弛。

**实际情景:**假设有四个点A,B,C,D。C,D与B相连,B与A相连,如果B被松弛成功,那么CD才有可能被松弛成功,否则不行

故而我们可以优化Bellman-Ford算法,这就是SPFA算法的由来

适用情形:

能够处理有负权边的图,还能判断图中是否存在负环

负环:

通过负环可以不断减少距离永无止境,故而若存在负环则没有最短路

模板&&例题

P3371 【模板】单源最短路径(弱化版)https://www.luogu.com.cn/problem/P3371

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+10;
const int mod = 998244353;
const int inf = 2147483647;
int d[N];
int n,m,s;
struct node {
    int v,w;
};
bool st[N];
queue<int>p;
vector<node>pp[N];
void spfa() {
    for (int i=0;i<=n;i++) {
        d[i]=inf;
    }
    d[s]=0;
    p.push(s);
    st[s]=1;
    while (p.size()!=0) {
        int q=p.front();
        p.pop();
        st[q]=0;
        for (auto x:pp[q]) {
            int y=x.v,z=x.w;
            if (d[y]>d[q]+z) {
                d[y]=d[q]+z;
                if (!st[y]){
                    p.push(y);
                    st[y]=1;
                }
            }
        }
    }
}
void solve() {
    cin >> n >> m >> s;
    for (int i=0;i<m;i++) {
        int x,y,z;
        cin >> x >> y >> z;
        pp[x].push_back({y,z});
    }
    spfa();
    for (int i=1;i<=n;i++) {
        cout << d[i] << ' ' ;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

练习题:

1:SPFA

P3385 【模板】负环https://www.luogu.com.cn/problem/P3385

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e3+10;
const int mod = 998244353;
const int inf = 2147483647;
int d[N];
int cnt[N];
int n,m;
struct node {
    int v,w;
};
bool st[N];
queue<int>p;
vector<node>pp[N];
bool spfa() {
    for (int i=0;i<=n;i++) {
        d[i]=inf;
    }
    memset(cnt,0,sizeof(cnt));
    memset(st,0,sizeof(st));
    while (p.size()!=0)p.pop();
    p.push(1);
    st[1]=1;
    d[1]=0;
    cnt[1]=1;
    while (p.size()!=0) {
        int q=p.front();
        p.pop();
        st[q]=0;
        for (auto x:pp[q]) {
            int y=x.v,z=x.w;
            if (d[y]>d[q]+z) {
                d[y]=d[q]+z;
                if (!st[y]){
                    p.push(y);
                    cnt[y]++;
                    st[y]=1;
                    if (cnt[y]>=n)return 0;
                }
            }
        }
    }
    return 1;
}
void solve() {
    for (int i=0;i<=n;i++) {
        pp[i].clear();
    }
    cin >> n >> m;
    for (int i=0;i<m;i++) {
        int x,y,z;
        cin >> x >> y >> z;
        if (z>=0){
            pp[x].push_back({y,z});
            pp[y].push_back({x,z});
        }else {
            pp[x].push_back({y,z});
        }
    }
    bool ok=spfa();
    if (!ok) {
        cout << "YES" << endl;
    }else {
        cout << "NO" << endl;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

2:Dijkstra算法+分层图

P4568 [JLOI2011] 飞行路线https://www.luogu.com.cn/problem/P4568

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+10;
const int mod = 998244353;
const int inf = 2147483647;
struct node {
    int u,w;
};
bool st[N];
vector<node>mp[N];
int d[N];
void dd(int start) {
    memset(d,0x3f,sizeof(d));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0,start});
    d[start]=0;
    while (pq.size()!=0) {
        int u = pq.top().first;
        int w = pq.top().second;
        pq.pop();
        if (st[w])continue;
        st[w]=1;
        for (auto x:mp[w]) {
            int a=x.u,b=x.w;
            if (!st[a]&&d[a]>d[w]+b)
                d[a]=d[w]+b;
            pq.push({d[a],a});
        }
    }
}
void solve() {
    int n,m,k,s,t;
    cin >> n >> m >> k;
    cin >> s >> t;
    for (int i=0;i<m;i++) {
        int u,v,w;
        cin >> u >> v >> w;
        mp[u].push_back({v,w});
        mp[v].push_back({u,w});
        for (int j=0;j<k;j++) {
            mp[u+j*n].push_back({v+(j+1)*n,0});
            mp[v+j*n].push_back({u+(j+1)*n,0});
            mp[u+(j+1)*n].push_back({v+(j+1)*n,w});
            mp[v+(j+1)*n].push_back({u+(j+1)*n,w});
        }
    }
    dd(s);
    int ans = inf;
    for (int j = 0; j <= k; j++) {
        ans = min(ans, d[t + j * n]);
    }
    cout << ans << "\n";
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int q=1;
    // cin >> q;
    while (q--) {
        solve();
    }
    return 0;
}

3:Floyd算法

P6464 [传智杯 #2 决赛] 传送门https://www.luogu.com.cn/problem/P6464

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200;
const int mod = 998244353;
int d[N][N],dd[N][N];
int n,m;
void fz() {
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++) {
            dd[i][j]=d[i][j];
        }
    }
}
void solve() {
    cin >> n >> m;
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++) {
            d[i][j]=100001;
        }
    }
    for (int i=0;i<m;i++) {
        int x,y,z;
        cin >> x >> y >> z;
        d[x][y]=z;d[y][x]=z;
    }
    for (int k=1;k<=n;k++) {
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) {
                if (i==j) {
                    d[i][j]=0;
                    continue;
                }
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
                d[j][i]=d[i][j];
            }
        }
    }
    int ans=1e9,mx=0;
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++) {
            fz();
            dd[i][j]=dd[j][i]=0;
            for (int k=1;k<=n;k++) {
                for (int l=1;l<=n;l++) {
                    dd[k][l]=min(dd[k][l],dd[k][i]+dd[i][l]);
                }
            }
            for (int k=1;k<=n;k++) {
                for (int l=1;l<=n;l++) {
                    dd[k][l]=min(dd[k][l],dd[k][j]+dd[j][l]);
                }
            }
        int anss=0;
        for (int k=1;k<=n;k++) {
            for (int l=1;l<k;l++) {
                if (k!=l)
                anss+=dd[k][l];
            }
        }
        ans=min(ans,anss);
        }
    }
    cout << ans << endl;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

4:Floyd算法

B3611 【模板】传递闭包https://www.luogu.com.cn/problem/B3611

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+10;
const int mod = 998244353;
int d[200][200];
int n;
void solve() {
    cin >> n ;
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++) {
            cin >> d[i][j];
        }
    }
    for (int k=1;k<=n;k++) {
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=n;j++) {
                d[i][j]=d[i][j]||(d[i][k]&&d[k][j]);
            }
        }
    }
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=n;j++) {
            cout << d[i][j] << ' ';
        }
        cout << endl;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

5:Dijkstra算法

P1144 最短路计数https://www.luogu.com.cn/problem/P1144

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6+10;
const int mod = 100003;
const int inf = 2147483647;
struct node {
    int u,w;
};
bool st[N];
int ans[N];
vector<node>mp[N];
int d[N];
void dd(int start) {
    memset(d,0x3f,sizeof(d));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0,start});
    d[start]=0;
    ans[start]=1;
    while (pq.size()!=0) {
        int u = pq.top().first;
        int w = pq.top().second;
        pq.pop();
        if (st[w])continue;
        st[w]=1;
        for (auto x:mp[w]) {
            int a=x.u,b=x.w;
            if (!st[a]&&d[a]>d[w]+b) {
                d[a]=d[w]+b;
                ans[a]=ans[w];
            }else if (d[a]==d[w]+b) {
                ans[a]+=ans[w];
                ans[a]%=mod;
            }
            pq.push({d[a],a});
        }
    }
}
void solve() {
    int n,m;
    cin >> n >> m ;
    for (int i=0;i<m;i++) {
        int u,v;
        cin >> u >> v ;
        mp[u].push_back({v,1});
        mp[v].push_back({u,1});
    }
    dd(1);
    for (int i=1;i<=n;i++) {
        cout << ans[i] <<endl;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int q=1;
    // cin >> q;
    while (q--) {
        solve();
    }
    return 0;
}
相关推荐
汉克老师1 小时前
GESPC++考试五级语法知识(二、埃氏筛与线性筛)课后习题
算法·线性筛·素数·gesp5级·gesp五级·埃氏筛·筛法
0 0 02 小时前
洛谷P4427 [BJOI2018] 求和 【考点】:树上前缀和
开发语言·c++·算法·前缀和
佩奇大王2 小时前
P593 既约分数
java·开发语言·算法
云泽8082 小时前
蓝桥杯算法精讲:贪心算法之推公式例题深度剖析
算法·贪心算法·蓝桥杯
客卿1232 小时前
力扣--组合,子集--回溯法的再探索--总结回溯法
java·算法·leetcode
_日拱一卒2 小时前
LeetCode(力扣):环形链表
算法·leetcode·链表
做怪小疯子2 小时前
Leetcode刷题——链表就地反转
算法·leetcode·链表
仟濹2 小时前
【算法打卡day22(2026-03-14 周六)今日算法or技巧:双指针 & 链表】9个题
数据结构·算法·链表·双指针
RechoYit2 小时前
数学建模——评价与决策类模型
python·算法·数学建模·数据分析