最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)

这篇具有很好参考价值的文章主要介绍了最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)  

文章目录

一、Dijkstra 算法

1、1 朴素版Dijkstra算法

1、1、1 Dijkstra求最短路 I

1、1、2 题解关键思路与与解答

1、2 堆优化版Dijkstra算法

1、2、1 Dijkstra求最短路 II

1、2、2 题解关键思路与答案

二、Bellman-Ford 算法

2、1 Bellman-Ford算法求有边数限制的最短路

2、1、1 题目描述

2、1、2 题解关键思路与解答

三、SPFA 算法

3、1 spfa求最短路

3、1、1 题目描述

3、1、2 题解关键思路与解答

四、Floyd 算法

4、1 Floyd求最短路

4、1、1 题目描述

4、1、2 题解关键思路与解答

五、总结


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:算法与竞赛 👀

💥 标题:最短路径算法 💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️

最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)

一、Dijkstra 算法

  Dijkstra 算法是由荷兰计算机科学家 Edsger Wybe Dijkstra 在1956年发现的算法,戴克斯特拉算法使用类似广度优先搜索的方法解决赋权图的单源最短路径问题。Dijkstra 算法原始版本仅适用于找到两个顶点之间的最短路径,后来更常见的变体固定了一个顶点作为源结点然后找到该顶点到图中所有其它结点的最短路径,产生一个最短路径树。Dijkstra 算法是一个基于「贪心」「广度优先搜索」「动态规划」求一个图中一个点到其他所有点的最短路径的算法。本算法每次取出未访问结点中距离最小的,用该结点更新其他结点的距离,最终如果是连通的话,更新到第n个点的距离即为最短距离。需要注意的是Dijkstra 算法不能有效处理带有负权边的图。Dijkstra 算法是图论中一个较为重要的算法。其中存储所有路径的方式有两种:邻接矩阵(稠密图)、邻接表(稀疏图)

  当图中的路径较多(路径的个数为点数的平方级别的倍数)时,我们用邻接矩阵(二维数组)来存储所有路径,我们称它为朴素版的Dijkstra 算法时间复杂度O(n^2)

  当图中的路径较少(路径的个数为点数为同一级别)时,我们用邻接表(多个单链表)来存储所有路径,我们称它为堆优化版的Dijkstra 算法。时间复杂度O(m*log n)。m为边数,n为点数。

  我们结合下面的例题和代码一起理解一下。

1、1 朴素版Dijkstra算法

1、1、1 Dijkstra求最短路 I

题目来源:Acwing

题目难度:简单

题目描述:给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式:

  第一行包含整数 n 和 m。

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

输出格式:

  输出一个整数,表示 1 号点到 n 号点的最短距离。如果路径不存在,则输出 −1。

数据范围:

  1 ≤ n ≤ 500,
  1 ≤ m ≤ 1e5,
  图中涉及边长均不小于 0,图中涉及边长均不超过10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

1、1、2 题解关键思路与与解答

  从上述的题目描述中,提取出重要信息:求1到n的最短距离,每个点的距离均为正。从而我们可以想到用Dijkstra算法求最短路径。我们发现图中的边数较多,所以我们在这里采用邻接矩阵来存储所有的边。我们结合下面的代码一起理解一下。

#include<bits/stdc++.h>

using namespace std;

const int N=510;
int g[N][N];
int dist[N];
bool st[N];  // 记录该点是否已经确定到第1点的距离为最短
int n,m;

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);  //初始话所有点的距离到第一个点的距离为正无穷
    
    dist[1]=0;
    for(int i=0;i<n-1;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j] && (t==-1 || dist[t]>dist[j]))
                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 main()
{
    cin>>n>>m;
    
    memset(g,0x3f,sizeof g);
    
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=min(g[a][b],c);  //多条路径取最短的
    }
    
    cout<<dijkstra()<<endl;
    return 0;
}

1、2 堆优化版Dijkstra算法

1、2、1 Dijkstra求最短路 II

题目来源:Acwing

题目难度:简单

题目描述:给定一个 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

1、2、2 题解关键思路与答案

  这道题与上面的一道题就有所差距了。该题目依然是求解1到n的最短路径,但是该题中的边数较少,那我们就可以采用堆优化版Dijkstra算法。堆优化版Dijkstra算法采用的是邻接表来存储所有路径。同时利用了小根堆,可以很快的找出当前的未确定的节点中路径最小的点,从而优化了算法的时间复杂度。我们结合代码一起理解一下:

#include<bits/stdc++.h>
using namespace std;
const int N =1.5e5+10;

typedef pair<int,int> PII;  

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

priority_queue<PII,vector<PII>,greater<PII>> q;

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

