Chapter 8: Advanced Graphs
Explore advanced graph algorithms including topological sort, shortest paths in weighted graphs, and minimum spanning trees. Master Kahn's algorithm, Dijkstra's algorithm, Kruskal's and Prim's algorithms, and solve classic problems like course scheduling and alien dictionary.
- Topics
- 5
- Demos
- 5
- Min read
- 21
Chapter Overview
Building on the graph fundamentals from the previous chapter, we now tackle algorithms that exploit special graph structures --- directed acyclic graphs (DAGs), weighted edges, and global optimization over all edges.
These algorithms power real-world systems: build systems use topological sort to determine compilation order, GPS navigation uses Dijkstra's algorithm to find the fastest route, and network design uses minimum spanning trees to minimize wiring costs.
This chapter covers five key areas:
- Topological Sort: Ordering vertices in a DAG so every edge points forward
- Task Scheduling: Detecting whether a set of tasks with prerequisites can be completed
- Alien Dictionary: Reconstructing an unknown ordering from sorted data
- Dijkstra's Algorithm: Finding shortest paths in graphs with non-negative edge weights
- Minimum Spanning Tree: Connecting all vertices with minimum total edge weight
Chapter Roadmap
Click any topic to jump in
Topological Sort
Linear ordering of a DAG so every edge points forward — via Kahn's BFS or reverse DFS postorder.
Task Scheduling
Cycle detection and Course Schedule — topological sort's most famous application.
Alien Dictionary
Extract character ordering from sorted words — build a DAG, then topo-sort it.
Dijkstra's Algorithm
Greedy shortest path in weighted graphs — priority queue + edge relaxation.
Minimum Spanning Tree
Connect all nodes at minimum cost — Kruskal with Union-Find or Prim with a heap.
Topics
5 topics in this chapter
A build system must compile a library before every program that imports it, a spreadsheet must recompute a cell before every formula that reads it, and a package manager must install dependencies before the packages that need them. Each problem is a set of tasks plus "must come before" rules, and the question is the same: in what order can the tasks run so that no task starts before its prerequisites?
The previous chapter, Graphs, ended with Word Ladder, a BFS over an undirected implicit graph. Ordering problems need directed edges: an edge means must come before . A topological sort lists the vertices so that every edge points forward. Such an order exists exactly when the graph has no directed cycle, a directed acyclic graph or DAG, because a cycle would require a task to come before itself.
We first build Kahn's algorithm, which repeatedly removes a vertex that no remaining edge points into, using in-degree counts and a queue. Then we derive the DFS method, which lists vertices in reverse order of when their searches finish, and compare what each approach reveals when the graph does contain a cycle.
Definition
A topological sort of a directed graph is a linear ordering of its vertices such that for every edge , comes before . It exists if and only if is acyclic, and is usually not unique. Kahn's algorithm and DFS finishing order each compute one in time.
In this topic
- 1Kahn's Algorithm (BFS-Based)
- 2DFS-Based Topological Sort
Kahn's Algorithm (BFS-Based)
Kahn's algorithm works by repeatedly removing vertices with in-degree 0 (no incoming edges). Start by computing the in-degree of every vertex. Add all vertices with in-degree 0 to a queue. Process each vertex from the queue: add it to the result, then decrement the in-degree of all its neighbors. If a neighbor's in-degree drops to 0, enqueue it.
If the result contains all vertices, the ordering is valid. If fewer than vertices are processed, the graph contains a cycle and no topological order exists.
Time: --- each vertex and edge processed once. Space: for the queue and in-degree array.
Kahn's algorithm produces a topological order by repeatedly removing vertices with in-degree 0. Start by computing in-degrees in , enqueue all zero-in-degree vertices, then repeatedly dequeue , append to result, and decrement in-degrees of 's successors; enqueue any that drop to 0. Total time is because each edge is processed once (decrement) and each vertex is enqueued/dequeued once. If the final result has fewer than vertices, the graph has a cycle — Kahn's naturally detects this.
Given a DAG with edges: 5->0, 5->2, 4->0, 4->1, 2->3, 3->1. Find a topological ordering using Kahn's algorithm.
DFS-Based Topological Sort
The DFS approach performs a depth-first traversal and records each vertex's finish time. When a DFS call finishes processing a vertex (all descendants explored), the vertex is pushed onto a stack (or prepended to a list). The final stack order, read top to bottom, is a valid topological sort.
The intuition is that a vertex finishes after all vertices it can reach have finished --- so it belongs earlier in the ordering. This method naturally handles disconnected DAGs by running DFS from each unvisited vertex.
Time: . Space: for the recursion stack and visited set.
DFS-based topo sort computes the postorder of a DFS traversal, then reverses it. The key insight: when DFS finishes processing a vertex (all descendants done), 's postorder timestamp is later than all its descendants'. Reversing puts before its descendants, which is exactly the topological requirement. Time is . To detect cycles, track vertices in three states: white (unvisited), gray (on stack), black (finished). Encountering a gray vertex means a back edge — a cycle.
Run DFS-based topological sort on the same graph: 5->0, 5->2, 4->0, 4->1, 2->3, 3->1. Start DFS from vertex 5, then 4.
A university publishes its course catalogue with prerequisites, and a student asks whether every course can be completed at all, and if so in what order and in how few semesters. One careless edit can make three courses require each other in a loop, and then nobody can take any of them.
The previous topic, Topological Sort, assumed the graph was a DAG and produced an order. Task scheduling drops that assumption: the first job is to decide whether an order exists, which means detecting a directed cycle. Cycle detection in a directed graph is subtler than in an undirected one. Reaching an already-visited vertex is not enough evidence, since two separate paths may simply meet at a shared prerequisite. What proves a cycle is reaching a vertex that is still on the current DFS path. Kahn's algorithm detects the same thing differently: if it stops before outputting every vertex, the vertices left over contain a cycle.
We first detect cycles with DFS colouring, white, gray, and black, and with Kahn's leftover count. Then we return a valid course order and use BFS levels to find the minimum number of semesters when courses run in parallel.
Definition
Task scheduling with prerequisites models tasks as vertices and each requirement "a before b" as a directed edge . All tasks can be completed if and only if the graph has no directed cycle; any topological order is then a valid schedule, and with unlimited parallelism the minimum number of rounds equals the number of vertices on the longest path.
In this topic
- 1Cycle Detection in Directed Graphs
- 2Course Schedule Ordering
Cycle Detection in Directed Graphs
A directed graph has a cycle if and only if a DFS encounters a back edge --- an edge from a vertex to one of its ancestors in the current DFS tree. We track three states for each vertex: unvisited, in the current recursion stack (active), and fully processed.
Alternatively, Kahn's algorithm detects cycles implicitly: if the topological sort result contains fewer than vertices, the remaining vertices form one or more cycles.
Key insight: Undirected cycle detection only needs a visited set, but directed cycle detection requires distinguishing between "currently being explored" and "fully finished" nodes.
A directed graph has a cycle iff DFS encounters a 'back edge' — an edge to an ancestor in the DFS tree (a gray vertex). Equivalently, Kahn's algorithm terminates with fewer than outputs. Both methods run in . The handshaking analog for DAGs: any DAG on vertices has at least one source (in-degree 0) and at least one sink (out-degree 0), because a cycle-free graph must have a vertex with no incoming edges — otherwise we could walk backward forever.
There are 4 courses (0-3) with prerequisites: [1,0], [2,1], [3,2], [1,3] (meaning to take course 1 you need course 0, etc.). Can all courses be finished?
Course Schedule Ordering
The extended version asks not just whether all courses can be completed, but to return a valid ordering. This is exactly the topological sort of the prerequisite graph. If no cycle exists, any topological ordering is a valid course schedule.
Common variations include:
- Course Schedule I: Return true/false (cycle detection only)
- Course Schedule II: Return a valid ordering or empty if impossible
- Minimum semesters: Find the minimum number of semesters to complete all courses (longest path in the DAG, equivalent to BFS level count)
The Course Schedule problem asks: given prerequisites as directed edges , produce a valid ordering or report impossibility. This is exactly topological sort. Using Kahn's: enqueue courses with no prerequisites, then remove them one at a time, decrementing downstream in-degrees. Output order is a valid schedule. Total time . If the output length , a prerequisite cycle exists and no valid schedule is possible.
4 courses with prerequisites: [1,0], [2,0], [3,1], [3,2]. Find a valid ordering and the minimum number of semesters (taking as many courses in parallel as possible).
Suppose you find a dictionary in an unknown alphabet. You cannot read the words, but you know they are sorted, so their order must encode the order of the letters. Recovering it is the Alien Dictionary problem, and the same reasoning infers an unfamiliar collation rule from a sorted file.
The previous topic, Task Scheduling, was handed its "must come before" edges. Here the edges are hidden and must be extracted first. Two adjacent words in sorted order agree up to some position; the first position where they differ says that the letter in the earlier word precedes the letter in the later word. Nothing after that position tells us anything, and one pair yields at most one edge. Once every adjacent pair has been compared, the letters and their precedence edges form a directed graph, and a topological sort of it is a valid alphabet. A cycle, or a word appearing after its own prefix, means the input was not sorted consistently.
We first extract ordering constraints from adjacent word pairs, including the invalid-prefix case. Then we build the character graph and sort it with Kahn's algorithm, making sure letters without any constraints still appear.
Definition
Alien Dictionary gives words sorted in an unknown alphabet and asks for a letter order consistent with them. Each adjacent pair contributes at most one edge, from the earlier word's letter to the later word's letter at their first differing position. A topological sort of these edges, over all letters present, is an answer; a cycle or a longer word before its own prefix makes the input invalid.
In this topic
- 1Extracting Ordering from Sorted Words
- 2Building and Sorting the Character DAG
Extracting Ordering from Sorted Words
Given a sorted list of alien words, compare each consecutive pair character by character. The first differing position reveals an ordering: the character from the earlier word comes before the character from the later word. Characters at matching positions provide no ordering information.
Edge case: If word A is a prefix of word B and A comes first, no new edge is extracted (this is consistent). But if B is a prefix of A and B comes first, the input is invalid --- a shorter word cannot come after its prefix in any valid ordering.
Time: total where is the sum of all character lengths, since each character is compared at most once.
Given words in alien dictionary order, adjacent pairs reveal one ordering constraint: the first position where they differ. If and , then in the alien order. This gives at most edges from words. Edge case: if is a prefix of , no constraint is extracted; but if is a proper prefix of (e.g., abc before ab), the input is invalid since lexicographic order would require the shorter one first.
Given sorted alien words: ["wrt", "wrf", "er", "ett", "rftt"]. Extract ordering edges.
Building and Sorting the Character DAG
After extracting all ordering edges, build a directed graph where vertices are characters and edges represent "comes before" relationships. Run topological sort to produce the alien alphabet.
Important details:
- All unique characters must appear in the result, even if they have no ordering edges.
- If the topological sort detects a cycle, the input is inconsistent and no valid ordering exists.
- Multiple valid orderings may exist (the problem typically asks for any one valid ordering).
- Characters that appear in the words but have no ordering constraints can be placed anywhere in the result.
Build a directed graph with all unique characters as vertices and extracted constraints as edges, then topologically sort. With at most 26 characters and edges, and , so Kahn's algorithm runs in . If the result is not a valid topological order (cycle detected), the alien dictionary is inconsistent. Multiple valid orderings can exist — Kahn's arbitrarily picks one based on queue processing order, which may depend on initial vertex insertion order.
From edges t->f, w->e, r->t, e->r, find the alien alphabet order.
Related Problems on PixelBank
A navigation app should not return the route with the fewest road segments; it should return the fastest one, where a two-minute side street and a twenty-minute highway stretch count very differently. Network routers face the same problem when links have different latencies.
The previous topic, Alien Dictionary, finished the ordering problems, where edges carried no cost. In the previous chapter, BFS found shortest paths because every edge cost the same, so fewer edges meant a shorter path. Once edges carry weights, that argument fails: a path of three cheap edges can beat one expensive edge. Dijkstra's algorithm restores the idea of settling vertices in order of distance. It keeps a tentative distance for every vertex, repeatedly settles the unsettled vertex with the smallest tentative distance, and relaxes its outgoing edges, lowering a neighbour's distance whenever a path through the settled vertex is shorter. A min-heap makes finding the next vertex fast, for total time. The guarantee needs non-negative weights.
We first trace relaxation with a priority queue, including stale heap entries. Then we rebuild the actual route from predecessor pointers and see why a single negative edge breaks the algorithm.
Definition
Dijkstra's algorithm computes shortest-path distances from a source to every vertex of a graph with non-negative edge weights. It repeatedly removes the unsettled vertex with the smallest tentative distance from a min-heap, fixes that distance, and relaxes each edge of weight by setting dist[v] to dist[u] + w when that is smaller. With a binary heap it runs in time.
In this topic
- 1Relaxation and the Priority Queue
- 2Path Reconstruction
Relaxation and the Priority Queue
Relaxation is the core operation: for an edge with weight , if , then update . This means we found a shorter path to through .
A min-heap (priority queue) always gives us the unprocessed vertex closest to the source. Once a vertex is extracted from the heap, its distance is final --- no future relaxation can improve it (this is guaranteed by non-negative weights).
Time: with a binary heap. Space: for distances plus for the graph.
Dijkstra's algorithm maintains , an upper bound on the shortest-path distance from source to , initially and elsewhere. It repeatedly extracts the vertex with minimum tentative distance and 'relaxes' each outgoing edge: if , update and push onto the priority queue. Using a binary heap, time is . The invariant: when is extracted, is finalized — the greedy choice works because edge weights are non-negative.
Graph with edges: A->B (weight 4), A->C (weight 1), C->B (weight 2), B->D (weight 1), C->D (weight 5). Find shortest paths from A.
Path Reconstruction
Finding shortest distances is useful, but often we need the actual path. During relaxation, whenever we update , we also record --- the vertex that led to this improvement. After the algorithm finishes, we reconstruct the path by following the prev pointers backward from the destination to the source.
Why Dijkstra fails with negative weights: The algorithm's correctness relies on the invariant that once a vertex is extracted from the heap, its distance is final. Negative edges can violate this --- a vertex processed later might offer a cheaper path through a negative edge to an already-finalized vertex.
Like BFS, Dijkstra tracks predecessors: when relaxing successfully, set . To reconstruct the shortest path to target , walk backward and reverse. This adds space for the predecessor map. For multiple shortest paths (ties), prev stores only one, giving any single optimal path. Enumerating all shortest paths requires storing all predecessors per vertex — exponential in the worst case.
Using the previous graph, reconstruct the shortest path from A to D.
Related Problems on PixelBank
A utility company must connect a set of towns with power lines. Each possible line between two towns has a known cost, and every town must be connected, directly or indirectly, at the lowest total cost. Laying cable across a campus poses the same question.
The previous topic, Dijkstra's Algorithm, minimised the distance from one source to each vertex. Here no vertex is special, and the quantity to minimise is the total weight of the chosen edges. Any cheapest connecting set contains no cycle, since removing one edge of a cycle keeps everything connected and saves its cost, so the answer is a tree with exactly edges: a minimum spanning tree. Two greedy algorithms find it, both justified by the cut property: for any split of the vertices into two groups, the cheapest edge crossing the split belongs to some minimum spanning tree.
We first build Kruskal's algorithm, which scans edges from cheapest to most expensive and uses Union-Find to skip any edge that would close a cycle. Then we build Prim's algorithm, which grows a single tree outward with a min-heap, much like Dijkstra, and compare when each is the better choice.
Definition
A minimum spanning tree of a connected, undirected, weighted graph is a subset of edges that connects every vertex without forming a cycle and has the smallest possible total weight. Kruskal's algorithm finds one in time with sorting and Union-Find; Prim's algorithm finds one in time with a binary heap.
In this topic
- 1Kruskal's Algorithm with Union-Find
- 2Prim's Algorithm
Kruskal's Algorithm with Union-Find
Kruskal's algorithm sorts all edges by weight and processes them in ascending order. For each edge, if it connects two vertices that are not already connected (in different components), add it to the MST. Otherwise, skip it (it would create a cycle).
The Union-Find (Disjoint Set Union) data structure efficiently tracks connected components. It supports two operations:
- Find(x): Return the root/representative of x's component (with path compression)
- Union(x, y): Merge the components containing x and y (with rank-based merging)
Time: dominated by sorting. Union-Find operations are nearly amortized.
Kruskal's builds an MST by considering edges in non-decreasing weight order and adding each edge that does not form a cycle. Union-Find (disjoint-set union) with path compression and union-by-rank supports find and union in near-constant amortized time — formally where is the inverse Ackermann function, at most 4 for any realistic input. Total time is dominated by the sort. Correctness follows from the cut property: the minimum-weight edge crossing any cut is in some MST.
Graph with edges: A-B(4), A-C(2), B-C(1), B-D(5), C-D(8), C-E(10), D-E(2). Find the MST using Kruskal's.
Prim's Algorithm
Prim's algorithm grows the MST from a single starting vertex. Maintain a set of vertices already in the MST. At each step, add the cheapest edge that connects a vertex in the MST to a vertex outside it. Repeat until all vertices are included.
Using a min-heap, Prim's runs in --- the same complexity as Dijkstra. In fact, Prim's is structurally similar to Dijkstra, but instead of tracking shortest distance from source, it tracks the minimum edge weight to connect to the growing tree.
Prim's vs Kruskal's: Prim's is better for dense graphs (adjacency matrix, without heap). Kruskal's is better for sparse graphs and when edges are already sorted.
Prim's grows the MST from a single vertex by repeatedly adding the minimum-weight edge that connects the current tree to a new vertex. With a binary heap, time is ; with a Fibonacci heap, . Each vertex enters the heap once, and each edge may trigger a decrease-key. Correctness also follows from the cut property. Prim's is preferred on dense graphs () where its work scales with ; Kruskal's is preferred on sparse graphs where sorting dominates.
Same graph: A-B(4), A-C(2), B-C(1), B-D(5), C-D(8), C-E(10), D-E(2). Find MST using Prim's starting from A.