logo

Sortowanie topologiczne

Sortowanie topologiczne dla Skierowany graf acykliczny (DAG) jest liniowym uporządkowaniem wierzchołków takim, że dla każdej skierowanej krawędzi u-v, wierzchołek W przychodzi wcześniej W w zamówieniu.

Notatka: Sortowanie topologiczne grafu nie jest możliwe, jeśli graf nie jest a DZIEŃ .



Przykład:

Wejście: Wykres:

inaczej, jeśli Java
przykład

Przykład



Wyjście: 5 4 2 3 1 0
Wyjaśnienie: Pierwszym wierzchołkiem w sortowaniu topologicznym jest zawsze wierzchołek o stopniu wejściowym równym 0 (wierzchołek bez krawędzi przychodzących). Sortowanie topologiczne poniższego grafu to 5 4 2 3 1 0. Dla grafu może istnieć więcej niż jedno sortowanie topologiczne. Innym sortowaniem topologicznym poniższego wykresu jest 4 5 2 3 1 0.

Zalecana praktykaRozwiązanie oparte na systemie DFS umożliwiające znalezienie sortowania topologicznego zostało już omówione.

Porządek topologiczny może nie być unikalny:

Sortowanie topologiczne jest problemem zależności, w którym wykonanie jednego zadania zależy od wykonania kilku innych zadań, których kolejność może być różna. Wyjaśnijmy tę koncepcję na przykładzie:



Załóżmy, że naszym zadaniem jest dotarcie do naszej Szkoły i żeby tam dotrzeć, musimy się najpierw ubrać. Zależności w noszeniu ubrań obrazuje poniższy wykres zależności. Na przykład nie można założyć butów przed założeniem skarpetek.

1

Z powyższego zdjęcia już zdałeś sobie sprawę, że istnieje wiele sposobów na ubieranie się. Poniższy obrazek przedstawia niektóre z tych sposobów.

2

Czy możesz wymienić cały możliwy porządek topologiczny ubierania się zgodnie z powyższym wykresem zależności?

ciąg znaków w C++

Algorytm sortowania topologicznego przy użyciu DFS:

Oto algorytm krok po kroku sortowania topologicznego przy użyciu wyszukiwania w głąb (DFS):

  • Utwórz wykres za pomocą N wierzchołki i M -kierowane krawędzie.
  • Zainicjuj stos i odwiedzaną tablicę rozmiaru N .
  • Dla każdego nieodwiedzonego wierzchołka grafu wykonaj następujące czynności:
    • Wywołaj funkcję DFS z wierzchołkiem jako parametrem.
    • W funkcji DFS zaznacz wierzchołek jako odwiedzony i rekurencyjnie wywołaj funkcję DFS dla wszystkich nieodwiedzonych sąsiadów wierzchołka.
    • Po odwiedzeniu wszystkich sąsiadów wsuń wierzchołek na stos.
  • Po odwiedzeniu wierzchołków usuń elementy ze stosu i dołącz je do listy wyjściowej, aż stos będzie pusty.
  • Wynikowa lista jest topologicznie posortowanym porządkiem wykresu.

Ilustracja algorytmu sortowania topologicznego:

Poniższy obraz jest ilustracją powyższego podejścia:

Sortowanie topologiczne

Ogólny przebieg sortowania topologicznego

Krok 1:

  • Rozpoczynamy DFS od węzła 0, ponieważ nie ma on żadnych węzłów przychodzących
  • Wrzucamy węzeł 0 na stos i przechodzimy do następnego węzła posiadającego minimalną liczbę sąsiadujących węzłów, czyli węzła 1.

plik

Krok 2:

  • W tym kroku, ponieważ nie ma sąsiada z tym węzłem, włóż węzeł 1 na stos i przejdź do następnego węzła.

plik

Krok 3:

  • W tym kroku wybieramy węzeł 2, ponieważ ma on minimalną liczbę sąsiadujących węzłów po 0 i 1.
  • Wywołujemy DFS dla węzła 2 i wypychamy wszystkie węzły przechodzące z węzła 2 w odwrotnej kolejności.
  • Więc naciśnij 3, a następnie naciśnij 2.

