算法基础系列第三章——图论之最短路径问题(2)

简介: 算法基础系列第三章——图论之最短路径问题(2)

bellman-ford算法

微信图片_20221018121035.jpg

bellman-ford算法和SPFA算法是用是单源的背景下且存在负权边情况下使用。整体来说,SPFA是优于bellman-ford算法的,但是对于边数有限制的最短路问题,就只能用bellman-ford算法了

微信图片_20221018121101.png

例题描述——有边数限制的最短路

微信图片_20221018121126.jpg

传送门


参考代码(C++版本)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 510 , M = 10010;
int n,m,k;
int dist[N];
int backup[N];
//存放点和权重的结构体
struct Edge{
    int a,b,w;
}edges[M];
void bellman_ford()
{
    //初始化环节
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    //迭代k条边
    for(int i = 0 ; i < k ; i++)
    {
        //备份
        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);
        }
    }
}
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};
    }
    //调用函数
    bellman_ford();
    //输出结果
    if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n",dist[n]);
    return 0;
}

算法模板


bellman-ford算法实现的流程图如下:

image.png

bellman-ford算法代码描述如下:

  int n, m;   // n表示点数,m表示边数
  int dist[N];    // dist[x]存储1到x的最短路距离
  struct Edge   // 边,a表示出点,b表示入点,w表示边的权重
  {
    int a, b, w;
  }edges[M];
  void bellman_ford()
  {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
      for (int j = 0; j < m; j ++ )
      {
        int a = edges[j].a, b = edges[j].b, w = edges[j].w;
        if (dist[b] > dist[a] + w)
          dist[b] = dist[a] + w;
      }
    }
  }

细节落实


一、备份——为了保证边数限制的条件可以不被打破

        memcpy(backup,dist,sizeof dist);

微信图片_20221018121321.png

为了避免这种,虽然确实是在外层循环运行的边数k下进行的操作,但是内层循环去探索点的时候,发生类似的情况,所以采取了备份了操作,来遏制这种情况微信图片_20221018121343.png

SPFA 算法


spfa在国际上通常是称呼的是"队列优化的Bellman-Ford"算法


例题描述

image.jpeg

参考代码(C++版本)

#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 ++ ;
}
void 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])
            {
                //符合条件,更新从1号点到j的距离
                dist[j] = dist[t] + w[i];
                //如果这个点没有在队列中
                if (!st[j])
                {
                    //入队,标记
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
}
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);
    }
    //调用函数
    spfa();
    //输出
    if (dist[n] == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", dist[n]);
    return 0;
}

算法模板


SPFA算法实现的流程图如下:微信图片_20221018121537.png

SPFA算法的代码描述如下:

  int n;                  // 总点数
  int h[N], w[N], e[N], ne[N], idx;   // 邻接表存储所有边
  int dist[N];              // 存储每个点到1号点的最短距离
  bool st[N];               // 存储每个点是否在队列中
  void spfa()
  {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while (q.size())
    {
      auto 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])   // 如果队列中已存在j,则不需要将j重复插入
          {
            q.push(j);
            st[j] = true;
          }
        }
      }
    }
  }

细节落实


一、入队与标记

在用t更新到新的点j以后,记得判断这个点有没有被标记过,假如没有,将它加入队列并放入st数组进行标记


二、联想记忆

SPFA的本质是用队列优化了bellman-ford算法,但是在实现上有长得很像堆优化版本的dijkstra算法,SPFA用的队列,dijkstra用的优先队列


Floyd算法


为了求出图中任意两点之间的最短路径,也就是最短路大纲中的多源汇最短路问题。对于求任意两点之间的最短路问题,图一般都比较稠密,实现也很简单暴力。


Floyd算法的思想:

把多源的最短路拆分成N次的单源最短路。

单源最短路在Dijkstra算法中是用了两个循环,时间复杂度是O(n2),Floyd这里就在最外层套一层循环N,所以时间复杂度也就是O(n3)


例题描述

微信图片_20221018121707.jpg

参考代码(C++版本)

#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
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();
    //处理Q次询问
    while(Q--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        if(d[a][b] > INF / 2) puts("impossible");
        else printf("%d\n",d[a][b]);
    }
    return 0;
}

算法模板


Floyd算法实现的流程图:

微信图片_20221018121756.png

Floyd算法代码描述:

初始化:
    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;
  // 算法结束后,d[a][b]表示a到b的最短距离
  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]);
  }

细节落实


一、初始化

        if (i == j) d[i][j] = 0;
        else d[i][j] = INF;

d数组表示的是从i到j的距离,所以初始化的时候,要将i == j的时候,初始化为0,其他位置初始化为一个无穷大的数


二、输出

因为边权可以为负,所以d[a][b]不一定还是INF了,因此判断条件改为d[a][b]仍然是一个比较大的数就行

        if(d[a][b] > INF / 2) puts("impossible");
        else printf("%d\n",d[a][b]);
相关文章
|
17天前
|
机器学习/深度学习 安全 算法
【图论】【割点】【C++算法】928. 尽量减少恶意软件的传播 II
【图论】【割点】【C++算法】928. 尽量减少恶意软件的传播 II
|
2月前
|
算法 测试技术 C++
【动态规划】【图论】【C++算法】1575统计所有可行路径
【动态规划】【图论】【C++算法】1575统计所有可行路径
|
4月前
|
算法
Floyd 最短路径【学习算法】
Floyd 最短路径【学习算法】
30 0
|
6月前
|
存储 算法 索引
数据结构与算法之图论及其相关算法
图的基本介绍 线性表局限于一个直接前驱和一个直接后继的关系,树也只能有一个直接前驱也就是父节点,当我们需要表示多对多的关系时, 这里我们就用到了图。 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。 结点也可以称为顶点。如图: 简单来说,图就是由顶点的有穷非空集合和顶点之间的边组成的集合。通常表示为:G(V,E),其中,G 表示一个图,V 表示顶点的集合,E 表示边的集合。 然后我们说说图中的一些常见概念: 节点(Vertex):图中的基本元素,用于表示某个实体。 边(Edge):连接两个节点的线段,用于表示节点之间的关系。 度(Degree):
32 0
|
2月前
|
算法 测试技术 C++
【动态规划】【图论】【C++算法】1928规定时间内到达终点的最小花费
【动态规划】【图论】【C++算法】1928规定时间内到达终点的最小花费
|
11天前
|
算法 定位技术 Windows
R语言最大流最小割定理和最短路径算法分析交通网络流量拥堵问题
R语言最大流最小割定理和最短路径算法分析交通网络流量拥堵问题
16 4
|
5月前
|
算法 存储 内存技术
【AcWing算法基础课】第三章 搜索与图论(2)
特点:尽可能先向纵深方向搜索。使用stack实现。所需空间O(h)(h为深度)。不具有“最短性”。
51 0
【AcWing算法基础课】第三章 搜索与图论(2)
|
1月前
|
人工智能 算法 Java
【算法设计与分析】— —单源最短路径的贪心算法
【算法设计与分析】— —单源最短路径的贪心算法
32 0
|
3月前
|
算法 C++ NoSQL
|
3月前
|
存储 算法 定位技术
图论算法dijkstra dfs bfs以及动态规划
图论算法dijkstra dfs bfs以及动态规划
34 0