题目描述
- 给定一个
n
个点m
条边的有向图,图中可能存在重边和自环,边权可能为负数。 - 请你求出
1
号点到n
号点的最短距离,如果无法从1
号点走到n
号点,则输出impossible
。 - 数据保证不存在负权回路。
输入格式
- 第一行包含整数
n
和m
。 - 接下来
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;
}