Algorytm sortowania topologicznego: Python, C++ Przykład

⚡ Inteligentne podsumowanie

Sortowanie topologiczne polega na uporządkowaniu węzłów skierowanego grafu acyklicznego w taki sposób, że każdy węzeł pojawia się przed węzłami, na które wskazuje, przy użyciu algorytmu Kahna, który umożliwia wielokrotne wybieranie węzłów o zerowym stopniu wejściowym.

  • 📐 Definicja: Sortowanie topologiczne generuje liniowy porządek wierzchołków DAG, w którym każda skierowana krawędź (u, v) ma u przed v.
  • 🔁 Algorytm Kahna: Wielokrotnie wybierz węzeł z zerową liczbą krawędzi przychodzących, dodaj go do kolejności i zmniejsz stopień wejściowy jego sąsiadów.
  • ???? Zablokowane cykle: Grafu zawierającego cykl nie można sortować topologicznie, ponieważ żaden węzeł nigdy nie osiąga zera stopni wewnątrz cyklu.
  • 💻 Code: C++ oraz Python Implementacje wykorzystują kolejkę i tablicę stopni wejściowych do obliczenia kolejności w czasie O(V + E).
  • 📊 Złożoność: Złożoność czasowa wynosi O(V + E), a złożoność przestrzenna wynosi O(V), gdzie V jest liczbą wierzchołków, a E liczbą krawędzi.
  • 🛠️. Aplikacje: Harmonogramowanie zadań i kompilacji, rozwiązywanie zależności pakietów (apt, npm), wykrywanie blokad i wymagania wstępne kursu — wszystkie te elementy korzystają z kolejności topologicznej.

Algorytm sortowania topologicznego

Co to jest algorytm sortowania topologicznego?

Sortowanie topologiczne jest również znane jako algorytm Kahna i jest popularnym algorytmem sortowania. Wykorzystując graf skierowany jako dane wejściowe, sortowanie topologiczne sortuje węzły w taki sposób, że każdy pojawia się przed tym, na który wskazuje.

Ten algorytm jest stosowany do DAG (Directed Acyclic Graph – ukierunkowanego grafu acyklicznego), tak aby każdy węzeł pojawiał się w uporządkowanej tablicy przed wszystkimi innymi węzłami, na które wskazuje. Algorytm ten powtarza pewne reguły, aż do zakończenia sortowania.

Dla uproszczenia przyjrzyjmy się poniższemu przykładowi:

Kierowany wykres

Kierowany wykres

Tutaj widzimy, że „A” nie ma stopnia wejściowego. Stopień wejściowy oznacza krawędź wskazującą na węzeł. „B” i „C” mają warunek wstępny w postaci węzłów „A”, a „E” ma warunek wstępny w postaci węzłów „D” i „F”. Niektóre węzły są zależne od innych węzłów.

Oto inna reprezentacja powyższego wykresu:

Zależność każdego węzła

Zależność każdego węzła (porządkowanie liniowe)

Zatem, gdy przekazujemy DAG (Skierowany Graf Acykliczny) do sortowania topologicznego, otrzymamy tablicę o uporządkowaniu liniowym, w której pierwszy element nie ma zależności.

Algorytm sortowania topologicznego

Oto kroki, aby to zrobić:

Krok 1) Znajdź węzeł z zerowymi krawędziami przychodzącymi i węzeł z zerowymi stopniami.

Krok 2) Zapisz węzeł o zerowym stopniu wejściowym w kolejce lub stosie i usuń węzeł z grafu.

Krok 3) Następnie usuń krawędź wychodzącą z tego węzła. Spowoduje to zmniejszenie liczby stopni wejściowych dla następnego węzła.

Porządkowanie topologiczne wymaga, aby struktura danych grafu nie zawierała żadnego cyklu. Graf zostanie uznany za DAG, jeśli spełnia następujące wymagania:

  • Jeden lub więcej węzłów o wartości stopnia równej zero.
  • Na wykresie nie ma żadnego cyklu.

Dopóki graf zawiera węzły i graf nadal jest DAG-iem, wykonamy powyższe trzy kroki. W przeciwnym razie algorytm popadnie w zależność cykliczną, a algorytm Kahna nie będzie w stanie znaleźć węzła o zerowym stopniu wejściowym.

Jak działa sortowanie topologiczne

Tutaj użyjemy „Algorytmu Kahna” do sortowania topologicznego. Załóżmy, że mamy następujący graf:

Sortowanie topologiczne działa

Oto kroki algorytmu Kahna:

Krok 1) Oblicz stopień wejściowy lub krawędź dochodzącą wszystkich węzłów na wykresie.

