网络延迟时间
典型的单源最短路径问题,解决这种问题的常用方法有优先队列,DFS, BFS, Dijkstra, Bellman-Ford, SPFA, Floyd-Warshall,下面就对该题采用这7种方法进行求解。
先放上各种算法的对比,思路可更清晰。
使用heap设计优先队列时,需要设计一个visited集合,存储过去处理过的节点;使用普通队列(deque)时,需要设计distence数组,记录源点到当前节点的最小距离。
PriorityQueue
似乎发现优先队列中,使用heapq比使用PriorityQueue效率更高?
另外需要注意的是,设计的visited集合何时add当前处理的节点要注意,不能在while循环中的for循环中去add!!!因为这样会使得在下一轮循环前会对len(visited)进行判断,这样便出错了!而应该在进入for循环之前进行相应的add操作!
from collections import defaultdict
from heapq import *
class Solution:
def networkDelayTime(self, times, N, K):
graph = defaultdict(dict)
for x, y, t in times:
graph[x][y] = t
visited = set()
q, totalTime = [(0, K)], 0
heapify(q)
while q:
t, node = heappop(q)
if node in visited: continue
visited.add(node)
if len(visited) == N: return t
for cur_node, cur_t in graph[node].items():
if cur_node not in visited:
heappush(q, (t + cur_t, cur_node))
return -1
DFS求解
distance[node]记录的是最早到达node的时间(最短距离)。
为了加快速度,在访问每个节点时,若传递该信号的时间比已有信号到达的时间长,则我们退出该信号。
该方法可能会超时。
from collections import defaultdict
class Solution:
def networkDelayTime(self, times, N, K):
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
distance = {node : float('inf') for node in range(1, N + 1)}
self.DFS(graph, distance, K, 0)
totalTime = max(distance.values())
return totalTime if totalTime != float('inf') else -1
def DFS(self, graph, distance, node, timeSoFar):
if timeSoFar > distance[node]: # 信号已经到达此节点,所以不需要探索这个节点
return
distance[node] = timeSoFar
for neighbour, time in sorted(graph[node]):
self.DFS(graph, distance, neighbour, timeSoFar + time)
BFS求解
本质还是Dijkstra算法,都是根据路径更新规则进行选择。该代码其实与Dijkstra一致。
from collections import deque
from collections import defaultdict
class Solution:
def networkDelayTime(self, times, N, K):
dic = defaultdict(list)
for u, v, w in times:
dic[u].append((v, w))
q = deque([(K, 0)])
visited = {K : 0}
while q:
curNode, curTime = q.popleft()
for node, time in dic[curNode]:
t = curTime + time
if node not in visited or t < visited[node]:
visited[node] = t
q.append((node, t))
return max(visited.values()) if len(visited) == N else -1
Dijkstra
与BFS代码一致!
其实再仔细对比优先队列的代码,Dijkstra代码中的最短路径更新公式即if node not in visited or t < visited[node]
,优先队列的使用就相当于该作用!
class Solution:
def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int:
weight = collections.defaultdict(dict)
for u, v, w in times:
weight[u][v] = w
heap = [(0, K)]
dist = {}
while heap:
time, u = heapq.heappop(heap)
if u not in dist:
dist[u] = time
for v in weight[u]:
heapq.heappush(heap, (dist[u] + weight[u][v], v))
return max(dist.values()) if len(dist) == N else -1
Bellman-Ford
class Solution:
def networkDelayTime(self, times, N, K):
dist = [float('inf')] * N
dist[K - 1] = 0
for _ in range(N - 1): # 最多松弛N - 1次达到最优
for u, v, w in times:
if dist[u - 1] + w < dist[v - 1]:
dist[v - 1] = dist[u - 1] + w
return max(dist) if max(dist) != float('inf') else -1
SPFA
Bellman-Ford每一次松弛都是随机的,针对这有点,便有了SPFA(Dijkstra也是在这一点上进行改进),它是Bellman-Ford的一种提升算法(队列优化进行松弛,具体可以参考我的上一篇博文讲解)。
from collections import deque
from collections import defaultdict
class Solution:
def networkDelayTime(self, times, N, K):
dist = [float('inf')] * N
dist[K - 1] = 0
dic = defaultdict(dict)
for u, v, w in times:
dic[u - 1][v - 1] = w
q = deque([K - 1])
while q:
node = q.popleft()
for v in dic.get(node, []):
if dist[node] + dic[node][v] < dist[v]:
dist[v] = dist[node] + dic[node][v]
q.append(v)
return max(dist) if max(dist) != float('inf') else -1
Floyd-Warshall
该算法通常用以解决所有节点对的最短路径,该算法基于这样一个定理:从节点 i 到节点 j,如果存在一条更短的路径话,那么一定是从另一个节点 k 中转而来,即有 d[i][j] = min(d[i][j],d[i][k]+d[k][j]),而d[i][k]和d[k][j]可以用一样的思想去构建,可以看出这是一个动态规划的思想。在构建i、j中,我们通过枚举所有的k值来进行操作。
class Solution:
def networkDelayTime(self, times, N, K):
graph = [[float('inf')] * N for _ in range(N)]
for i, j, v in times:
graph[i - 1][j - 1] = v
for i in range(N):
graph[i][i] = 0
for k in range(N):
for i in range(N):
for j in range(N):
graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j])
res = max(graph[K - 1])
return res if res != float('inf') else -1