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?
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.