DSA Network Delay Time
Network Delay Time asks how long a signal takes to reach every node in a network. The answer is the largest shortest distance from the start node.
The problem
Network Delay Time asks how long a signal takes to reach every node in a network. The network is a set of directed links. A directed link can be crossed in one direction only, and each link carries a travel time.
Every node receives the signal at its shortest travel time from the start node. The answer is the largest of those arrival times, because the signal has arrived everywhere only when the slowest node receives it. A node that can never be reached makes the answer minus one.
The cases used in this article
cases = [
([(2, 1, 1), (2, 3, 1), (3, 4, 1)], 4, 2, 2),
([(1, 2, 3), (2, 3, 4)], 4, 1, -1),
([(1, 2, 1), (1, 3, 4), (2, 3, 1), (3, 4, 2)], 4, 1, 4),
([], 1, 1, 0),
]
for links, n, start, expected in cases:
print("nodes", n, "start", start, "links", links, "-> delay:", expected)Read the third row slowly. Node 3 is reached in time 2 through node 2, not in time 4 by the direct link. Node 4 then arrives at time 4, and that slowest arrival is the answer.
Intuition: the route with the fewest links is not the fastest route
The first idea counts links. A route with fewer links feels faster, the way fewer stops feels faster on a trip. Travel times break that feeling, because one slow link can cost more than two fast ones.
The two routes to node 3 below come from the third case. The direct route crosses one link and takes time 4. The route through node 2 crosses two links and takes time 2.
Two routes to node 3, priced
direct = 4
via_two = 1 + 1
print("direct route: 1 link, time", direct)
print("via node 2: 2 links, time", via_two)
print("faster route uses more links:", via_two < direct)Link count is the wrong measure. The only measure is summed travel time along the route. Every method below therefore works with distances, never with hop counts.
Brute force: relax every link, round after round
Relaxing a link means testing whether travelling that link improves the arrival time of its far node. The brute force relaxes every link in every round. It runs one round per node, which is enough rounds for the longest useful route.
No link is ever relaxed in a clever order. The counter records every relaxation test. The method is slow for the same reason it is trustworthy, since no route can hide from a full set of rounds.
Full relaxation rounds, with counted tests
def delay_brute(links, n, start):
dist = [None] * (n + 1)
dist[start] = 0
checks = 0
for _ in range(n - 1):
for src, dst, w in links:
checks += 1
if dist[src] is not None:
cand = dist[src] + w
if dist[dst] is None or cand < dist[dst]:
dist[dst] = cand
arrivals = dist[1:]
if any(v is None for v in arrivals):
return -1, arrivals, checks
return max(arrivals), arrivals, checks
links = [(1, 2, 1), (1, 3, 4), (2, 3, 1), (3, 4, 2)]
answer, arrivals, checks = delay_brute(links, 4, 1)
print("arrivals:", arrivals, " delay:", answer, " relaxation tests:", checks)
answer, arrivals, checks = delay_brute([(2, 1, 1), (2, 3, 1), (3, 4, 1)], 4, 2)
print("arrivals:", arrivals, " delay:", answer, " relaxation tests:", checks)Four nodes and four links cost twelve tests, which is nodes minus one times links. Most of those tests recheck links whose far node stopped improving rounds earlier. The optimal method exists to skip exactly that rechecking.
The optimal solution: settle nodes in arrival order
The distance table holds the earliest known arrival for each node, and a heap that always settles the unsettled node with the smallest arrival next turns those arrivals final one by one. That one sentence is the whole method.
Settling a node makes its arrival final, because no shorter route to it can appear later. Each link out of a settled node is relaxed once. When the heap empties, any node still holding no arrival is unreachable.
Dijkstra on every case
import heapq
def delay_time(links, n, start):
graph = {node: [] for node in range(1, n + 1)}
for src, dst, w in links:
graph[src].append((dst, w))
dist = {node: None for node in graph}
dist[start] = 0
heap = [(0, start)]
while heap:
d, node = heapq.heappop(heap)
if d > dist[node]:
continue
for nxt, w in graph[node]:
cand = d + w
if dist[nxt] is None or cand < dist[nxt]:
dist[nxt] = cand
heapq.heappush(heap, (cand, nxt))
if any(v is None for v in dist.values()):
return -1, dist
return max(dist.values()), dist
cases = [
([(2, 1, 1), (2, 3, 1), (3, 4, 1)], 4, 2, 2),
([(1, 2, 3), (2, 3, 4)], 4, 1, -1),
([(1, 2, 1), (1, 3, 4), (2, 3, 1), (3, 4, 2)], 4, 1, 4),
([], 1, 1, 0),
]
for links, n, start, expected in cases:
answer, dist = delay_time(links, n, start)
print("start", start, "-> delay:", answer, "expected:", expected, "match:", answer == expected)All four cases pass. The second case returns minus one because node 4 holds no arrival when the heap empties. That single missing value decides the whole answer, exactly as the problem states.
Walkthrough: settling the third network node by node
The walk runs the third case from node 1. The state is the arrival table after each node settles. Arrivals are printed in node order, with None for a node not yet reached.
The arrival table after each settled node
import heapq
links = [(1, 2, 1), (1, 3, 4), (2, 3, 1), (3, 4, 2)]
graph = {node: [] for node in range(1, 5)}
for src, dst, w in links:
graph[src].append((dst, w))
dist = {node: None for node in graph}
dist[1] = 0
heap = [(0, 1)]
while heap:
d, node = heapq.heappop(heap)
if d > dist[node]:
continue
for nxt, w in graph[node]:
cand = d + w
if dist[nxt] is None or cand < dist[nxt]:
dist[nxt] = cand
heapq.heappush(heap, (cand, nxt))
print("settled node", node, "at", d, "- arrivals:", [dist[k] for k in sorted(dist)])
print("delay:", max(dist.values()))Node 3 improves from 4 to 2 when node 2 settles. That improvement is the whole lesson of the intuition section, happening inside the table. Node 4 settles last at time 4, and the answer is read off as the largest arrival.
Complexity
The brute force runs n minus 1 rounds over every link, so its time is O(n times E) for E links. The heap method relaxes each link once and pays a heap cost per relaxation, so its time is O(E log n).
The brute force stores one distance per node, so its space is O(n). The heap method stores one distance per node plus a heap that can hold up to one entry per link relaxation, so its space is O(n + E) in the worst case.
Counting the work instead of timing it
for n in [4, 8, 16]:
links = n - 1
print("nodes", n, "- brute relaxation tests:", (n - 1) * links, " heap relaxations:", links)The counts use a chain network, where each node links only to the next. Even there, 16 nodes cost the brute force 225 tests against 15 relaxations. Denser networks widen the gap, because the brute rounds multiply every added link.
Edge cases and pitfalls
Direction is the first pitfall. A link from node 2 to node 1 carries no signal from node 1 to node 2. Reading a directed link backwards invents routes the network does not have.
The second pitfall is the final step. The answer is the largest arrival, not the sum of arrivals and not the arrival of the highest-numbered node. The rows below pin both facts down.
Edge cases, run
import heapq
def delay_time(links, n, start):
graph = {node: [] for node in range(1, n + 1)}
for src, dst, w in links:
graph[src].append((dst, w))
dist = {node: None for node in graph}
dist[start] = 0
heap = [(0, start)]
while heap:
d, node = heapq.heappop(heap)
if d > dist[node]:
continue
for nxt, w in graph[node]:
cand = d + w
if dist[nxt] is None or cand < dist[nxt]:
dist[nxt] = cand
heapq.heappush(heap, (cand, nxt))
if any(v is None for v in dist.values()):
return -1, dist
return max(dist.values()), dist
answer, dist = delay_time([], 1, 1)
print("single node:", answer)
answer, dist = delay_time([(1, 2, 3), (2, 3, 4)], 4, 1)
print("one node unreached:", answer, " arrivals:", [dist[k] for k in sorted(dist)])
answer, dist = delay_time([(2, 1, 1)], 2, 1)
print("link points the other way:", answer)
answer, dist = delay_time([(1, 2, 5), (1, 3, 2)], 3, 1)
print("arrivals:", [dist[k] for k in sorted(dist)], " delay is the largest:", answer)The last row sums to 7 and ends at node 3 with arrival 2. Neither number is the answer. The delay is 5, the moment the slower of the two nodes receives the signal.
Exercise
A network's arrival times are 1, 4 and 6, with every node reached. What is the delay time?