https://en.wikipedia.org/wiki/Topological_sorting
[Knuth] Sortowanie topologiczne polega na zanurzeniu porządku częściowego (~<) w porządku liniowym, tj. na utworzeniu takiego ciągu elementów a[], że jeśli a[j] ~< a[k], to j < k.
Zbiór elementów z porządkiem częściowym może być zapisany w grafie skierowanym nie zawierającym cykli (DAG, ang. Directed Acyclic Graph). Sortowanie topologiczne wierzchołków polega na ich liniowym uporządkowaniu w taki sposób, że jeżeli istnieje krawędź skierowana (a,b), to wierzchołek a znajdzie się przed wierzchołkiem b. Zwykle wierzchołki grafu acyklicznego można posortować topologicznie na jeden lub więcej sposobów (jest gwarancja istnienia co najmniej jednego rozwiązania).
Przykłady porządku częściowego [Knuth].
SPECYFIKACJA Problem sortowania topologicznego. DANE WEJŚCIOWE Dany jest zbiór V liczb naturalnych od 0 do V-1 (wierzchołków) i E par tych liczb (krawędzi, połączeń) opisujących porządek częściowy w zbiorze liczb. DANE WYJŚCIOWE Ciąg liczb od 0 do V-1 posortowany topologicznie.
Podstawowe algorytmy sortowania topologicznego to:
[Tytuł z Wikipedii] Algorytm polega na wybraniu wierzchołka, który nie ma poprzednika. Inaczej mówiąc, wybieramy wierzchołek o stopniu wchodzącym 0. W grafie acyklicznym musi istnieć co najmniej jeden taki wierzchołek. Wierzchołek umieszczamy na pierwszym miejscu ciągu wynikowego, usuwamy go ze zbioru wierzchołków i usuwamy krawędzie z niego wychodzące. Powtarzamy proces dla zbioru pozostałych wierzchołków. Rozwiązaniem jest ciąg kolejno usuwanych wierzchołków.
Wersja dla grafów GvR bez otoczki obiektowej.
import collections
def topsort_queue(graph):
# Słownik z liczbą poprzedników wierzchołka
# (stopień wejściowy wierzchołka).
in_edges = dict((node, 0) for node in graph)
for source in graph:
for target in graph[source]:
in_edges[target] += 1
# Zakończyło się wczytywanie danych.
# Tworzymy kolejkę wierzchołków nie mających poprzedników.
sorted_nodes = list()
queue = collections.deque()
# Do kolejki idą pierwsze wierzchołki bez poprzedników.
for node in graph:
if in_edges[node] == 0:
queue.append(node)
while queue:
source = queue.popleft() # weź wierzchołek z kolejki
sorted_nodes.append(source) # wstaw wierzchołek do rozwiązania
# Usuwamy wszystkie krawędzie wychodzące z wierzchołka.
for target in graph[source]:
# Usuwamy poprzednika węzła target.
in_edges[target] -= 1
# Jeżeli zero, to wstawiamy do kolejki.
if in_edges[target] == 0:
queue.append(target)
return sorted_nodes
Algorytm polega na stworzeniu listy wierzchołków grafu odwiedzanych podczas wykonywania algorytmu DFS w kolejności ich czasu przetworzenia. Po odwróceniu kolejności wierzchołków na liście dostaniemy wierzchołki posortowane topologicznie. Czas działania jest O(V+E), bo tyle trwa DFS.
def topsort_dfs(graph):
sorted_nodes = list()
for source in graph:
if source not in sorted_nodes:
traverse_dfs(graph, source,
post_action=lambda node: sorted_nodes.append(node))
sorted_nodes.reverse()
return sorted_nodes