最短路径 | 743. 网络延迟时间之 Dijkstra 算法和 Floyd 算法

目录


前言:我在做「399. 除法求值」时,看到了基于 Floyd 算法的解决方案,突然想起来自己还没有做过最短路径相关的题。因此找来了「743. 网络延迟时间」作为练习,其本质是在求解一个源点到其他各点的最短路径。

1 基于 Dijkstra 算法

假设源点为 2 \mathrm{2} 2,那么手工模拟如下图所示:

代码的编写在本质上就是实现上述手工模拟过程。

1.1 代码说明

为了表示两点之间没有路径,我们定义两点之间的距离为无穷大:

cpp 复制代码
const int inf = INT_MAX / 2;

说明:这里只是对 i n f \mathrm{inf} inf 进行定义,后面才会进行使用;为什么不直接定义为 i n f = I N T − M A X \mathrm{inf = INT_{-}MAX} inf=INT−MAX?因为在更新距离时涉及加法操作,而 I N T − M A X \mathrm{INT_{-}MAX} INT−MAX 可能让加法越界,所以我们取其一半来表示无穷大。

Step1:构建图

由于题目通常给出的是边的起点、终点以及权值,而非存储了图结构的二维数组,因此无论是 Dijkstra 算法还是 Floyd 算法,我们都需要完成图的构建。代码如下:

cpp 复制代码
vector<vector<int>> graph(n + 1, vector<int>(n + 1, inf));
for (auto & t : times)
  graph[t[0]][t[1]] = t[2];

逻辑非常简单:① 创建一个二维数组 g r a p h \mathrm{graph} graph;② g r a p h [ i ] [ j ] \mathrm{graph[i][j]} graph[i][j] 表示边 < i , j > \mathrm{<i, j>} <i,j> 的权值。

说明:初始时如果两点之间没有边,那么认为两点之间的距离为 i n f \mathrm{inf} inf 无穷大。

Step2:定义数组

cpp 复制代码
vector<int> dist(n + 1, inf);
dist[k] = 0;
vector<int> used(n + 1, 0);
  • d i s t \mathrm{dist} dist 数组用于存储每一轮源点 k \mathrm{k} k 到其他点的距离;
  • u s e d \mathrm{used} used 数组用于表明当前点是否已经被纳入集合。

说明:由于 k \mathrm{k} k 到自己的距离为 0 \mathrm{0} 0,因此有 d i s t [ k ] = 0 \mathrm{dist[k] = 0} dist[k]=0;为什么不直接让 u s e d [ k ] = 1 \mathrm{used[k] = 1} used[k]=1?由于在纳入每个点时都会更新源点 k \mathrm{k} k 到其他点的距离,因此我们在初始时并不直接将 k \mathrm{k} k 纳入集合,而是放到后面和其他点统一处理,从而避免了需要在初始时更新 d i s t \mathrm{dist} dist 数组的值的问题。

Step3:纳入并更新距离

cpp 复制代码
for (int i = 1; i <= n; ++i) {
  // 查找距离源点最近的点
  int s = -1;
  for (int t = 1; t <= n; ++t) {
    if (!used[t] && (s == -1 || dist[s] > dist[t]))
      s = t;
  }
  // 纳入该点
  used[s] = 1;
  // 更新距离
  for (int j = 1; j <= n; ++j)
    dist[j] = min(dist[j], dist[s] + graph[s][j]);
}

其中 s \mathrm{s} s 用于查找当前距离源点最近的点, t \mathrm{t} t 用于遍历所有未被纳入的点。

说明:由于初始时只有 d i s t [ k ] = 0 \mathrm{dist[k] = 0} dist[k]=0,而其他距离被默认为 i n f \mathrm{inf} inf 无穷大,因此第一个被纳入的一定是源点 k \mathrm{k} k。

Step4:返回结果

由于题目提问「需要多久才能使所有节点都收到信号」,因此我们返回源点 k \mathrm{k} k 到其他点的最短距离的最大值即可。代码如下:

cpp 复制代码
int ans = * max_element(dist.begin() + 1, dist.end());
return ans == inf ? -1 : ans;

如果最大值是 i n f \mathrm{inf} inf,那么说明源点 k \mathrm{k} k 无法到达某些点,因此返回 − 1 \mathrm{-1} −1。

1.2 完整代码