int dijkstar()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    q.push({0,1});
    
    while(q.size())
    {
        auto t=q.top();
        q.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]>dist[ver]+w[i])
            {
                dist[j]=dist[ver]+w[i];
                q.push({dist[j],j});
            }
        }
    }
    if (dist[n] == 0x3f3f3f3f) 
        return -1;
        
    return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    
    cin>>n>>m;
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    cout<<dijkstar()<<endl;
    return 0;
}

 二、Bellman-Ford 算法

  贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼和莱斯特·福特创立的,求解单源最短路径问题的一种算法。它的原理是对图进行m次松弛操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(mn)。

  Bellman-Ford算法是一种处理存在负权边的单元最短路问题的算法。解决了Dijkstra无法求的存在负权边的问题。 虽然其算法效率不高,但是也有其特别的用处。其实现方式是通过m次迭代求出从源点到终点不超过m条边构成的最短路的路径。一般情况下要求途中不存在负环。但是在边数有限制的情况下允许存在负环。因此Bellman-Ford算法是可以用来判断负环的。

  我们下面结合例题和详解来理解一下Bellman-Ford算法。

2、1 Bellman-Ford算法求有边数限制的最短路

2、1、1 题目描述

题目来源:Acwing

题目难度:简单

题目描述:给定一个 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

2、1、2 题解关键思路与解答

  注意,该体重存在负权边的路径,同时也存在负权回路。但是有边数的限制,所以我们这道题只能采用 Bellman-Ford算法。我们需要限制的k条边,我们需要迭代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];

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");
    else printf("%d\n", dist[n]);

    return 0;
}

三、SPFA 算法

  从上面的介绍我们知道bellmon-ford算法是带着一定的盲目性的,作为对它的优化,spfa采用类似bfs的思想,使用一个队列,只松弛那些可能更新点的距离的边。算法的流程为:

  1. 将除源点之外的所有的点当前距离初始化为无穷,并标记为未入队。源点的当前距离为0,将源点入队。
  2. 取出队首t,遍历t的所有出边,检查是否能更新所连接的点v的当前距离。如果j的当前距离被更新并且j不在队中,则将j入队。重复该操作直到队列为空。

3、1 spfa求最短路

3、1、1 题目描述

题目来源:Acwing

题目难度:简单

题目描述:给定一个 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

3、1、2 题解关键思路与解答

  该题中存在负权边,但是不存在负回路。我们可以直接采用 spfa求最短路求取最短路。我们直接看代码。

#include<bits/stdc++.h>
using namespace std;
const int N =1.5e5+10;

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

queue<int> q;

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

int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    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()
{
    memset(h,-1,sizeof h);
    
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int t=spfa();
    if(t==0x3f3f3f3f)
        printf("impossible");
    else
        cout<<spfa();
    return 0;
}

四、Floyd 算法

  Floyd算法又称为Floyd-Warshell算法,其实Warshell算法是离散数学中求传递闭包的算法,两者的思想是一致的。Floyd算法是求解多源最短路时通常选用的算法,经过一次算法即可求出任意两点之间的最短距离,并且可以处理有负权边的情况(但无法处理负权环)

4、1 Floyd求最短路

4、1、1 题目描述

题目来源:Acwing

题目难度:简单

题目描述:给定一个 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≤ n^2
  1 ≤ m ≤ 20000,
  图中涉及边长绝对值均不超过 10000。

输入样例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:

impossible
1

4、1、2 题解关键思路与解答

   Floyd本质上是动态规划的思想。倘若现在我们想求i到j的最短路径长度,我们限制这条路径上除i和j之外只准经过k个点(这样的路径称为k允许路径),我们在算法的最外层循环每次将k加1,那么当k等于点数时求得的结果便是最优的。我们看代码:

#include<bits/stdc++.h>

using namespace std;

const int N =210,INF=1e9;

int g[N][N];
int n,m,t;

void floyd()
{
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
            }
        }
    }
}
int main()
{
    cin>>n>>m>>t;
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                g[i][j]=0;
            else
                g[i][j]=INF;
        }
    }
    
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=min(g[a][b],c);
    }
    
    floyd();
    
    while(t--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int q=g[x][y];
        
        if(q>INF/2)
        {
            puts("impossible");
        }
        else
        {
            printf("%d\n",q);
        }
    }
    return 0;
}

五、总结

  上述的四个求最短路径的算法都十分重要,我们应该熟悉掌握其用法,熟练写出其模板。这里给大家总结出一张图,可以根据这张图一同记忆一下最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)文章来源地址https://www.toymoban.com/news/detail-404806.html

