Skip to content

Topological order

In a graph of tasks with no cycle, every task can be listed after the tasks it depends on, by Kahn’s method or by reversing a depth-first search’s finishing order.

Helps to know firstQueues, Depth-first search

Helps to know first
This topic

First, the picture

Eight tasks for a cup of tea, and the arrows between them. Watch the tasks with nothing left before them leave, one at a time, in a working order.

Putting tasks in a working order

Eight tasks for a cup of tea, ordered so each comes after what it needs

int kahn(int *order){    int indeg[V] = { 0 };    for (int u = 0; u < V; u++)        for (const struct enode *e = out[u]; e != NULL; e = e->next)            indeg[e->to]++;    int queue[V];    int head = 0, tail = 0;    for (int u = 0; u < V; u++)        if (indeg[u] == 0)            queue[tail++] = u;    int n = 0;    while (head < tail) {        int u = queue[head++];        order[n++] = u;        for (const struct enode *e = out[u]; e != NULL; e = e->next) {            indeg[e->to]--;            if (indeg[e->to] == 0)                queue[tail++] = e->to;        }    }    return n;}    int order[V];    int n = kahn(order);    printf("Kahn:");    for (int i = 0; i < n; i++)        printf(" %s", name[order[i]]);    if (n < V)        printf(" (stopped after %d of %d: a cycle)", n, V);    printf("\n");    for (int u = 0; u < V; u++)

Seven arcs: each task points to a task that must wait for it.

free now
–
placed
0 of 8

Output

0.00 / 15.85 s
Describe this picture

The picture has two parts, one above the other. On top are eight numbered circles in three columns, each with its task’s name on a small plate above it: on the left, top to bottom, 0 fill-kettle, 2 get-cup and 6 get-milk; in the middle, 1 boil and 3 add-bag; on the right, 4 pour, 5 steep and 7 add-milk. Seven arrows join them, each from a task to a task that must wait for it: fill-kettle to boil, boil to pour, get-cup to add-bag, add-bag to pour, pour to steep, steep to add-milk, and get-milk to add-milk. Under them is a strip of eight boxes, order, with a marker “n” above the next box to fill. Under the picture are the lines of tea.c the clip runs, with the line about to run marked, one sentence on the current step, two readouts (the tasks free now, and how many are placed), and the output. The clip plays once, about 16 seconds, and has no control. First each plate gains a count, the number of arrows pointing into its task: 0 on fill-kettle, get-cup and get-milk, 1 on boil, add-bag and steep, 2 on pour and add-milk. The three tasks with 0 turn to the frontier tone one by one, as they join the queue: they are free. Then, again and again, the first free task is ringed and its number goes into the next box of the strip; it turns to the settled tone, and each arrow leaving it lights, lowers the count at its far end by one, and fades. A task whose count reaches 0 turns to the frontier tone. The tasks leave in the order fill-kettle, get-cup, get-milk, boil, add-bag, pour, steep, add-milk, and the strip ends as 0 2 6 1 3 4 5 7. The clip rests on the output, Kahn: fill-kettle get-cup get-milk boil add-bag pour steep add-milk. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as topo.step.

Tasks that wait on other tasks

Some jobs must be done in a certain order: the kettle is filled before it boils, and the tea is poured before it steeps. Draw each task as a vertex and each “must come before” as an arrow, an arc, from the earlier task to the later one. The graph is now directed: an arc goes one way only.

A topological order lists every task after all the tasks that point to it. For the tea, one such order is the one in the picture. It exists only when the arcs never lead in a circle back to where they started: the graph must be a directed acyclic graph, a DAG.

The code and its cost

The graph

tea.c stores the arcs as adjacency lists, as in storing a graph, but each arc only once, in the list of the task it leaves. out[u] is the list of tasks that wait on u.

const int arcs[7][2] = { { 0, 1 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 7 }, { 6, 7 } };

Kahn’s method

A task can go next as soon as nothing still points into it. kahn counts, for every task, the arcs pointing into it, its in-degree:

int indeg[V] = { 0 };
for (int u = 0; u < V; u++)
    for (const struct enode *e = out[u]; e != NULL; e = e->next)
        indeg[e->to]++;

The counts are 0 1 0 1 2 1 0 2, for tasks 0 to 7. The tasks with no arc into them, fill-kettle, get-cup and get-milk, go into a queue, as in queues. Then the loop takes the task at the head, writes it into the order, and removes its arcs by lowering the count at the far end of each; a task whose count reaches 0 joins the queue at the tail:

while (head < tail) {
    int u = queue[head++];
    order[n++] = u;
    for (const struct enode *e = out[u]; e != NULL; e = e->next) {
        indeg[e->to]--;
        if (indeg[e->to] == 0)
            queue[tail++] = e->to;
    }
}
return n;

Taking fill-kettle frees boil. Taking get-cup frees add-bag. Taking get-milk lowers add-milk only to 1, because steep still points into it. The order comes out as fill-kettle, get-cup, get-milk, boil, add-bag, pour, steep, add-milk.