Uwaga:

  • Stopień oznacza skierowane krawędzie wskazujące na węzeł.
  • Stopień zewnętrzny oznacza skierowane krawędzie wychodzące z węzła.

Oto stopień wejściowy i wyjściowy powyższego wykresu:

Stopień wejściowy i stopień wyjściowy

Krok 2) Znajdź węzeł z zerowym stopniem wejściowym lub zerową liczbą krawędzi przychodzących. Węzeł z zerowym stopniem wejściowym oznacza, że ​​nie ma krawędzi zbliżających się do niego. Węzeł „A” ma zerowy stopień wejściowy, co oznacza, że ​​nie ma krawędzi wskazującej na węzeł „A”. Wykonamy zatem następujące czynności:

  • Usuń ten węzeł i jego krawędzie wychodzące.
  • Umieść węzeł w kolejce do zamówienia.
  • Zaktualizuj liczbę stopni wejściowych węzła sąsiedniego „A”.

Sortowanie topologiczne działa

Krok 3) Musimy znaleźć węzeł z wartością stopnia wejściowego równą zero. W tym przykładzie „B” i „C” mają zerowy stopień wejściowy. Możemy wybrać dowolny z nich. Weźmy „B” i usuńmy go z grafu. Następnie zaktualizujmy wartości stopni wejściowych pozostałych węzłów. Po wykonaniu tych operacji nasz graf i kolejka będą wyglądać następująco:

Sortowanie topologiczne działa

Krok 4) Węzeł „C” nie ma krawędzi przychodzącej. Dlatego usuniemy węzeł „C” z grafu i umieścimy go w kolejce. Możemy również usunąć krawędź wychodzącą z „C”. Teraz nasz graf będzie wyglądał następująco:

Sortowanie topologiczne działa

Krok 5) Widzimy, że węzły „D” i „F” mają stopień wejściowy równy zero. Weźmy węzeł i umieścimy go w kolejce. Najpierw usuńmy „D”. Wtedy liczba stopni wejściowych dla węzła „E” wyniesie 1. Teraz nie będzie żadnego węzła od D do E. Musimy zrobić to samo dla węzła „F”, a nasz wynik będzie wyglądał następująco:

Sortowanie topologiczne działa

Krok 6) Stopień wejściowy (krawędzie przychodzące) i stopień wyjściowy (krawędzie wychodzące) węzła „E” stały się równe zero. Spełniliśmy zatem wszystkie wymagania wstępne dla węzła „E”. W tym przypadku umieścimy „E” na końcu kolejki. Nie mamy już żadnych węzłów i algorytm kończy się w tym miejscu.

Sortowanie topologiczne działa

Rzekomy Code do sortowania topologicznego

Poniżej znajduje się pseudokod sortowania topologicznego przy użyciu algorytmu Kahna.

function TopologicalSort( Graph G ):
  for each node in G:
    calculate the indegree
  start = Node with 0 indegree
  G.remove(start)
  topological_list = [start]
  while node with 0 indegree present:
    topological_list.append(node)
    G.remove(node)
    // Update indegree of present nodes
  return topological_list

Sortowanie topologiczne można również wdrożyć za pomocą DFS (Głębokie pierwsze wyszukiwanie) metoda. Jednak to podejście jest metodą rekurencyjną. Algorytm Kahna jest bardziej wydajny niż podejście DFS.

C++ Implementacja sortowania topologicznego

#include<bits/stdc++.h>
using namespace std;
class graph{
  int vertices;
  list<int> *adjecentList;
public:
  graph(int vertices){
    this->vertices = vertices;
    adjecentList = new list<int>[vertices];
  }
  void createEdge(int u, int v){
    adjecentList[u].push_back(v);
  }
  void TopologicalSort(){
    // filling the vector with zero initially
    vector<int> indegree_count(vertices,0);

    for(int i=0;i<vertices;i++){
      list<int>::iterator itr;
      for(itr=adjecentList[i].begin(); itr!=adjecentList[i].end();itr++){
        indegree_count[*itr]++;
      }
    }
    queue<int> Q;
    for(int i=0; i<vertices;i++){
      if(indegree_count[i]==0){
        Q.push(i);
      }
    }
    int visited_node = 0;
    vector<int> order;
    while(!Q.empty()){
      int u = Q.front();
      Q.pop();
      order.push_back(u);

      list<int>::iterator itr;
      for(itr=adjecentList[u].begin(); itr!=adjecentList[u].end();itr++){
        if(--indegree_count[*itr]==0){
          Q.push(*itr);
        }
      }
      visited_node++;
    }
    if(visited_node!=vertices){
      cout<<"There's a cycle present in the Graph.\nGiven graph is not DAG"<<endl;
      return;
    }
    for(int i=0; i<order.size();i++){
      cout<<order[i]<<"\t";
    }
  }
};
int main(){
  graph G(6);
  G.createEdge(0,1);
  G.createEdge(0,2);
  G.createEdge(1,3);
  G.createEdge(1,5);
  G.createEdge(2,3);
  G.createEdge(2,5);
  G.createEdge(3,4);
  G.createEdge(5,4);
  G.TopologicalSort();
}