plik

Krok 4:

  • Wywołujemy teraz DFS dla węzła 4
  • Ponieważ 0 i 1 są już obecne na stosie, po prostu wpychamy węzeł 4 na stos i wracamy.

plik

Krok 5:

  • W tym kroku, ponieważ wszystkie sąsiednie węzły 5 znajdują się już na stosie, wpychamy węzeł 5 na stos i wracamy.

plik

Krok 6: To ostatni krok sortowania topologicznego, w którym zdejmujemy wszystkie elementy ze stosu i drukujemy je w tej kolejności.

Poniżej implementacja powyższego podejścia:

C++
#include  using namespace std; // Function to perform DFS and topological sorting void topologicalSortUtil(int v, vector>& przym., wektor & odwiedził, stos & Stack) { // Oznacz bieżący węzeł jako odwiedzony [v] = true;  // Powtórz dla wszystkich sąsiednich wierzchołków for (int i: adj[v]) { if (!visited[i]) topologicalSortUtil(i, adj, odwiedził, Stack);  } // Prześlij bieżący wierzchołek na stos, który przechowuje wynik Stack.push(v); } // Funkcja wykonująca sortowanie topologiczne void topologicalSort(vector>& adj, int V) { stos Stos; // Stos do przechowywania wektora wynikowego odwiedził(V, fałsz);  // Wywołanie rekursywnej funkcji pomocniczej do przechowywania // Sortowanie topologiczne rozpoczynające się od wszystkich wierzchołków jeden po drugim // jeden dla (int i = 0; i< V; i++) {  if (!visited[i])  topologicalSortUtil(i, adj, visited, Stack);  }  // Print contents of stack  while (!Stack.empty()) {  cout << Stack.top() << ' ';  Stack.pop();  } } int main() {  // Number of nodes  int V = 4;  // Edges  vector> krawędzie = { { 0, 1 }, { 1, 2 }, { 3, 1 }, { 3, 2 } };  // Wykres reprezentowany jako wektor listy sąsiedztwa> przym(V);  for (auto i: krawędzie) { adj[i[0]].push_back(i[1]);  } cout<< 'Topological sorting of the graph: ';  topologicalSort(adj, V);  return 0; }>
Jawa
import java.util.*; public class TopologicalSort {  // Function to perform DFS and topological sorting  static void  topologicalSortUtil(int v, List> adj, boolean[] odwiedzone, Stos stos) { // Oznacz bieżący węzeł jako odwiedzony[v] = true;  // Powtórz dla wszystkich sąsiednich wierzchołków for (int i : adj.get(v)) { if (!visited[i]) topologicalSortUtil(i, adj, odwiedził, stos);  } // Prześlij bieżący wierzchołek na stos, który przechowuje // wynikowy stos.push(v);  } // Funkcja wykonująca sortowanie topologiczne static void topologicalSort(List> adj, int V) { // Stos do przechowywania wyniku Stack stos = nowy stos();  wartość logiczna[] odwiedzona = nowa wartość logiczna[V];  // Wywołanie rekursywnej funkcji pomocniczej do przechowywania // Sortowanie topologiczne rozpoczynające się od wszystkich wierzchołków jeden // jeden po drugim for (int i = 0; i< V; i++) {  if (!visited[i])  topologicalSortUtil(i, adj, visited, stack);  }  // Print contents of stack  System.out.print(  'Topological sorting of the graph: ');  while (!stack.empty()) {  System.out.print(stack.pop() + ' ');  }  }  // Driver code  public static void main(String[] args)  {  // Number of nodes  int V = 4;  // Edges  List> krawędzie = nowa ArrayList();  krawędzie.add(Arrays.asList(0, 1));  krawędzie.add(Arrays.asList(1, 2));  krawędzie.add(Arrays.asList(3, 1));  krawędzie.add(Arrays.asList(3, 2));  // Wykres reprezentowany jako lista sąsiedztwa Lista> adj = nowa ArrayList(V);  for (int i = 0; tj< V; i++) {  adj.add(new ArrayList());  }  for (List i : krawędzie) { adj.get(i.get(0)).add(i.get(1));  } sortowanie topologiczne(adj, V);  } }>
Python3
def topologicalSortUtil(v, adj, visited, stack): # Mark the current node as visited visited[v] = True # Recur for all adjacent vertices for i in adj[v]: if not visited[i]: topologicalSortUtil(i, adj, visited, stack) # Push current vertex to stack which stores the result stack.append(v) # Function to perform Topological Sort def topologicalSort(adj, V): # Stack to store the result stack = [] visited = [False] * V # Call the recursive helper function to store # Topological Sort starting from all vertices one by # one for i in range(V): if not visited[i]: topologicalSortUtil(i, adj, visited, stack) # Print contents of stack print('Topological sorting of the graph:', end=' ') while stack: print(stack.pop(), end=' ') # Driver code if __name__ == '__main__': # Number of nodes V = 4 # Edges edges = [[0, 1], [1, 2], [3, 1], [3, 2]] # Graph represented as an adjacency list adj = [[] for _ in range(V)] for i in edges: adj[i[0]].append(i[1]) topologicalSort(adj, V)>
C#
using System; using System.Collections.Generic; class Program {  // Function to perform DFS and topological sorting  static void TopologicalSortUtil(int v,  List> adj, bool[] odwiedził, Stos stos) { // Oznacz bieżący węzeł jako odwiedzony[v] = true;  // Powtórz dla wszystkich sąsiednich wierzchołków foreach(int i in adj[v]) { if (!visited[i]) TopologicalSortUtil(i, adj, odwiedzone, stos);  } // Prześlij bieżący wierzchołek na stos, który przechowuje // stos wyników.Push(v);  } // Funkcja wykonująca sortowanie topologiczne static void TopologicalSort(List> adj, int V) { // Stos do przechowywania wyniku Stack stos = nowy stos ();  bool[] odwiedzono = nowy bool[V];  // Wywołanie rekursywnej funkcji pomocniczej do przechowywania // Sortowanie topologiczne rozpoczynające się od wszystkich wierzchołków jeden // jeden po drugim for (int i = 0; i< V; i++) {  if (!visited[i])  TopologicalSortUtil(i, adj, visited, stack);  }  // Print contents of stack  Console.Write('Topological sorting of the graph: ');  while (stack.Count>0) { Console.Write(stack.Pop() + ' ');  } } // Kod sterownika static void Main(string[] args) { // Liczba węzłów int V = 4;  // Lista krawędzi> krawędzie = nowa lista>{nowa lista { 0, 1 }, nowa lista { 1, 2 }, nowa lista { 3, 1 }, nowa lista { 3, 2 } };  // Wykres reprezentowany jako lista sąsiedztwa Lista> adj = nowa lista>();  for (int i = 0; tj< V; i++) {  adj.Add(new List ());  } foreach(Lista i na krawędziach) { adj[i[0]].Add(i[1]);  } Sortowanie topologiczne(adj, V);  } }>
JavaScript
// Function to perform DFS and topological sorting function topologicalSortUtil(v, adj, visited, stack) {  // Mark the current node as visited  visited[v] = true;  // Recur for all adjacent vertices  for (let i of adj[v]) {  if (!visited[i])  topologicalSortUtil(i, adj, visited, stack);  }  // Push current vertex to stack which stores the result  stack.push(v); } // Function to perform Topological Sort function topologicalSort(adj, V) {  // Stack to store the result  let stack = [];  let visited = new Array(V).fill(false);  // Call the recursive helper function to store  // Topological Sort starting from all vertices one by  // one  for (let i = 0; i < V; i++) {  if (!visited[i])  topologicalSortUtil(i, adj, visited, stack);  }  // Print contents of stack  console.log('Topological sorting of the graph: ');  while (stack.length>0) { console.log(stack.pop() + ' ');  } } // Kod sterownika (() => { // Liczba węzłów const V = 4; // Krawędzie const krawędzie = [[0, 1], [1, 2], [3, 1], [3, 2]]; // Wykres reprezentowany jako lista sąsiedztwa const adj = Array.from({ długość: V }, () => []); for (let i krawędzi) { adj[i[0]].push (i[1]); Sortowanie topologiczne(adj, V })();>

Wyjście
Topological sorting of the graph: 3 0 1 2>

Złożoność czasowa: O(V+E). Powyższy algorytm to po prostu DFS z dodatkowym stosem. Zatem złożoność czasowa jest taka sama jak DFS
Przestrzeń pomocnicza: O(V). Dodatkowa przestrzeń jest potrzebna na stos

Sortowanie topologiczne przy użyciu BFS:

supw
C++
#include  #include  #include  using namespace std; // Class to represent a graph class Graph {  int V; // No. of vertices  list * przym.; // Wskaźnik do tablicy zawierającej // listy sąsiedztwa public: Graph(int V); // Konstruktor void addEdge(int v, int w); // Funkcja dodająca krawędź do wykresu void topologicalSort(); // drukuje sortowanie topologiczne // całego wykresu }; Graph::Graph(int V) { this->V = V;  adj = nowa lista [V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); // Dodaj w do listy v. } // Funkcja wykonująca sortowanie topologiczne void Graph::topologicalSort() { // Utwórz wektor do przechowywania wektorów stopni wszystkich wierzchołków in_stopień(V, 0);  // Przemierzaj listy sąsiedztwa, aby wypełnić stopień // wierzchołków dla (int v = 0; v< V; ++v) {  for (auto const& w : adj[v])  in_degree[w]++;  }  // Create a queue and enqueue all vertices with  // in-degree 0  queue Q;  for (int i = 0; tj< V; ++i) {  if (in_degree[i] == 0)  q.push(i);  }  // Initialize count of visited vertices  int count = 0;  // Create a vector to store topological order  vector górny_porządek;  // Jeden po drugim usuwaj wierzchołki z kolejki i umieszczaj w kolejce // sąsiednie wierzchołki, jeśli stopień przylegania wynosi 0 while (!q.empty()) { // Wyodrębnij początek kolejki (lub wykonaj usuwanie z kolejki) // i dodaj go do porządek topologiczny int u = q.front();  q.pop();  top_order.push_back(u);  // Iteruj przez wszystkie sąsiednie węzły // usuniętego z kolejki węzła u i zmniejsz ich stopień // o 1 listę ::iterator itr;  for (itr = adj[u].begin(); itr != adj[u].end(); ++itr) // Jeśli stopień-in wynosi zero, dodaj go do kolejki if (--in_stopień[*itr ] == 0) q.push(*itr);  liczyć++;  } // Sprawdź, czy był cykl if (count != V) { cout<< 'Graph contains cycle
';  return;  }  // Print topological order  for (int i : top_order)  cout << i << ' '; } // Driver code int main() {  // Create a graph given in the above diagram  Graph g(6);  g.addEdge(5, 2);  g.addEdge(5, 0);  g.addEdge(4, 0);  g.addEdge(4, 1);  g.addEdge(2, 3);  g.addEdge(3, 1);  cout << 'Following is a Topological Sort of the given '  'graph
';  g.topologicalSort();  return 0; }>
Jawa
import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; // Class to represent a graph class Graph {  private int V; // No. of vertices  private ArrayList [] przym.; // Lista sąsiedztwa // reprezentacja // wykresu // Konstruktor Graph(int V) { this.V = V;  adj = nowa ArrayList[V];  for (int i = 0; tj< V; ++i)  adj[i] = new ArrayList();  }  // Function to add an edge to the graph  void addEdge(int v, int w)  {  adj[v].add(w); // Add w to v’s list.  }  // Function to perform Topological Sort  void topologicalSort()  {  // Create an array to store in-degree of all  // vertices  int[] inDegree = new int[V];  // Calculate in-degree of each vertex  for (int v = 0; v < V; ++v) {  for (int w : adj[v]) {  inDegree[w]++;  }  }  // Create a queue and enqueue all vertices with  // in-degree 0  Queue q = nowa lista połączona();  for (int i = 0; tj< V; ++i) {  if (inDegree[i] == 0)  q.add(i);  }  // Initialize count of visited vertices  int count = 0;  // Create an ArrayList to store topological order  ArrayList topOrder = nowa ArrayList();  // Jeden po drugim usuwaj wierzchołki z kolejki i // umieszczaj w kolejce sąsiednie wierzchołki, jeśli stopień // przylegania osiągnie 0 while (!q.isEmpty()) { // Wyodrębnij początek kolejki i dodaj go do // porządku topologicznego int u = q.poll();  topOrder.add(u);  liczyć++;  // Wykonaj iterację przez wszystkie sąsiednie węzły // usuniętego z kolejki węzła u i zmniejsz ich stopień // o 1 for (int w : adj[u]) { // Jeśli stopień wejścia wyniesie zero, dodaj go do // kolejki if (--inDegree[w] == 0) q.add(w);  } } // Sprawdź, czy wystąpił cykl if (count != V) { System.out.println('Wykres zawiera cykl');  powrót;  } // Wydrukuj porządek topologiczny dla (int i : topOrder) System.out.print(i + ' ');  } } // Kod sterownika public class Main { public static void main(String[] args) { // Utwórz wykres przedstawiony na powyższym diagramie Graph g = new Graph(6);  g.addEdge(5, 2);  g.addEdge(5, 0);  g.addEdge(4, 0);  g.addEdge(4, 1);  g.addEdge(2, 3);  g.addEdge(3, 1);  System.out.println( 'Poniżej znajduje się sortowanie topologiczne danego wykresu');  g.Sortowanie topologiczne();  } }>
Python3
from collections import defaultdict class Graph: def __init__(self, vertices): # Number of vertices self.V = vertices # Dictionary to store adjacency lists self.adj = defaultdict(list) def addEdge(self, u, v): # Function to add an edge to the graph self.adj[u].append(v) def topologicalSort(self): # Function to perform Topological Sort # Create a list to store in-degree of all vertices in_degree = [0] * self.V # Traverse adjacency lists to fill in_degree of vertices for i in range(self.V): for j in self.adj[i]: in_degree[j] += 1 # Create a queue and enqueue all vertices with in-degree 0 q = [] for i in range(self.V): if in_degree[i] == 0: q.append(i) # Initialize count of visited vertices count = 0 # Create a list to store topological order top_order = [] # One by one dequeue vertices from queue and enqueue # adjacent vertices if in-degree of adjacent becomes 0 while q: # Extract front of queue (or perform dequeue) # and add it to topological order u = q.pop(0) top_order.append(u) # Iterate through all its neighbouring nodes # of dequeued node u and decrease their in-degree # by 1 for node in self.adj[u]: # If in-degree becomes zero, add it to queue in_degree[node] -= 1 if in_degree[node] == 0: q.append(node) count += 1 # Check if there was a cycle if count != self.V: print('Graph contains cycle') return # Print topological order print('Topological Sort:', top_order) # Driver code if __name__ == '__main__': # Create a graph given in the above diagram g = Graph(6) g.addEdge(5, 2) g.addEdge(5, 0) g.addEdge(4, 0) g.addEdge(4, 1) g.addEdge(2, 3) g.addEdge(3, 1) print('Following is a Topological Sort of the given graph') g.topologicalSort()>
JavaScript
// Class to represent a graph class Graph {  constructor(V) {  this.V = V; // No. of vertices  this.adj = new Array(V); // Array containing adjacency lists  for (let i = 0; i < V; i++) {  this.adj[i] = [];  }  }  // Function to add an edge to the graph  addEdge(v, w) {  this.adj[v].push(w); // Add w to v’s list.  }  // Function to perform Topological Sort  topologicalSort() {  // Create a array to store in-degree of all vertices  let inDegree = new Array(this.V).fill(0);  // Traverse adjacency lists to fill inDegree of vertices  for (let v = 0; v < this.V; v++) {  for (let w of this.adj[v]) {  inDegree[w]++;  }  }  // Create a queue and enqueue all vertices with in-degree 0  let queue = [];  for (let i = 0; i < this.V; i++) {  if (inDegree[i] === 0) {  queue.push(i);  }  }  // Initialize count of visited vertices  let count = 0;  // Create an array to store topological order  let topOrder = [];  // One by one dequeue vertices from queue and enqueue  // adjacent vertices if in-degree of adjacent becomes 0  while (queue.length !== 0) {  // Extract front of queue and add it to topological order  let u = queue.shift();  topOrder.push(u);  // Iterate through all its neighboring nodes  // of dequeued node u and decrease their in-degree by 1  for (let w of this.adj[u]) {  // If in-degree becomes zero, add it to queue  if (--inDegree[w] === 0) {  queue.push(w);  }  }  count++;  }  // Check if there was a cycle  if (count !== this.V) {  console.log('Graph contains cycle');  return;  }  // Print topological order  console.log('Topological Sort of the given graph:');  console.log(topOrder.join(' '));  } } // Driver code // Create a graph given in the above diagram let g = new Graph(6); g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); console.log('Following is a Topological Sort of the given graph:'); g.topologicalSort(); //This code is contributed by Utkarsh>

Wyjście
Following is a Topological Sort of the given graph 4 5 2 0 3 1>

Złożoność czasowa:

Złożoność czasowa konstrukcji grafu wynosi O(V + E), gdzie V to liczba wierzchołków, a E to liczba krawędzi.

stała Java

Złożoność czasowa sortowania topologicznego przy użyciu BFS wynosi również O(V + E), gdzie V to liczba wierzchołków, a E to liczba krawędzi. Dzieje się tak, ponieważ każdy wierzchołek i każda krawędź są odwiedzane raz podczas przejścia BFS.

Złożoność przestrzeni:

Złożoność przestrzenna przechowywania grafu przy użyciu listy sąsiedztwa wynosi O (V + E), gdzie V to liczba wierzchołków, a E to liczba krawędzi.

Dodatkowa przestrzeń jest wykorzystywana do przechowywania stopnia wierzchołków, co wymaga przestrzeni O(V).

Do przechodzenia przez BFS używana jest kolejka, która może zawierać co najwyżej V wierzchołków. Zatem złożoność przestrzenna kolejki wynosi O (V).

Ogólnie rzecz biorąc, złożoność przestrzenna algorytmu wynosi O (V + E) ze względu na przechowywanie wykresu, tablicy stopni i kolejki.

Podsumowując, złożoność czasowa dostarczonej implementacji wynosi O(V + E), a złożoność przestrzenna również O(V + E).

Notatka: Tutaj możemy również użyć tablicy zamiast stosu. Jeśli używana jest tablica, wydrukuj elementy w odwrotnej kolejności, aby uzyskać sortowanie topologiczne.

Zalety sortowania topologicznego:

  • Pomaga w planowaniu zadań lub wydarzeń w oparciu o zależności.
  • Wykrywa cykle na grafie skierowanym.
  • Skuteczne w rozwiązywaniu problemów z ograniczeniami pierwszeństwa.

Wady sortowania topologicznego:

  • Ma zastosowanie wyłącznie do skierowanych grafów acyklicznych (DAG), nie nadaje się do grafów cyklicznych.
  • Może nie być unikalny, może istnieć wiele prawidłowych porządków topologicznych.
  • Nieefektywne w przypadku dużych grafów z wieloma węzłami i krawędziami.

Zastosowania sortowania topologicznego:

  • Planowanie zadań i zarządzanie projektami.
  • Rozwiązywanie zależności w systemach zarządzania pakietami.
  • Ustalanie kolejności kompilacji w systemach budowy oprogramowania.
  • Wykrywanie zakleszczeń w systemach operacyjnych.
  • Plan zajęć na uniwersytetach.

Powiązane artykuły:

  • Algorytm Kahna do sortowania topologicznego
  • Wszystkie rodzaje topologiczne skierowanego grafu acyklicznego