The cost

Counting walks every list once. Each task enters the queue once, when its count reaches 0, and each arc is removed once, when its task leaves. With V tasks and E arcs that is O(V + E).

Many right answers

At most moments in the clip, more than one task is free: at the start, all of fill-kettle, get-cup and get-milk. The queue takes them first come, first served, but any free task would do. A different choice gives a different order, and every one of them is right. There is no single correct topological order unless the arcs force a single chain.

The DFS method

Depth-first search gives an order too. Run it on the arcs, from every task not yet started, and list the tasks in reverse order of finishing. dfs keeps three states per task, 0 not started, 1 in progress and 2 done:

static void dfs(int u)
{
    state[u] = 1;
    for (const struct enode *e = out[u]; e != NULL; e = e->next) {
        if (state[e->to] == 0)
            dfs(e->to);
        else if (state[e->to] == 1)
            cyclic = true;
    }
    state[u] = 2;
    fin_order[nfin++] = u;
}

From fill-kettle it goes to boil, pour, steep and add-milk, which finish in the order add-milk, steep, pour, boil, fill-kettle. From get-cup it goes to add-bag, whose arc reaches pour, already done; add-bag and get-cup finish. Last, get-milk finishes alone, since add-milk is done. Reversed:

Kahn: fill-kettle get-cup get-milk boil add-bag pour steep add-milk
DFS: get-milk get-cup add-bag fill-kettle boil pour steep add-milk

It works because a task finishes only after every task it points to has finished: each is either searched from it, or done already. So in reverse order of finishing, every task comes before everything that waits on it. Both lines are topological orders, and they differ.

When there is a cycle

Compiled with -DCYCLE=1, main adds one more arc, add-milk to fill-kettle: the milk must now go in before the kettle is filled, which no order can satisfy.

Kahn: get-cup get-milk add-bag (stopped after 3 of 8: a cycle)
DFS: a cycle

Neither method loops for ever. Kahn’s method stops short: fill-kettle now waits on add-milk, so it is never free, and nothing after it is freed either. The queue empties after three tasks, and kahn returns 3, fewer than 8. The DFS method meets a task still in progress: from fill-kettle it goes five calls deep, to boil, pour, steep and add-milk, and the arc from add-milk leads back to fill-kettle, whose call has not returned. An arc to a task in progress goes back along the current path, so it closes a cycle. An arc to a task that is done, such as add-bag’s arc to pour, does not.

Running it yourself

gcc -std=c11 -Wall -Wextra -pedantic tea.c -o tea
./tea
gcc -std=c11 -Wall -Wextra -pedantic -DCYCLE=1 tea.c -o tea-cycle
./tea-cycle

Worked example

Check the DFS order arc by arc. In get-milk get-cup add-bag fill-kettle boil pour steep add-milk the tasks stand at places 1 to 8. Each arc must go from an earlier place to a later one: fill-kettle (4) to boil (5), boil (5) to pour (6), get-cup (2) to add-bag (3), add-bag (3) to pour (6), pour (6) to steep (7), steep (7) to add-milk (8), get-milk (1) to add-milk (8). All seven do.

Why the cycle variant stops after three. Once get-cup, get-milk and add-bag have left, every remaining task still waits on another: fill-kettle on add-milk, boil on fill-kettle, pour on boil, steep on pour and add-milk on steep. Those five arcs form the cycle fill-kettle, boil, pour, steep, add-milk and back to fill-kettle.

No arcs at all. Five tasks with no arcs between them can go in any order: 5! = 5 · 4 · 3 · 2 · 1 = 120 topological orders.

Two checks. Which tasks are free at the start? (fill-kettle, get-cup and get-milk: nothing points into them.) Must pour come after get-milk? (No: no chain of arcs leads from get-milk to pour. It comes earlier in both printed orders by choice, not by rule.)

Where it’s used

Build tools. A program’s files are compiled after the files they depend on, and a dependency that loops back on itself is reported.

Spreadsheets. When a cell changes, the cells that refer to it are recalculated after it, in an order that respects every reference; a formula that refers back to itself is a circular reference.

Package installers. A package is installed after the packages it needs.

Prerequisites. Planning a course of study in which some subjects must come before others.

Reference card

Code or ideaMeaning
an arc u → vu must come before v
a DAGa directed graph with no cycle
topological orderevery arc goes from earlier to later
indeg[v]how many arcs still point into v
queue every count of 0the tasks free now
order[n++] = u; then lower its arcsKahn’s method, O(V + E)
fewer than V placeda cycle
reverse finishing orderthe DFS method
an arc to a task in progressa cycle
several free at onceseveral right orders

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

Keep reading with a free account.

Graphs is a members’ section. An account costs nothing.

By creating an account you accept the terms of use.

Sign in

Already have an account? Use the way you signed up.

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look