Wydajność

0       1       2       3       5       4

Python Implementacja sortowania topologicznego

from collections import defaultdict
class graph:
    def __init__(self, vertices):
        self.adjacencyList = defaultdict(list)
        self.Vertices = vertices  # No. of vertices
    # function to add an edge to adjacencyList
    def createEdge(self, u, v):
        self.adjacencyList[u].append(v)
    # The function to do Topological Sort.
    def topologicalSort(self):
        total_indegree = [0]*(self.Vertices)
        for i in self.adjacencyList:
            for j in self.adjacencyList[i]:
                total_indegree[j] += 1
        queue = []
        for i in range(self.Vertices):
            if total_indegree[i] == 0:
                queue.append(i)
        visited_node = 0
        order = []
        while queue:
            u = queue.pop(0)
            order.append(u)
            for i in self.adjacencyList[u]:
                total_indegree[i] -= 1

                if total_indegree[i] == 0:
                    queue.append(i)
            visited_node += 1
        if visited_node != self.Vertices:
            print("There's a cycle present in the Graph.\nGiven graph is not DAG")
        else:
            print(order)
G = graph(6)
G.createEdge(0,1)
G.createEdge(0,2)
G.createEdge(1,3)
G.createEdge(1,5)
G.createEdge(2,3)
G.createEdge(2,5)
G.createEdge(3,4)
G.createEdge(5,4)
G.topologicalSort()

Wydajność

[0, 1, 2, 3, 5, 4]

Wykresy cykliczne algorytmu sortowania topologicznego

Graf zawierający cykl nie może być uporządkowany topologicznie, ponieważ graf cykliczny ma zależność w sposób cykliczny. Na przykład, spójrz na ten graf:

Wykresy cykliczne algorytmu sortowania topologicznego

Ten graf nie jest skierowanym grafem acyklicznym (DAG), ponieważ A, B i C tworzą cykl. Zauważ, że nie ma węzła o zerowej wartości stopnia wejściowego. Zgodnie z algorytmem Kahna, jeśli przeanalizujemy powyższy graf:

  • Znajdź węzeł o zerowych stopniach (bez krawędzi przychodzących).
  • Usuń ten węzeł z Grafu i przenieś go do Kolejki. Jednak na powyższym Grafie nie ma węzła o zerowych stopniach wejściowych. Każdy węzeł ma wartość stopnia wejściowego większą niż 0.
  • Zwróć pustą kolejkę, ponieważ nie można znaleźć żadnego węzła o zerowych stopniach wejściowych.

Możemy wykryć cykle, stosując porządkowanie topologiczne, wykonując następujące kroki:

Krok 1) Wykonaj sortowanie topologiczne.

Krok 2) Oblicz całkowitą liczbę elementów na liście posortowanej topologicznie.

Krok 3) Jeżeli liczba elementów jest równa całkowitej liczbie wierzchołków, to cykl nie istnieje.

Krok 4) Jeśli nie jest ona równa liczbie wierzchołków, to w danej strukturze danych grafu istnieje co najmniej jeden cykl.

Analiza złożoności sortowania topologicznego

Istnieją dwa rodzaje złożoności algorytmów. Są to:

  1. Złożoność czasowa
  2. Złożoność przestrzeni

Złożoności te są reprezentowane przez funkcję zapewniającą ogólną złożoność.

Złożoność czasowa: Złożoność czasowa sortowania topologicznego jest taka sama. Istnieją najgorsze, średnie i najlepsze scenariusze złożoności czasowej. Złożoność czasowa sortowania topologicznego wynosi O(E + V), gdzie E oznacza liczbę krawędzi w grafie, a V oznacza liczbę wierzchołków w grafie.

Przełammy tę złożoność:

Krok 1) Na początku obliczymy wszystkie stopnie. Aby to zrobić, musimy przejść przez wszystkie krawędzie i początkowo przypiszemy wszystkie stopnie wierzchołków V do zera. Zatem kolejne kroki, które wykonamy, będą takie O(V+E).

Krok 2) Znajdziemy węzeł o zerowej wartości stopnia. Musimy szukać od numeru V wierzchołka. Tak więc kroki zostaną ukończone O (V).

Krok 3) Dla każdego węzła z zerowymi stopniami wejściowymi usuniemy ten węzeł i zmniejszymy stopień wejściowy. Wykonanie tej operacji dla wszystkich węzłów zajmie O(E).