cpp 复制代码
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
  const int inf = INT_MAX / 2;
  vector<vector<int>> graph(n + 1, vector<int>(n + 1, inf));
  for (auto & t : times)
    graph[t[0]][t[1]] = t[2];

  vector<int> dist(n + 1, inf);
  dist[k] = 0;
  vector<int> used(n + 1, 0);

  for (int i = 1; i <= n; ++i) {
    int s = -1;
    for (int t = 1; t <= n; ++t) {
      if (!used[t] && (s == -1 || dist[s] > dist[t]))
        s = t;
    }
    used[s] = 1;
    for (int j = 1; j <= n; ++j)
      dist[j] = min(dist[j], dist[s] + graph[s][j]);
  }

  int ans = * max_element(dist.begin() + 1, dist.end());
  return ans == inf ? -1 : ans;
}

2 基于 Floyd 算法

说明:上图只是给出一个示例,并没有把整个更新过程画完整,请自行脑补。

2.1 代码说明

Step1:构建图(与 Dijkstra 算法一致)

Step2:更新距离

Floyd 算法的核心:不断尝试在点 i \mathrm{i} i 和点 j \mathrm{j} j 之间加入其他点 k \mathrm{k} k 作为中间点,如果加入 k \mathrm{k} k 之后的距离比加入 k \mathrm{k} k 之前的距离短,那么就更新点 i \mathrm{i} i 和点 j \mathrm{j} j 之间的距离。重复上述操作 n \mathrm{n} n 次,即可计算出任意两点之间的最短路径。

cpp 复制代码
for (int k = 1; k <= n; ++k) {
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) {
      if (graph[i][k] >= 0 && graph[k][j] >= 0)
        graph[i][j] = graph[i][j] >= 0 ?
        min(graph[i][j], graph[i][k] + graph[k][j])
        : graph[i][k] + graph[k][j];
    }
  }
}

注意:中间点 k \mathrm{k} k 必须在最外层循环,否则一些路径无法被更新到;为什么判断条件是 > = 0 \mathrm{>= 0} >=0?因为题目给出的边的权值的范围为 [ 0 , 100 ] \mathrm{[0,100]} [0,100],所以需要包含 0 \mathrm{0} 0。

Step3:返回结果

cpp 复制代码
int ans = -1;
for (int j = 1; j <= n; ++j) {
  if (graph[k][j] == -1 && k != j)
    return -1;
  else if (k != j)
    ans = max(ans, graph[k][j]);
}
return ans;

由于我们只需要源点 k \mathrm{k} k 到其他点的距离,因此只需要遍历 g r a p h \mathrm{graph} graph 中的第 k \mathrm{k} k 行。

说明:由于我们在本方案中定义两点之间没有路径时的边的权值为 − 1 \mathrm{-1} −1,因此只要 g r a p h [ k ] [ j ] = = − 1 \mathrm{graph[k][j] == -1} graph[k][j]==−1,就说明源点 k \mathrm{k} k 无法到达点 j \mathrm{j} j,因此返回 − 1 \mathrm{-1} −1。

2.2 完整代码

cpp 复制代码
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
  vector<vector<int>> graph(n + 1, vector<int>(n + 1, -1));
  for (auto & t : times)
    graph[t[0]][t[1]] = t[2];

  for (int k = 1; k <= n; ++k) {
    for (int i = 1; i <= n; ++i) {
      for (int j = 1; j <= n; ++j) {
        if (graph[i][k] >= 0 && graph[k][j] >= 0)
          graph[i][j] = graph[i][j] >= 0 ?
          min(graph[i][j], graph[i][k] + graph[k][j])
          : graph[i][k] + graph[k][j];
      }
    }
  }

  int ans = -1;
  for (int j = 1; j <= n; ++j) {
    if (graph[k][j] == -1 && k != j)
      return -1;
    else if (k != j)
      ans = max(ans, graph[k][j]);
  }
  return ans;
}

虽然 Floyd 算法写起来没有 Dijkstra 算法繁琐,但是针对该问题的时间复杂度更高。

相关推荐
海琴烟Sunshine1 小时前
Leetcode 26. 删除有序数组中的重复项
java·算法·leetcode
PAK向日葵1 小时前
【算法导论】NMWQ 0913笔试题
算法·面试
PAK向日葵1 小时前
【算法导论】DJ 0830笔试题题解
算法·面试
PAK向日葵1 小时前
【算法导论】LXHY 0830 笔试题题解
算法·面试
麦麦麦造2 小时前
DeepSeek突然发布 V3.2-exp,长文本能力加强,价格进一步下探
算法
lingran__3 小时前
速通ACM省铜第十七天 赋源码(Racing)
c++·算法
MobotStone3 小时前
手把手教你玩转AI绘图
算法
CappuccinoRose4 小时前
MATLAB学习文档(二十二)
学习·算法·matlab
学c语言的枫子5 小时前
数据结构——基本查找算法
算法
yanqiaofanhua5 小时前
C语言自学--自定义类型:结构体
c语言·开发语言·算法