最短路问题

最短路问题

最短路问题

最短路算法(Shortest Path Algorithm)是用来解决图中两点之间的最短路径的问题。常见的应用包括:地图导航、网络路由、游戏寻路等。根据图的性质(有向/无向、是否有负权边)和需求(单源/多源),常见的最短路算法有以下几种:


🚩1. Dijkstra 算法(迪杰斯特拉)

适用:边权非负、单源最短路

  • 思路 :贪心 + 堆优化

    从起点出发,每次选择当前距离起点最近的未访问点,更新它的邻居。

  • 复杂度

    • 普通实现:O(n²)
    • 堆优化:O(m log n)(用优先队列)
cpp 复制代码
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
vector<int> dist(n+1, INF);
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
    auto [d, u] = pq.top(); pq.pop();
    if (d > dist[u]) continue;
    for (auto [v, w] : adj[u]) {
        if (dist[v] > dist[u] + w) {
            dist[v] = dist[u] + w;
            pq.push({dist[v], v});
        }
    }
}

🚩2. Bellman-Ford 算法

适用:可处理负权边,单源最短路

  • 思路 :动态规划思想,最多做 n-1 轮松弛操作
  • 可检测负权环
  • 复杂度:O(nm)
cpp 复制代码
vector<int> dist(n+1, INF);
dist[start] = 0;
for (int i = 1; i < n; ++i)
    for (auto [u, v, w] : edges)
        if (dist[u] + w < dist[v])
            dist[v] = dist[u] + w;

🚩3. SPFA 算法(队列优化的 Bellman-Ford)

适用:单源最短路,能处理负权边,效率比 Bellman-Ford 好很多(在稀疏图中)

  • 思路:维护一个队列,只处理"可能更新别人的点"
  • 复杂度:最坏 O(nm),实际通常很快

🚩4. Floyd-Warshall 算法

适用:多源最短路径(任意两点)

  • 思路:动态规划,三层循环枚举中转点 k、起点 i、终点 j
  • 复杂度:O(n³)
  • 能处理负边权,但不能有负权环
cpp 复制代码
for (int k = 1; k <= n; ++k)
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);

🚩5. A* 算法(启发式最短路)

适用:用于地图类路径规划,效率高于 Dijkstra(例如游戏寻路)

  • 思路:在 Dijkstra 的基础上,加一个"估价函数 h(x)"(例如曼哈顿距离),引导搜索方向
  • 复杂度:依赖启发函数,理论上最坏仍是 O(n log n)

🧠如何选择算法?

图类型/需求 推荐算法
单源、无负权 Dijkstra
单源、有负权 Bellman-Ford / SPFA
多源任意两点 Floyd-Warshall
实时导航/地图 A*

3.6Dijkstra

3.6.1Dijkstra求最短路I

给定一个 n个点 m条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1号点到 n号点的最短距离,如果无法从 1号点走到 n号点,则输出 −1。

输入格式

第一行包含整数 n和 m。

接下来 m行每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。

输出格式

输出一个整数,表示 11 号点到 nn 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n≤500,

1≤m≤1e5,

图中涉及边长均不超过10000。

输入样例:

3 3

1 2 2

2 3 1

1 3 4

输出样例:

3

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e6+10;
const int M=2*N;

int h[N],e[N],w[N],ne[N],idx;
int g[510][510];
int dist[N];
int n,m;
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 dijkstra1()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[j]<dist[t])) t=j;
        st[t]=true;
        for(int j=1;j<=n;j++)
            dist[j]=min(dist[j],dist[t]+g[t][j]);
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}

//邻接表法
int dijkstra2()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dist[j]<dist[t])) t=j;
        st[t]=true;
        for(int j=h[t];j!=-1;j=ne[j])
        {
            int k=e[j];
            dist[k]=min(dist[k],dist[t]+w[j]);
        }
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c);
        add(a,b,c);
    }
    cout<<dijkstra2();
    return 0;
}
3.6.2Dijkstra求最短路II

给定一个 n个点 m条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出 1号点到 n号点的最短距离,如果无法从 1号点走到 n号点,则输出 −1。

输入格式

第一行包含整数 n和 m。

接下来 m行每行包含三个整数x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1号点到 n号点的最短距离。

如果路径不存在,则输出−1。

数据范围

1≤n,m≤1.5×1e5,

图中涉及边长均不小于 0,且不超过10000。

数据保证:如果最短路存在,则最短路的长度不超过 1e9。

输入样例:

3 3

1 2 2

2 3 1

1 3 4

输出样例:

3

用小根堆即可,我们把每次求离起点最近进行了堆优化,可以缩短时间。

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>//堆的头文件

using namespace std;

typedef pair<int, int> PII;//堆里存储距离和节点编号

const int N = 1e6 + 10;

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 dijkstra()
{
    memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆
    heap.push({0, 1});//插入距离和节点编号

    while (heap.size())
    {
        auto t = heap.top();//取距离源点最近的点
        heap.pop();

        int ver = t.second, distance = t.first;//ver:节点编号,distance:源点距离ver 的距离

        if (st[ver]) continue;//如果距离已经确定,则跳过该点
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});//距离变小,则入堆
            }
        }
    }

    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);
    }

    cout << dijkstra() << endl;

    return 0;
}

3.7bellman-ford

3.7.1有边数限制的最短路

给定一个 n个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从 1号点到 n号点的最多经过 k条边的最短距离,如果无法从 1号点走到 n号点,输出 impossible。

注意:图中可能 存在负权回路 。

输入格式

第一行包含三个整数 n,m,k。

接下来 m行,每行包含三个整数x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。

点的编号为 1∼n。

输出格式

输出一个整数,表示从 1号点到 n号点的最多经过 k条边的最短距离。

如果不存在满足条件的路径,则输出 impossible。

数据范围

1≤n,k≤500,

1≤m≤10000,

1≤x,y≤n,

任意边长的绝对值不超过10000。

输入样例:

3 3 1

1 2 1

2 3 1

1 3 3

输出样例:

3

思路:

cpp 复制代码
#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];

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

    dist[1] = 0;
    for (int i = 0; i < k; i ++ )
    {
        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);
        }
    }
}

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;
}

3.8spfa

3.8.1spfa求最短路

给定一个 n个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出 1号点到 n号点的最短距离,如果无法从 1号点走到 n号点,则输出 impossible。

数据保证不存在负权回路。

输入格式

第一行包含整数 n和 m。

接下来 m行每行包含三个整数x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。

输出格式

输出一个整数,表示 1号点到 n号点的最短距离。

如果路径不存在,则输出 impossible。

数据范围

1≤n,m≤1e5,

图中涉及边长绝对值均不超过10000。

输入样例:

3 3

1 2 5

2 3 -3

1 3 4

输出样例:

2

spfa用的最多

cpp 复制代码
#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;
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;
                }
            }
        }
    }

    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;
}
3.8.2spfa判断负环

给定一个 n个点 m条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你判断图中是否存在负权回路。

输入格式

第一行包含整数 n和 m。

接下来 m行每行包含三个整数 x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。

输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No。

数据范围

1≤n≤2000,

1≤m≤10000,

图中涉及边长绝对值均不超过 10000。

输入样例:

3 3

1 2 -1

2 3 4

3 1 -4

输出样例:

Yes

cpp 复制代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[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 ++ ;
}

bool 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;
}

3.9Floyd

3.9.1Floyd求最短路

给定一个 n个点 m条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定 k个询问,每个询问包含两个整数 x 和 y,表示查询从点 x到点 y的最短距离,如果路径不存在,则输出 impossible。

数据保证图中不存在负权回路。

输入格式

第一行包含三个整数 n,m,k。

接下来 m行,每行包含三个整数x,y,z,表示存在一条从点 x到点 y的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x,y,表示询问点 x到点 y的最短距离。

输出格式

共 k行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible。

数据范围

1≤n≤200,

1≤k≤n2

1≤m≤20000,

图中涉及边长绝对值均不超过10000。

输入样例:

3 3 2

1 2 1

2 3 2

1 3 1

2 1

1 3

输出样例:

impossible

1

多源汇最短路

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

const int N = 210, INF = 1e9;

int n, m, Q;
int d[N][N];

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()
{
    scanf("%d%d%d", &n, &m, &Q);
    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;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }
    floyd();
    while (Q--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int t = d[a][b];
        if (t > INF / 2) puts("impossible");
        else printf("%d\n", t);
    }
    return 0;
}
相关推荐
福居路冥想的草莓27 分钟前
逆波兰表达式求值(中等)
数据结构
oioihoii35 分钟前
C++23 新特性:为 std::pair 的转发构造函数添加默认实参
算法·c++23
智者知已应修善业1 小时前
【验证哥德巴赫猜想(奇数)】2021-11-19 15:54
c语言·c++·经验分享·笔记·算法
cdut_suye2 小时前
【Linux系统】从零开始构建简易 Shell:从输入处理到命令执行的深度剖析
java·linux·服务器·数据结构·c++·人工智能·python
-qOVOp-2 小时前
zst-2001 历年真题 设计模式
java·算法·设计模式
yaoshengvalve2 小时前
V型球阀材质性能深度解析:专攻颗粒、料浆与高腐蚀介质的工业利器-耀圣
开发语言·网络·数据结构·c++·安全·材质
evolution_language2 小时前
LintCode第68题-二叉树的前序遍历,第67题-二叉树的后序遍历
数据结构·算法·新手必刷编程50题
passionSnail2 小时前
《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-俄罗斯方块:用旋转矩阵打造经典
算法·matlab·矩阵·游戏程序·交互
yxc_inspire2 小时前
C++STL在算法竞赛中的应用详解
c++·算法·stl
James. 常德 student2 小时前
leetcode-hot-100(哈希)
算法·leetcode·哈希算法