Krok 4) Na koniec sprawdzimy, czy istnieje jakiś cykl, czy nie. Sprawdzimy, czy całkowita liczba elementów w posortowanej tablicy jest równa całkowitej liczbie węzłów. To zajmie O (1).

Oto zatem indywidualne złożoności czasowe dla każdego kroku sortowania topologicznego lub porządkowania topologicznego. Możemy powiedzieć, że złożoność czasowa z powyższego obliczenia wyniesie O(V + E); tutaj O oznacza funkcję złożoności.

Złożoność przestrzeni: Potrzebowaliśmy przestrzeni O(V) do uruchomienia algorytmu sortowania topologicznego. Oto kroki, w których potrzebowaliśmy tej przestrzeni dla programu:

  • Musieliśmy obliczyć wszystkie stopnie węzłów obecnych na wykresie. Ponieważ wykres ma w sumie V węzłów, musimy utworzyć tablicę o rozmiarze V. Zatem wymagana przestrzeń wynosiła O (V).
  • Do przechowywania węzła z zerowym stopniem wykorzystano strukturę danych Queue. Usunęliśmy węzły o zerowym stopniu z oryginalnego wykresu i umieściliśmy je w kolejce. W tym celu wymagana była przestrzeń O (V).
  • Tablica nosi nazwę „order”, co oznacza, że ​​węzły są przechowywane w kolejności topologicznej. Wymagało to również O (V) spacje.

To były indywidualne złożoności przestrzenne. Musimy więc zmaksymalizować te przestrzenie w czasie wykonania. Złożoność przestrzenna to O(V), gdzie V oznacza numer wierzchołka w grafie.

Zastosowanie sortowania topologicznego

Sortowanie topologiczne ma ogromne zastosowanie. Oto niektóre z nich:

  • Używa się go, gdy Operasystem tingu musi przeprowadzić alokację zasobów.
  • Znajdowanie cyklu w grafie. Możemy sprawdzić, czy graf jest DAG-iem, czy nie, za pomocą sortowania topologicznego.
  • Porządkowanie zdań w aplikacjach do automatycznego uzupełniania.
  • Służy do wykrywania zakleszczenia.
  • Różne typy harmonogramowania lub harmonogramowania kursów wykorzystują sortowanie topologiczne.
  • Rozwiązywanie zależności. Na przykład, jeśli spróbujesz zainstalować pakiet, ten pakiet może również potrzebować innych pakietów. Porządkowanie topologiczne wyszukuje wszystkie pakiety niezbędne do zainstalowania bieżącego pakietu.
  • Linux używa sortowania topologicznego w „apt”, aby sprawdzić zależności pakietów.

FAQ

Sortowanie topologiczne powoduje liniowe uporządkowanie wierzchołków DAG-u tak, że dla każdej skierowanej krawędzi od u do v, u pojawia się przed v w uporządkowaniu.

Każdy cykl zamyka każdy węzeł w sobie z niezerowym stopniem wejściowym, który nigdy nie spada do zera, więc algorytm Kahna nie może wybrać następnego węzła. Prawidłowy porządek topologiczny wymaga skierowanego grafu acyklicznego.

Algorytm Kahna iteracyjnie wykorzystuje kolejkę i liczniki stopni wejściowych. Sortowanie topologiczne oparte na DFS rekursywnie przechodzi przez graf i umieszcza gotowe węzły na stosie. Oba algorytmy działają w tempie O(V + E).

Złożoność czasowa wynosi O(V + E), ponieważ każdy wierzchołek i krawędź są przetwarzane raz. Złożoność przestrzenna wynosi O(V) dla tablicy stopni wejściowych, kolejki i tablicy kolejności wyjściowej.

Tak. Jeśli dwa lub więcej węzłów ma zerowy stopień wejściowy w tym samym kroku, każdy z nich może zostać pobrany jako pierwszy. Różne kolejności pobierania generują różne prawidłowe uporządkowania topologiczne tego samego DAG.

Menedżery pakietów, takie jak apt, npm i pip, wykorzystują porządek topologiczny do rozwiązywania zależności. Systemy kompilacji, harmonogramy zadań i planery wymagań wstępnych kursów również na nim polegają.

Ramy uczenia maszynowego, takie jak TensorFlow i PyTorSortuj topologicznie grafy obliczeniowe, aby zaplanować przebiegi do przodu i do tyłu. Sieci bayesowskie wymagają również topologicznego porządku zmiennych.

Tak. Narzędzia AI Copilot, takie jak GitHub Copilot, generują szablon algorytmu Kahna w C++, Pythonlub JavaDeweloperzy muszą nadal weryfikować wykrywanie cykli i prawidłową obsługę kolejek.

Podsumuj ten post następująco: