LeetCode 743. 网络延迟时间
题目描述:
有 n 个网络节点,标记为从 1 到 n。我们会给你一个列表 times,表示信号经过有向边的传递时间。times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点,wi 是一个信号从源节点传递到目标节点需要的时间。现在,我们从某个节点 k 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点都收到信号,则返回 -1。
解题过程:
这个问题可以抽象为:在一个有向加权图中,给定一个起点 k,我们需要找出从 k 到所有其他节点的最短路径。那么,所有节点都收到信号的时间,实际上就是所有最短路径中的最大值(因为我们要等最慢的那个节点收到信号)。如果存在无法到达的节点,我们就返回 -1。这是一个典型的单源最短路径问题。由于边的权重(时间)都是正数,我们可以使用著名的 Dijkstra 算法。
Dijkstra 算法的核心思想是广度优先搜索与贪心策略的结合。它每次都从"未确定最短路径的节点集合"中,选择一个当前距离起点最近的节点,然后通过这个节点来更新其邻居节点到起点的距离。
让我们一步步来:
-
构建图:
首先,我们需要一种数据结构来存储这个图,这样给定一个节点,我们能快速找到它的所有邻居以及到这些邻居的边的权重。通常我们使用"邻接表"来实现。例如,对于一个节点u,我们存储一个列表,列表中的每个元素是(v, w),表示有一条从u到v的边,权重为w。 -
初始化距离数组:
我们需要一个数组dist,dist[i]表示从起点k到节点i的当前已知最短距离。在算法开始时,我们只知道起点到自己的距离是0,所以我们将dist[k]初始化为0。对于其他所有节点,因为我们还不知道距离,所以先初始化为一个"无穷大"的值(在代码中可以用一个非常大的数,比如float('inf')来表示)。 -
初始化优先队列(最小堆):
为了高效地找到当前距离起点最近的未处理节点,我们使用一个优先队列(通常用最小堆实现)。这个队列的元素是(当前已知距离, 节点)。一开始,我们把起点(0, k)放入队列。 -
主循环:
只要队列不为空,我们就重复以下步骤:
a. 出队: 从优先队列中弹出当前距离最小的节点,我们称这个节点为current_node,它到起点的当前距离是current_distance。
b. 检查是否已确定: 由于贪心策略,一旦一个节点被从队列中弹出(基于当前最小的距离),这个距离就是它的最终最短距离,不会再被更新得更小。但是,在算法运行过程中,同一个节点可能会因为被多次更新而多次入队。因此,当我们弹出(current_distance, current_node)时,需要检查一下:如果current_distance大于我们dist数组中记录的current_node的最短距离dist[current_node],说明这个节点之前已经被处理过了(用一个更小的距离),当前这次出队是一个过时的、无效的结果,我们直接忽略它,继续处理队列中的下一个节点。
c. 处理邻居: 如果current_distance等于dist[current_node](说明这是最新、最小的距离),那么我们就来"松弛"这个节点的所有邻居。对于current_node的每一个邻居节点neighbor以及连接它们的边的权重weight,我们计算一条新的路径距离:new_distance = current_distance + weight。
d. 更新距离: 如果计算出的new_distance小于dist数组中记录的neighbor节点的当前最短距离dist[neighbor],那么我们就找到了一个更短的路径。于是,我们更新dist[neighbor]为new_distance,并将这个新的距离和节点(new_distance, neighbor)加入到优先队列中,以便后续处理。 -
得到结果:
当队列为空时,意味着我们已经处理完了所有从起点k可以到达的节点。此时,dist数组中存储的就是从k到每个节点的最短路径长度。我们遍历整个dist数组(注意节点编号从1到n,索引0我们不用):- 如果任何一个距离的值仍然是初始的"无穷大",说明这个节点从
k出发无法到达,根据题目要求,我们返回-1。 - 如果所有节点都是有限的距离,那么我们就找出
dist数组中的最大值,这个最大值就是信号传遍整个网络所需的时间。
- 如果任何一个距离的值仍然是初始的"无穷大",说明这个节点从
总结步骤:
- 用邻接表构建图。
- 初始化距离数组
dist,除起点外全为无穷大,起点为0。 - 初始化最小堆(优先队列),放入起点
(0, k)。 - 当堆不为空:
a. 弹出堆顶(d, node)。
b. 如果d > dist[node],跳过。
c. 否则,遍历node的邻居n和权重w:
- 计算新距离nd = d + w。
- 如果nd < dist[n],则更新dist[n] = nd,并将(nd, n)入堆。 - 遍历
dist数组(从1到n):- 若有值为无穷大,返回
-1。 - 否则,返回数组中的最大值。
- 若有值为无穷大,返回
这个算法高效地找到了单源最短路径,其时间复杂度取决于优先队列的实现,使用二叉堆时约为 O(E log V),其中 E 是边数,V 是节点数。