Pattern 12 of 20
Advanced Graphs
Weighted shortest paths, minimum spanning trees, topological order and union-find.
When to use it
- Edges have different costs and you need the cheapest route
- Connecting every node at minimum total cost
- Tasks with prerequisites, or merging groups as edges arrive
The idea
Pick the algorithm that matches the constraint: Dijkstra for non-negative weights, Bellman-Ford when the number of edges in a path is limited, Kruskal or Prim for spanning trees, Kahn's algorithm for ordering.
Typical cost Typically O(E log V) time.
How it works
Once edges have different costs, BFS no longer gives shortest paths, because a path with more edges can be cheaper. Dijkstra's algorithm fixes this by always expanding the closest unfinished node, using a min-heap keyed by distance.
Dijkstra works because with non-negative weights, the closest node in the heap can never be improved later: any other route goes through a node that is already at least as far. That is also why negative edges break it, and why Bellman-Ford (relax every edge V - 1 times) exists.
Minimum spanning trees connect every node at the lowest total cost. Prim's grows one tree from a start node with a heap of crossing edges; Kruskal's sorts all edges and adds each one that joins two different components, using union-find.
Disguises include network delay times, cheapest flights within k stops (Bellman-Ford with a stop limit), paths that minimize the maximum effort (Dijkstra on a different cost function), connecting points at minimum cost, and reconstructing an itinerary that uses every ticket (an Eulerian path).
How to attack it
- 1Check edge weights: all equal means BFS, non-negative means Dijkstra, negative means Bellman-Ford.
- 2Define what 'distance' means; it may be a maximum along the path, not a sum.
- 3Store (distance, node) in the heap and skip stale entries when popped.
- 4For 'connect everything cheaply', use Kruskal or Prim instead of shortest paths.
- 5Add any extra constraint (stops, fuel) to the state, not to the graph.
Template
A skeleton to adapt, not a solution to paste. Write it from memory a few times.
import heapq
def dijkstra(n, edges, source):
graph = [[] for _ in range(n)]
for u, v, w in edges: # directed edge u -> v with weight w >= 0
graph[u].append((v, w))
dist = [float("inf")] * n
dist[source] = 0
heap = [(0, source)]
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]:
continue # stale entry: a shorter path was found
for v, w in graph[u]:
nd = d + w
if nd < dist[v]:
dist[v] = nd
heapq.heappush(heap, (nd, v))
return dist
def kruskal(n, edges):
parent = list(range(n))
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]] # path halving
x = parent[x]
return x
total = 0
for w, u, v in sorted(edges): # edges as (weight, u, v)
ru, rv = find(u), find(v)
if ru != rv:
parent[ru], total = rv, total + w
return totalVariants
- Dijkstra
- Single-source shortest paths with non-negative weights. Change the combine step to max or min for bottleneck paths.
- Bellman-Ford
- Relax all edges up to V - 1 times; handles negative weights, detects negative cycles, and limits path length to k edges naturally.
- 0-1 BFS
- When weights are only 0 or 1, a deque replaces the heap: push weight-0 edges to the front and weight-1 edges to the back.
- Minimum spanning tree
- Kruskal sorts edges and uses union-find; Prim grows from one node with a heap. Both give the same total.
- Eulerian path
- Use every edge exactly once with Hierholzer's algorithm: DFS, and append a node only after its edges are exhausted.
Pitfalls
- Running Dijkstra with negative edge weights.
- Not skipping stale heap entries, which repeats work and can blow up the running time.
- Marking a node final when it is pushed rather than when it is popped.
- Encoding a constraint like 'at most k stops' outside the state, so the algorithm discards valid paths.
- Confusing a minimum spanning tree with shortest paths from one source; they answer different questions.
What it costs
| Operation | Cost |
|---|---|
| Dijkstra with a binary heap | O((V + E) log V) |
| Bellman-Ford | O(V · E) |
| Kruskal | O(E log E) for the sort |
| Prim with a heap | O(E log V) |
| Floyd-Warshall (all pairs) | O(V³) time, O(V²) space |
30 Advanced Graphs problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Depot Delivery DistancesFreeEasy
- 2Cheapest Wiring PlanFreeEasy
- 3All-Pairs Travel TableFreeEasy
- 4One-Stroke DrawingEasy
- 5Net Cost With RefundsEasy
- 6Towns Within Fuel BudgetEasy
- 7Endless Discount LoopEasy
- 8Dense Power GridEasy
- 9Module Dependency QueriesEasy
- 10Gentlest Hiking RouteMedium
- 11Cheapest Route With LayoversMedium
- 12Link the OutpostsMedium
- 13Rebuild the Travel LogMedium
- 14Conveyor Grid FixesMedium
- 15Fragile Network LinksMedium
- 16Single Points of FailureMedium
- 17Quietest Hub TownMedium
- 18Most Reliable Relay PathMedium
- 19Count Fastest RoutesMedium
- 20Fewest Walls to BreakMedium
- 21Cheapest Water SupplyMedium
- 22Currency Loop ProfitMedium
- 23Timetable Earliest ArrivalMedium
- 24Robust Network ZonesMedium
- 25Toll Route With PassesHard
- 26Essential and Optional CablesHard
- 27Domino Chain OrderHard
- 28Cheapest Trip Under DeadlineHard
- 29Fortify the NetworkHard
- 30Fewest Turns MazeHard
Solve them in the browser, in Python or JavaScript.