到了这里,关于最短路径算法( Dijkstra + Bellman-Ford + SPFA + Floyd)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 图论14-最短路径-Dijkstra算法+Bellman-Ford算法+Floyed算法

    https://github.com/Chufeng-Jiang/Graph-Theory/tree/main/src/Chapter11_Min_Path 2.4.1 判断某个顶点的连通性 2.4.2 求源点s到某个顶点的最短路径 存放节点编号和距离 这里的缺点就是,更新node时候,会重复添加节点相同的node,但是路径值不一样。不影响最后结果。 更新pre数组 输出路径 初始化两

    2024年02月04日
    浏览(35)
  • 图论:最短路(dijkstra算法、bellman算法、spfa算法、floyd算法)详细版

    终于是学完了,这个最短路我学了好几天,当然也学了别的算法啦,也是非常的累啊。 话不多说下面看看最短路问题吧。 最短路问题是有向图,要求的是图中一个点到起点的距离,其中我们要输入点和点之间的距离,来求最短路。 下面分为几类题目: 单源汇最短路--一个起

    2024年01月21日
    浏览(44)
  • 最短路之 Bellman-ford 算法

    若有向图有n个点,m条边 。 扫描所有边,对每条边进行一次松弛(即对a,b为端点 , 权重为w的边,dist[b] = min(dist[a] , dist[a] + w )) 重复此流程(最多重复n次)直到没有更新操作发生 给你一张 n 个顶点 m 条边的有向简单图,顶点编号从 1 到 n,每条边都有一个边权,边权为非

    2024年02月17日
    浏览(40)
  • 最短路径算法 | Bellman-Ford Algorithm

    我们在之前的文章中已经讨论了最短路径算法中最经典的Dijkstra‘s Algorithm。然而,Dijkstra\\\'s Algorithm虽然好用,却仍然存在一些缺点即无法解决带有负权重路线的问题,改进后的Dijkstra算法尽管可以解决一些简单的负权重问题,但仍然无法解决带有负循环的图的最短路径问题。

    2024年02月08日
    浏览(51)
  • 图搜索算法详解 - DFS、BFS、Bellman-Ford、Dijkstra

    图搜索算法是许多应用程序的基础,例如社交网络分析、路径规划、数据挖掘和推荐系统。在本文中,我们将深入探讨图搜索算法的世界,探索它们的定义、重要性和实际应用。 图搜索算法是一种用于遍历图的技术,图是由 关系 连接的 节点集合 。在社交网络、网页或生物

    2024年02月16日
    浏览(42)
  • 图论最短路径——Bellman-Ford Algorithm算法

    在图论中,寻找最短路径是一个常见且重要的问题。对于这个问题,有许多算法可以解决,其中最著名的是 Dijkstra 算法。然而,当图中包含负权边时,Dijkstra 算法可能无法正确工作。这时,贝尔曼-福特(Bellman-Ford)算法就派上了用场。 贝尔曼-福特算法可以在含有负权边的图

    2024年04月27日
    浏览(38)
  • Bellman-Ford-贝尔曼-福特-算法求最短路-负环

    Bellman-Ford(贝尔曼-福特)算法基于松弛操作的单源最短路算法。 e[u]存u点的出边的邻点和边权,d[u]存u点到源点的距离。 初始化,ds]=0,d[其它点]=+o; 执行多轮循环。每轮循环,对所有边都尝试进行一次松弛操作; 当一轮循环中没有成功的松弛操作时,算法停止 为什么最坏需要

    2024年02月13日
    浏览(38)
  • C++ | 数据结构与算法 | 单源最短路径 | Dijkstra && Bellman Ford

    (关于代码实现的图结构,可以看图结构的实现这篇文章) Dijkstra的实现与Prim的实现相似,两者都是通过贪心思想实现,它们有什么不同呢?首先Prim算法是针对无向图的最小生成树的,而Dijkstra算法是针对 有向图 的最短路径生成。一个的目的是连接图中的所有顶点,生成一

    2024年02月03日
    浏览(49)
  • 最短路问题 Bellman-Ford(单源最短路径)(图解)

    对于边(u,v),用dist(u)和(u,v)的和尝试更新dist(v):                          dist(v) = min(dist(v) , dist(u)+l(u,v) 注:dist(i)为源点(起点)到i点的距离,l(u,v)为u-v的边权。 Bellman-Ford的基本操作是进行多次迭代,每一轮迭代对图上所有边进行松弛操作,直到

    2024年02月09日
    浏览(38)
  • 【蓝桥杯--图论】Dijkstra、Ballman-Ford、Spfa、Floyd

    今日语录: 每一次挑战都是一次成长的机会 如上所示即为做题时应对的方法 引用与稠密图,即mn^2

    2024年01月23日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包