算法刷题笔记 spfa求最短路(C++实现)

69 篇文章 3 订阅
63 篇文章 1 订阅

题目描述

  • 给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
  • 请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出impossible
  • 数据保证不存在负权回路。

输入格式

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

输出格式

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

数据范围

  • 1 ≤ n,m ≤ 10^5,
  • 图中涉及边长绝对值均不超过10000

基本思路

  • 贝尔曼福特算法存在的问题:贝尔曼福特算法中,每次更新最短路径长度数组中的某个元素时,并非一定会修改这个元素的值,这就造成了时间的浪费。SPFA算法就是针对这个问题对算法进行的改进。
  • SPFA算法的基本思路:在贝尔曼福特算法中,每次更新最短路径长度数组的某个元素,如果真的发生更新(例如遍历到边ab时更新了dist[b]),那么一定是因为dist[a]变小了。出于这样的考虑,SPFA算法采用和广度优先搜索类似的思想来对贝尔曼福特算法做优化。
  • SPFA算法的基本流程:创建一个队列,首先将起点放入队列中,之后进入循环直到队列为空。该队列用于存储所有最短路径长度变小了的结点。每一轮迭代中,都从队列中取出队头,并找出队头点的所有出边并进行更新,这是因为当起点到该点的最短路径长度变短时,该点的出边的终点的最短路径长度也会变短。更新成功且队列中不存在的的点需要加入队列。
  • 算法的时间复杂度:对于含有n个点和m条边的有向图,该算法的平均时间复杂度是O(m),最坏时间复杂度是O(n * m)
  • 算法的适用情况:该算法可以处理存在负权边的单源最短路问题。虽然对于只有正权边的图,采用Dijkstra算法是最常见的情况,但是也可以适用SPFA算法进行完成。

实现代码

#include <cstdio>
#include <vector>
#include <cstring>
#include <climits>
#include <queue>
using namespace std;

// 【变量定义】有向图中点的个数和边的条数
int n, m;
// 【变量定义】有向边的起点编号、终点编号和边长(权重)
int x, y, z;
// 【辅助结构体定义】表示有向边的终点和边长的结构体
struct directedEdge
{
    int endPoint;
    int length;
};
// 【辅助常量定义】有向图中点个数的上限
const int N = 100010;
// 【变量定义】用于存储有向图的邻接表
vector<directedEdge> graph[N];
// 【变量定义】从1号点到n号点的最短路径长度计算结果
int result;
// 【变量定义】记录从1号点有向图中每一个点当前的最短路径长度
int distances[N];
// 【变量定义】记录每一个点是否在队列中的数组
bool in_queue[N];

// 【函数定义】基于spfa算法求有向图中1号点到n号点的最短路径长度的函数
int spfa(void)
{
    // 【初始化和准备工作】对最短路径长度数组进行初始化。然后在创建一个队列后,将1号点入队,同时进行记录
    distances[1] = 0;
    for(int i = 2; i <= n; ++ i) distances[i] = (INT_MAX >> 1);
    queue<int> aid_queue;
    aid_queue.push(1);
    in_queue[1] = true;
    // 【算法主体】
    while(aid_queue.empty() == false)
    {
        // 【第一步】每次队列不为空时,从队列中取出队首点的编号
        int current = aid_queue.front();
        aid_queue.pop();
        in_queue[current] = false;
        // 【第二步】从邻接表中找到该点的所有出边,根据出边的边长、起点当前的最短路径长度和终点当前的最短路径长度,判定是否需要更新
        for(directedEdge edge : graph[current])
        {
            if(edge.length + distances[current] < distances[edge.endPoint])
            {
                // 找到了一条更短的路径,则进行最短路径长度更新
                distances[edge.endPoint] = edge.length + distances[current];
                // 【第三步】如果更新了终点的最短路径长度,则判定终点是否在队列中,如果不在则将其放入队列
                if(in_queue[edge.endPoint] == false)
                {
                    aid_queue.push(edge.endPoint);
                    in_queue[edge.endPoint] = true;
                }
            }
        }
    }
    // 【返回结果】将从1号点到n号点的最短路径长度返回
    return distances[n];
}

int main(void)
{
    // 【变量输入】输入有向图中点的个数和边的条数
    scanf("%d%d", &n, &m);
    // 【变量输入】输入有向图中每一条有向边的起点、终点和边长(权重)
    for(int i = 0; i < m; ++ i)
    {
        scanf("%d%d%d", &x, &y, &z);
        // 将有向边的信息存储起来
        graph[x].push_back({y, z});
    }
    // 【计算结果】使用自定义的函数计算从1号点到n号点的最短路径长度
    result = spfa();
    // 【判定和输出】如果最终的最短路径长度合理,则输出最短路径长度
    if(result <= 1e9) printf("%d", result);
    // 如果最终的最短路径长度不合理,则输出"impossible"
    else printf("impossible");
    return 0;
}
  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值