Topologische Sortierung
Zuletzt aktualisiert
Eine topologische Sortierung eines gerichteten azyklischen Graphen (DAG) ist eine lineare Anordnung seiner Knoten, sodass für jede Kante u → v der Knoten u vor v steht. Sie beantwortet Fragen wie „In welcher Reihenfolge kann ich diese Aufgaben ausführen, sodass jede Voraussetzung zuerst abgeschlossen ist?“ Drücke oben auf Wiedergabe, um zu sehen, wie der Kahn-Algorithmus die Knoten in einer gültigen Reihenfolge herauslöst.
Der Kahn-Algorithmus nimmt wiederholt einen Knoten ohne verbleibende eingehende Kanten (Eingangsgrad 0), hängt ihn an die Reihenfolge an und entfernt seine ausgehenden Kanten - was neue Knoten mit Eingangsgrad 0 freisetzen kann. Er funktioniert nur auf einem DAG: Existiert ein Zyklus, erreichen manche Knoten nie den Eingangsgrad 0 und es gibt keine gültige Reihenfolge. Er läuft in O(V + E) Zeit.
Zeit- und Speicherkomplexität
| Maß | Komplexität | Anmerkungen |
|---|---|---|
| Zeit | O(V + E) | Jeder Knoten einmal ausgegeben, jede Kante einmal entfernt |
| Speicher | O(V) | Eingangsgrad-Zähler + Bereit-Menge + Reihenfolge |
| Erfordert | Einen DAG | Zyklen haben keine topologische Sortierung |
| Ergebnis | Nicht eindeutig | Es können viele gültige Reihenfolgen existieren |
Schritt für Schritt (Kahn-Algorithmus)
| Schritt | Was passiert |
|---|---|
| 1 | Berechne den Eingangsgrad (Anzahl eingehender Kanten) jedes Knotens. |
| 2 | Sammle alle Knoten mit Eingangsgrad 0 in einer Bereit-Menge. |
| 3 | Nimm einen bereiten Knoten und hänge ihn an die Ausgabereihenfolge an. |
| 4 | Verringere den Eingangsgrad jedes seiner Nachfolger. |
| 5 | Jeder Nachfolger, der den Eingangsgrad 0 erreicht, tritt der Bereit-Menge bei. |
| 6 | Wiederhole, bis die Bereit-Menge leer ist. |
Durchgerechnetes Beispiel
Sortierung des DAG mit den Kanten A→C, B→C, C→D, C→E, D→F, E→F (Anfangs-Eingangsgrade A:0 B:0 C:2 D:1 E:1 F:2):
| Schritt | Bereit-Menge | Reihenfolge | Aktion |
|---|---|---|---|
| 0 | {A, B} | [] | A und B starten mit Eingangsgrad 0, also sind beide bereit. |
| 1 | {B} | [A] | Gib A aus; seine Kante A→C senkt Cs Eingangsgrad von 2 → 1. |
| 2 | {C} | [A, B] | Gib B aus; seine Kante B→C senkt C von 1 → 0, also wird C bereit. |
| 3 | {D, E} | [A, B, C] | Gib C aus; die Kanten C→D und C→E senken D und E auf 0, beide werden bereit. |
| 4 | {E} | [A, B, C, D] | Gib D aus; seine Kante D→F senkt Fs Eingangsgrad von 2 → 1. |
| 5 | {F} | [A, B, C, D, E] | Gib E aus; seine Kante E→F senkt F von 1 → 0, also wird F bereit. |
| 6 | {} | [A, B, C, D, E, F] | Gib F aus; die Bereit-Menge ist leer und alle 6 Knoten sind geordnet - fertig. |
Wann die topologische Sortierung verwenden
| Verwende sie, wenn | Vermeide sie, wenn |
|---|---|
| Du eine Reihenfolge brauchst, die Abhängigkeiten respektiert (Build-Schritte, Paketinstallationen, Kursvoraussetzungen). | Der Graph ungerichtet ist - die topologische Ordnung ist nur für gerichtete Graphen definiert. |
| Der Graph ein DAG ist und du irgendeine gültige lineare Reihenfolge willst. | Der Graph Zyklen enthalten kann und du trotzdem eine totale Ordnung brauchst (es gibt keine). |
| Du Zyklen günstig erkennen willst - eine fehlgeschlagene topologische Sortierung beweist, dass einer existiert. | Du die kürzeste oder optimale Reihenfolge nach einem Gewicht brauchst; die einfache topologische Sortierung ignoriert Gewichte. |
Du die Reihenfolge einmal in O(V + E) verarbeitest. | Die Kanten sich ständig ändern und du bei jeder Aktualisierung neu sortieren musst, wo eine inkrementelle Struktur besser passt. |
Topological Sort-Code
Eine saubere, lauffähige Topological Sort-Implementierung in Python, JavaScript, Java, C++, C. Wähle eine Sprache, kopiere den Code oder öffne ihn vorgeladen im Coddy-Playground.
Topological Sort-Code in Python
1from collections import deque2
3
4def topological_sort(graph):5 # Kahn's algorithm: repeatedly remove nodes with no incoming edges6 in_degree = {node: 0 for node in graph}7 for node in graph:8 for neighbor in graph[node]:9 in_degree[neighbor] += 110 queue = deque(node for node in graph if in_degree[node] == 0)11 order = []12 while queue:13 node = queue.popleft()14 order.append(node)15 for neighbor in graph[node]:16 in_degree[neighbor] -= 117 if in_degree[neighbor] == 0:18 queue.append(neighbor)19 if len(order) != len(graph):20 raise ValueError("Graph has a cycle, no topological order")21 return order22
23
24graph = {25 "shirt": ["tie", "jacket"],26 "tie": ["jacket"],27 "pants": ["shoes", "jacket"],28 "socks": ["shoes"],29 "shoes": [],30 "jacket": [],31}32
33print(" -> ".join(topological_sort(graph)))Topological Sort-Code in JavaScript
1const graph = {2 A: ["C"],3 B: ["C", "D"],4 C: ["E"],5 D: ["F"],6 E: ["F"],7 F: [],8};9
10// Kahn algorithm: repeatedly take a node with no incoming edges11function topologicalSort() {12 const inDegree = {};13 for (const node in graph) inDegree[node] = 0;14 for (const node in graph) {15 for (const next of graph[node]) inDegree[next]++;16 }17 const queue = Object.keys(inDegree).filter((n) => inDegree[n] === 0);18 const order = [];19 while (queue.length > 0) {20 const node = queue.shift();21 order.push(node);22 for (const next of graph[node]) {23 if (--inDegree[next] === 0) queue.push(next);24 }25 }26 if (order.length !== Object.keys(graph).length) {27 throw new Error("Graph has a cycle");28 }29 return order;30}31
32console.log("Topological order:", topologicalSort().join(" -> "));Topological Sort-Code in Java
1import java.util.ArrayDeque;2import java.util.List;3import java.util.Queue;4
5public class Main {6 public static void main(String[] args) {7 int n = 6;8 // DAG: an edge u -> v means u must come before v9 List<List<Integer>> adj = List.of(10 List.of(2),11 List.of(2, 3),12 List.of(4),13 List.of(4),14 List.of(5),15 List.of()16 );17 int[] inDegree = new int[n];18 for (List<Integer> next : adj) {19 for (int v : next) inDegree[v]++;20 }21
22 // Kahn: start from every node with no prerequisites23 Queue<Integer> queue = new ArrayDeque<>();24 for (int i = 0; i < n; i++) {25 if (inDegree[i] == 0) queue.add(i);26 }27
28 StringBuilder order = new StringBuilder("Topological order:");29 while (!queue.isEmpty()) {30 int node = queue.poll();31 order.append(" ").append(node);32 for (int next : adj.get(node)) {33 if (--inDegree[next] == 0) queue.add(next);34 }35 }36 System.out.println(order);37 }38}Topological Sort-Code in C++
1#include <iostream>2#include <queue>3#include <vector>4
5int main() {6 // Directed acyclic graph: adj[u] lists nodes that depend on u7 std::vector<std::vector<int>> adj = {8 {}, // 09 {}, // 110 {3}, // 2 -> 311 {1}, // 3 -> 112 {0, 1}, // 4 -> 0, 113 {0, 2}, // 5 -> 0, 214 };15 int n = static_cast<int>(adj.size());16 std::vector<int> inDegree(n, 0);17 for (const auto& neighbors : adj) {18 for (int v : neighbors) ++inDegree[v];19 }20 // Kahn's algorithm: start from nodes with no incoming edges21 std::queue<int> ready;22 for (int u = 0; u < n; ++u) {23 if (inDegree[u] == 0) ready.push(u);24 }25 std::cout << "Topological order: ";26 while (!ready.empty()) {27 int u = ready.front();28 ready.pop();29 std::cout << u << " ";30 // Removing u unlocks neighbors whose in-degree drops to 031 for (int v : adj[u]) {32 if (--inDegree[v] == 0) ready.push(v);33 }34 }35 std::cout << "\n";36 return 0;37}Topological Sort-Code in C
1#include <stdio.h>2
3#define N 64
5int main(void) {6 // Directed acyclic graph: adj[u][v] = 1 means an edge u -> v7 int adj[N][N] = {0};8 adj[2][3] = 1;9 adj[3][1] = 1;10 adj[4][0] = 1;11 adj[4][1] = 1;12 adj[5][0] = 1;13 adj[5][2] = 1;14 int inDegree[N] = {0};15 for (int u = 0; u < N; u++) {16 for (int v = 0; v < N; v++) inDegree[v] += adj[u][v];17 }18 // Kahn's algorithm: start from nodes with no incoming edges19 int queue[N];20 int head = 0, tail = 0;21 for (int u = 0; u < N; u++) {22 if (inDegree[u] == 0) queue[tail++] = u;23 }24 printf("Topological order: ");25 while (head < tail) {26 int u = queue[head++];27 printf("%d ", u);28 // Removing u unlocks neighbors whose in-degree drops to 029 for (int v = 0; v < N; v++) {30 if (adj[u][v] && --inDegree[v] == 0) queue[tail++] = v;31 }32 }33 printf("\n");34 return 0;35}FAQ zur topologischen Sortierung
Wofür wird eine topologische Sortierung verwendet?
Sie ordnet Aufgaben so, dass jede Abhängigkeit vor dem steht, was sie benötigt. Reale Anwendungen sind Build-Systeme und Paketmanager (Abhängigkeiten zuerst kompilieren), die Kursplanung mit Voraussetzungen und die Auswertungsreihenfolge von Tabellenkalkulationsformeln.
Wie hoch ist die Zeitkomplexität der topologischen Sortierung?
O(V + E) Zeit, da jeder Knoten einmal verarbeitet und jede Kante einmal geprüft wird. Sie benötigen O(V) zusätzlichen Speicher.Warum erfordert die topologische Sortierung einen DAG?
a vor b und b vor a stehen muss, erfüllt keine lineare Ordnung beides. Daher existiert eine topologische Sortierung genau dann, wenn der Graph ein gerichteter azyklischer Graph ist. Der Kahn-Algorithmus erkennt einen Zyklus, wenn er endet, bevor alle Knoten ausgegeben wurden.Was ist der Unterschied zwischen dem Kahn-Algorithmus und der DFS-basierten topologischen Sortierung?
O(V + E); Kahn vermeidet tiefe Rekursion und legt die Bereit-Menge natürlich offen, während DFS oft kürzer zu schreiben ist.Wann sollte ich eine topologische Sortierung statt einer normalen Sortierung verwenden?
O(n log n)-Mergesort ordnet nach Wert; die topologische Sortierung ordnet nach „muss davor kommen“-Kanten und kann im Gegensatz zu einem Vergleichssortierverfahren viele gültige Antworten für dieselbe Eingabe liefern.Ist das Ergebnis einer topologischen Sortierung eindeutig?
Meist nicht. Immer wenn zwei oder mehr Knoten gleichzeitig bereit sind (Eingangsgrad 0), kannst du sie in beliebiger Reihenfolge ausgeben, sodass die meisten DAGs mehrere gültige topologische Sortierungen zulassen. Die Reihenfolge ist nur dann eindeutig, wenn es in jedem Schritt genau einen bereiten Knoten gibt, was der Fall ist, wenn der DAG eine einzige Kette bildet (einen Hamilton-Pfad).