<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Graphs
All lessons in this course

Lesson 34 of 50 14 minAdvanced

Topological sort

Order the vertices of a directed acyclic graph so every edge points forward: Kahn’s algorithm with in-degrees, the DFS method, cycle detection, and critical-path scheduling.

You cannot place beams before the columns are up, and you cannot fabricate steel before it is ordered. Given a list of “X before Y” rules, a topological order is a sequence of all tasks where every task comes after everything it depends on. The same problem appears in build systems (which project to compile first), package managers, course prerequisites and spreadsheet recalculation.

A topological order exists only if there is no cycle. The graph must be a DAG (directed acyclic graph). There are usually several valid orders.

Kahn’s algorithm

  1. Count each vertex’s in-degree: how many prerequisites it has.
  2. Put every vertex with in-degree 0 in a queue: they can start now.
  3. Take one from the queue and add it to the order. For each vertex that depended on it, subtract 1 from its in-degree; if that reaches 0, put it in the queue.
  4. When the queue is empty: if every vertex is in the order, done. Otherwise the leftovers are stuck in a cycle.
Interactive

Scheduling tasks with a topological sort

An arrow X → Y means “X must be finished before Y can start”. The small number is how many prerequisites are still unfinished.

Survey0Excavate1Footings1Order steel0Fabricate1Columns2Beams2Deck1Roof1
Queue (ready to start)
SurveyOrder steel
Order
none yet
Count each task’s prerequisites (its in-degree, the small number). Tasks with 0 can start now: Survey, Order steel. Put them in a queue.
Kahn.cs
string[] tasks = { "Survey", "Excavate", "Footings", "Order steel", "Fabricate", "Columns", "Beams", "Deck", "Roof" };(string Before, string After)[] deps ={    ("Survey", "Excavate"), ("Excavate", "Footings"), ("Footings", "Columns"),    ("Order steel", "Fabricate"), ("Fabricate", "Columns"), ("Fabricate", "Beams"),    ("Columns", "Beams"), ("Beams", "Deck"), ("Deck", "Roof")}; var plan = TopoSort(tasks, deps);Console.WriteLine(plan is null ? "Cycle: no valid order" : string.Join(" → ", plan)); static List<string>? TopoSort(string[] nodes, (string Before, string After)[] edges){    var next = nodes.ToDictionary(n => n, _ => new List<string>());    var inDegree = nodes.ToDictionary(n => n, _ => 0);    foreach (var (a, b) in edges) { next[a].Add(b); inDegree[b]++; }     var ready = new Queue<string>(nodes.Where(n => inDegree[n] == 0));    var order = new List<string>();    while (ready.Count > 0)    {        var u = ready.Dequeue();        order.Add(u);        foreach (var v in next[u])            if (--inDegree[v] == 0) ready.Enqueue(v);   // its last prerequisite is done    }    return order.Count == nodes.Length ? order : null;  // leftovers mean a cycle}
Output
Survey → Order steel → Excavate → Fabricate → Footings → Columns → Beams → Deck → Roof

Every vertex is queued once and every edge decreases one counter once: O(V + E). Using a PriorityQueue instead of a Queue gives a specific order among tasks that are ready at the same time (for example alphabetical, or by priority), at O(log V) extra per step.

The DFS method

Run DFS; when a vertex finishes (all its dependants explored), add it to a list. Every task finishes after all tasks that depend on it, so the reversed finish order is a topological order. Combined with the “on the current path” check from the last lesson, it reports cycles too.

TopoDfs.cs
var next = new Dictionary<string, string[]>{    ["Underwear"] = new[] { "Trousers" },    ["Socks"] = new[] { "Boots" },    ["Trousers"] = new[] { "Boots", "Belt" },    ["Shirt"] = new[] { "Belt" },    ["Belt"] = Array.Empty<string>(),    ["Boots"] = Array.Empty<string>()}; var done = new HashSet<string>();var finished = new List<string>();foreach (var task in next.Keys) Visit(task);finished.Reverse();Console.WriteLine(string.Join(", ", finished)); void Visit(string u){    if (!done.Add(u)) return;    foreach (var v in next[u]) Visit(v);    finished.Add(u);                       // u finishes after everything that needs it}
Output
Shirt, Socks, Underwear, Trousers, Belt, Boots

Scheduling: the critical path

With durations, a topological order also tells you how long the whole project takes. Process tasks in order; each task can start when its latest prerequisite finishes. The longest chain of dependent tasks is the critical path: any delay on it delays the whole project. This is the CPM method used in construction planning, and it is just “longest path in a DAG”, which is easy once the vertices are in topological order (on a general graph, longest path is a very hard problem).

Quick check

Kahn’s algorithm ends with 9 of 12 tasks in the order. What does that mean?

Quick check

In the DFS method, why is the finish order reversed?

Practice

Compute the project duration (the critical path length) for the task network in Kahn.cs, with durations in days: Survey 2, Excavate 5, Footings 4, Order steel 10, Fabricate 6, Columns 3, Beams 4, Deck 3, Roof 2. A task starts when all its prerequisites have finished. Expected: 28 days.

Show a hint

Get a topological order first. Then, in that order, finish[task] = duration[task] + the largest finish of its prerequisites (0 if it has none). The answer is the largest finish time.

Show the solution
Solution.cs
string[] tasks = { "Survey", "Excavate", "Footings", "Order steel", "Fabricate", "Columns", "Beams", "Deck", "Roof" };int[] days = { 2, 5, 4, 10, 6, 3, 4, 3, 2 };(string Before, string After)[] deps ={    ("Survey", "Excavate"), ("Excavate", "Footings"), ("Footings", "Columns"),    ("Order steel", "Fabricate"), ("Fabricate", "Columns"), ("Fabricate", "Beams"),    ("Columns", "Beams"), ("Beams", "Deck"), ("Deck", "Roof")}; var duration = tasks.Zip(days).ToDictionary(p => p.First, p => p.Second);var inDegree = tasks.ToDictionary(t => t, _ => 0);var next = tasks.ToDictionary(t => t, _ => new List<string>());foreach (var (a, b) in deps) { next[a].Add(b); inDegree[b]++; } var finish = tasks.ToDictionary(t => t, _ => 0);var start = tasks.ToDictionary(t => t, _ => 0);        // earliest startvar ready = new Queue<string>(tasks.Where(t => inDegree[t] == 0));while (ready.Count > 0){    var u = ready.Dequeue();    finish[u] = start[u] + duration[u];    foreach (var v in next[u])    {        start[v] = Math.Max(start[v], finish[u]);      // wait for the latest prerequisite        if (--inDegree[v] == 0) ready.Enqueue(v);    }} Console.WriteLine($"Project: {finish.Values.Max()} days");Console.WriteLine($"Columns can start on day {start["Columns"]}");
Output
Project: 28 days
Columns can start on day 16

Remember

  • A topological order puts every task after its prerequisites; it exists only for DAGs.
  • Kahn’s algorithm repeatedly takes tasks with in-degree 0: O(V + E). Leftovers mean a cycle.
  • Reversed DFS finish order is also a topological order.
  • With durations, processing in topological order gives earliest starts and the critical path.
0 of 50 lessons completed