网络延迟时间

标签: 深度优先搜索 广度优先搜索 最短路 堆(优先队列)

难度: Medium

n 个网络节点,标记为 1 到 n

给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。

现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1

示例 1:

输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
输出:2

示例 2:

输入:times = [[1,2,1]], n = 2, k = 1
输出:1

示例 3:

输入:times = [[1,2,1]], n = 2, k = 2
输出:-1

提示:

  • 1 <= k <= n <= 100
  • 1 <= times.length <= 6000
  • times[i].length == 3
  • 1 <= ui, vi <= n
  • ui != vi
  • 0 <= wi <= 100
  • 所有 (ui, vi) 对都 互不相同(即,不含重复边)

Submission

运行时间: 45 ms

内存: 17.7 MB

class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        # 使用邻接矩阵存储图
        graph = [[float('inf')] * n for _ in range(n)]
        for source, target, time in times:
            graph[source-1][target-1] = time
        
        # 初始化距离数组,所有节点到起始节点的距离设为无穷大
        distances = [float('inf')] * n
        distances[k-1] = 0  # 起始节点到自身的距离是0

        # 初始化访问状态数组,用于标记节点是否已被访问
        visitedNodes = [False] * n
        for _ in range(n):
            # 在未访问的节点中找到距离起始节点最近的节点
            currentNode = -1
            for neighbor, visited in enumerate(visitedNodes):
                if not visited and (currentNode == -1 or distances[neighbor] < distances[currentNode]):
                    currentNode = neighbor
            
            # 标记当前节点为已访问
            visitedNodes[currentNode] = True
            # 更新当前节点的所有未访问邻居节点的距离
            for neighbor, time in enumerate(graph[currentNode]):
                distances[neighbor] = min(distances[neighbor], distances[currentNode] + time)
        
        # 计算从起始节点到所有节点的最长最短距离
        maxDistance = max(distances)

        # 如果所有节点都是可达的,返回最长的最短距离;否则返回-1
        return maxDistance if maxDistance < float('inf') else -1


Explain

这个解决方案使用邻接矩阵来表示图,并通过一个修改版的迪杰斯特拉算法来找到从节点K到所有其他节点的最短路径。算法开始时,初始化一个距离数组,所有节点的距离设为无穷大,除了起点K,其距离设为0。然后使用一个布尔数组来跟踪每个节点的访问状态。对于每一次循环,选择一个未访问的且距离最小的节点作为当前节点,然后遍历其所有邻居,更新邻居的最短距离。这一过程重复n次,每次处理一个节点。最后,从距离数组中找出最大值,如果所有节点都已访问(即最大值不是无穷大),则返回此最大值作为结果,否则返回-1。

时间复杂度: O(n^2)

空间复杂度: O(n^2)

class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        # 使用邻接矩阵存储图
        graph = [[float('inf')] * n for _ in range(n)]
        for source, target, time in times:
            graph[source-1][target-1] = time
        
        # 初始化距离数组,所有节点到起始节点的距离设为无穷大
        distances = [float('inf')] * n
        distances[k-1] = 0  # 起始节点到自身的距离是0
        
        # 初始化访问状态数组,用于标记节点是否已被访问
        visitedNodes = [False] * n
        for _ in range(n):
            # 在未访问的节点中找到距离起始节点最近的节点
            currentNode = -1
            for neighbor, visited in enumerate(visitedNodes):
                if not visited and (currentNode == -1 or distances[neighbor] < distances[currentNode]):
                    currentNode = neighbor
            
            # 标记当前节点为已访问
            visitedNodes[currentNode] = True
            # 更新当前节点的所有未访问邻居节点的距离
            for neighbor, time in enumerate(graph[currentNode]):
                distances[neighbor] = min(distances[neighbor], distances[currentNode] + time)
        
        # 计算从起始节点到所有节点的最长最短距离
        maxDistance = max(distances)
        
        # 如果所有节点都是可达的,返回最长的最短距离;否则返回-1
        return maxDistance if maxDistance < float('inf') else -1

Explore

在这个算法中选择使用邻接矩阵而不是邻接列表的主要原因是因为邻接矩阵可以更方便地访问任意两个节点之间的直接连接信息,特别是在需要频繁查询特定节点对之间是否存在边以及边的权重时。邻接矩阵使得在迪杰斯特拉算法中更新节点距离和检查节点连接状态变得直观和快速。虽然邻接矩阵在稀疏图中可能会导致空间效率降低,但在本题中可能是为了简化实现或者因为节点数量不是特别大,所以空间复杂度可以接受。

在使用邻接矩阵表示图时,确定当前节点的所有邻居节点非常直接。具体来说,可以通过遍历当前节点对应的行或列(取决于矩阵的行和列分别代表了什么)来检查每一个其他节点。如果当前节点为 i,那么我们可以遍历图矩阵 graph[i][j] 的所有元素,其中 j 是其他所有可能的节点索引。如果 graph[i][j] 不是无穷大,则节点 j 是节点 i 的邻居节点,并且存在一条从 i 到 j 的边,权重为 graph[i][j]。

在迪杰斯特拉算法的实现中,图中的环或自环可以被自然地处理,特别是在邻接矩阵的表示下。对于自环的情况,即一个节点i到自身的边,这在邻接矩阵中表示为 graph[i][i]。在算法执行过程中,如果 graph[i][i] 的值更新为一个有意义的最小值(小于无穷大),它可能会影响到从该节点出发的路径成本计算。然而,通常在实现迪杰斯特拉算法时,会忽略自环,因为它们不会对从一个节点到另一个不同节点的最短路径计算产生影响。

在这个具体的算法实现中,并没有使用效率更高的数据结构如优先队列来优化查找过程。算法直接遍历所有节点来找到距离最小的未访问节点,这样的操作是 O(n) 的时间复杂度。在节点数量非常大的情况下,这种方法效率不是最优的。使用优先队列(特别是最小堆)可以将寻找最小距离节点的操作优化到 O(log n) 的时间复杂度,从而使整体算法的时间复杂度更低,特别是在稠密图中。然而,本题解中未采用这种优化可能是为了保持代码的简洁性或是出于教学目的。