Topologische Sortierung: Python, C++ Algorithmusbeispiel

Was ist ein topologischer Sortieralgorithmus?

Die topologische Sortierung wird auch als Kahn-Algorithmus bezeichnet und ist ein beliebter Sortieralgorithmus. Unter Verwendung eines gerichteten Graphen als Eingabe sortiert Topological Sort die Knoten so, dass jeder vor dem Knoten erscheint, auf den er zeigt.

Dieser Algorithmus wird auf einen DAG (Directed Asymmetric Graph) angewendet, sodass jeder Knoten im geordneten Array erscheint, bevor alle anderen Knoten auf ihn verweisen. Dieser Algorithmus folgt einigen Regeln wiederholt, bis die Sortierung abgeschlossen ist.

Betrachten Sie zur Vereinfachung das folgende Beispiel:

Gerichteter Graph
Gerichteter Graph

Hier kรถnnen wir sehen, dass โ€žAโ€œ keinen Grad hat. Damit ist die Kante gemeint, die auf einen Knoten zeigt. โ€žBโ€œ und โ€žCโ€œ haben die Voraussetzung โ€žAโ€œ, dann hat โ€žEโ€œ die Voraussetzung โ€žDโ€œ- und โ€žFโ€œ-Knoten. Einige der Knoten sind von anderen Knoten abhรคngig.

Hier ist eine weitere Darstellung der obigen Grafik:

Abhรคngigkeit jedes Knotens

Abhรคngigkeit jedes Knotens (lineare Reihenfolge)

Wenn wir also den DAG (Directed Asymmetric Graph) an die topologische Sortierung รผbergeben, erhalten wir ein Array mit linearer Reihenfolge, bei dem das erste Element keine Abhรคngigkeit hat.

Topologischer Sortieralgorithmus

Dieses Beispiel zeigt ein Diagramm mit einem Zyklus:

Hier sind die Schritte dazu:

Schritt 1) Suchen Sie den Knoten mit null eingehenden Kanten, einen Knoten mit null Grad.

Schritt 2) Speicher, der In-Grad-Knoten in einer Warteschlange oder einem Stapel auf Null setzt und den Knoten aus dem Diagramm entfernt.

Schritt 3) Lรถschen Sie dann die ausgehende Kante von diesem Knoten.

Dadurch wird die In-Grad-Zรคhlung fรผr den nรคchsten Knoten verringert.

Die topologische Reihenfolge erfordert, dass die Diagrammdatenstruktur keinen Zyklus aufweist.

Ein Diagramm gilt als DAG, wenn es die folgenden Anforderungen erfรผllt:

  • Ein oder mehrere Knoten mit einem Gradwert von Null.
  • Diagramm enthรคlt keinen Zyklus

Solange es Knoten im Graphen gibt und der Graph noch DAG ist, fรผhren wir die obigen drei Schritte aus. Andernfalls fรคllt der Algorithmus in die zyklische Abhรคngigkeit und Kahns Algorithmus kann keinen Knoten mit einem Eingangsgrad von Null finden.

So funktioniert die topologische Sortierung

Hier verwenden wir โ€žKahns Algorithmusโ€œ fรผr die topologische Sortierung. Nehmen wir an, wir haben den folgenden Graphen:

Topologische Sortierarbeiten

Hier sind die Schritte fรผr Kahns Algorithmus:

Schritt 1) Berechnen Sie den Eingangsgrad oder die Eingangskante aller Knoten im Diagramm.

Hinweis:

  • Ingrad bezeichnet die gerichteten Kanten, die auf den Knoten zeigen.
  • Unter Grad versteht man die gerichteten Kanten, die von einem Knoten ausgehen.

Hier ist der In-Grad und Out-Grad des obigen Diagramms:

Topologische Sortierarbeiten

Schritt 2) Suchen Sie den Knoten mit null Grad oder eingehenden Kanten.

Der Knoten mit dem Grad Null bedeutet, dass keine Kanten auf diesen Knoten zukommen. Knoten โ€žAโ€œ hat null Grad, was bedeutet, dass es keine Kante gibt, die auf Knoten โ€žAโ€œ zeigt.

Wir werden also die folgenden Aktionen durchfรผhren:

  • Entfernen Sie diesen Knoten und seine AuรŸenkanten (ausgehende Kanten).
  • Platzieren Sie den Knoten zur Bestellung in der Warteschlange.
  • Aktualisieren Sie die In-Grad-Anzahl des Nachbarknotens von โ€žAโ€œ.

Topologische Sortierarbeiten

Schritt 3) Wir mรผssen einen Knoten mit einem Gradwert von Null finden. In diesem Beispiel haben โ€žBโ€œ und โ€žCโ€œ einen Grad von Null.

Hier kรถnnen wir eines dieser beiden nehmen. Nehmen wir โ€žBโ€œ und lรถschen Sie es aus dem Diagramm.

Aktualisieren Sie dann die Indegree-Werte anderer Knoten.

Nach der Durchfรผhrung dieser Vorgรคnge sehen unser Diagramm und unsere Warteschlange wie folgt aus:

Topologische Sortierarbeiten

Schritt 4) Knoten โ€žCโ€œ hat keine eingehende Kante. Wir entfernen also den Knoten โ€žCโ€œ aus dem Diagramm und verschieben ihn in die Warteschlange.

Wir kรถnnen auch die von โ€žCโ€œ ausgehende Kante lรถschen.

Nun sieht unser Diagramm so aus:

Topologische Sortierarbeiten

Schritt 5) Wir kรถnnen sehen, dass die Knoten โ€žDโ€œ und โ€žFโ€œ den Grad Null haben. Wir nehmen einen Knoten und stellen ihn in die Warteschlange.

Nehmen wir zuerst โ€žDโ€œ heraus. Dann ist die Ingradzahl fรผr Knoten โ€žEโ€œ 1. Jetzt gibt es keinen Knoten von D nach E.

Das Gleiche mรผssen wir fรผr den Knoten โ€žFโ€œ tun. Unser Ergebnis wird dann wie folgt aussehen:

Topologische Sortierarbeiten

Schritt 6) Der Indegree (eingehende Kanten) und Outdegree (ausgehende Kanten) des Knotens โ€žEโ€œ wurden Null. Wir haben also alle Voraussetzungen fรผr Knoten โ€žEโ€œ erfรผllt.

Hier setzen wir โ€žEโ€œ an das Ende der Warteschlange. Da wir keine Knoten mehr haben, endet der Algorithmus hier.

Topologische Sortierarbeiten

Pseudocode fรผr topologische Sortierung

Hier ist der Pseudocode fรผr die topologische Sortierung unter Verwendung des Kahn-Algorithmus.

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 O indegree present:
    topological_list.append(node)
    G.remove(node)
    Update Indegree of present nodes
  Return topological_list

Die topologische Sortierung kann auch mit dem DFS implementiert werden (Tiefe Erste Suche) Methode. Dieser Ansatz ist jedoch die rekursive Methode. Kahns Algorithmus ist effizienter als der DFS-Ansatz.

C++ Implementierung der topologischen Sortierung

#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();
}

Ausgang

0       1       2       3       5       4

Python Implementierung der topologischen Sortierung

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()

Ausgang

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

Zyklische Graphen des topologischen Sortieralgorithmus

Ein Graph, der einen Zyklus enthรคlt, kann nicht topologisch geordnet werden. Da der zyklische Graph die Abhรคngigkeit zyklisch hat.

Sehen Sie sich zum Beispiel diese Grafik an:

Zyklische Graphen des topologischen Sortieralgorithmus

Dieser Graph ist kein DAG (Directed Asymmetric Graph), da A, B und C einen Zyklus erzeugen. Wie Sie bemerken, gibt es keinen Knoten mit einem In-Grad-Wert von Null.

Wenn wir gemรครŸ Kahns Algorithmus die obige Grafik analysieren:

  • Suchen Sie einen Knoten mit null Grad (keine eingehenden Kanten).
  • Entfernen Sie diesen Knoten aus dem Diagramm und verschieben Sie ihn in die Warteschlange.
    Im obigen Diagramm gibt es jedoch keinen Knoten mit Null in Grad. Jeder Knoten hat einen In-Grad-Wert grรถรŸer als 0.
  • Gibt eine leere Warteschlange zurรผck, da kein Knoten mit Null in Grad gefunden werden konnte.

Wir kรถnnen Zyklen mithilfe der topologischen Ordnung mit den folgenden Schritten erkennen:

Schritt 1) Fรผhren Sie eine topologische Sortierung durch.

Schritt 2) Berechnen Sie die Gesamtzahl der Elemente in der topologisch sortierten Liste.

Schritt 3) Wenn die Anzahl der Elemente der Gesamtzahl der Scheitelpunkte entspricht, gibt es keinen Zyklus.

Schritt 4) Wenn sie nicht der Anzahl der Scheitelpunkte entspricht, gibt es mindestens einen Zyklus in der gegebenen Diagrammdatenstruktur.

Komplexitรคtsanalyse der topologischen Sortierung

Es gibt zwei Arten von Komplexitรคt in Algorithmen. Sie sind

  1. Zeitliche Komplexitรคt
  2. Raumkomplexitรคt

Diese Komplexitรคten werden mit einer Funktion dargestellt, die eine allgemeine Komplexitรคt bereitstellt.

Zeitliche Komplexitรคt: Die Zeitkomplexitรคt ist bei der topologischen Sortierung immer gleich. Es gibt Worst-Case-, Durchschnitts- und Best-Case-Szenarien fรผr die Zeitkomplexitรคt.

Die Zeitkomplexitรคt fรผr die topologische Sortierung betrรคgt O(E + V), wobei E die Anzahl der Kanten im Graphen und V die Anzahl der Eckpunkte im Graphen bezeichnet.

Lassen Sie uns diese Komplexitรคt durchbrechen:

Schritt 1) Zu Beginn berechnen wir alle Ingrade. Dazu mรผssen wir alle Kanten durchgehen und zunรคchst allen V-Scheitelpunkten den Wert Null zuweisen. Die inkrementellen Schritte, die wir durchfรผhren, werden also sein O(V+E).

Schritt 2) Wir werden den Knoten mit dem Gradwert Null finden. Wir mรผssen anhand der V-Nummer des Scheitelpunkts suchen. Damit sind die Schritte abgeschlossen O (V).

Schritt 3) Fรผr jeden Knoten mit null Eingangsgraden entfernen wir diesen Knoten und verringern den Eingangsgrad. Die Durchfรผhrung dieser Operation fรผr alle Knoten dauert O(E).

Schritt 4) AbschlieรŸend prรผfen wir, ob es einen Zyklus gibt oder nicht. Wir prรผfen, ob die Gesamtzahl der Elemente im sortierten Array gleich der Gesamtzahl der Knoten ist. Es wird dauern O (1).

Dies waren also die einzelnen Zeitkomplexitรคten fรผr jeden Schritt der topologischen Sortierung oder topologischen Ordnung. Wir kรถnnen sagen, dass die Zeitkomplexitรคt aus der obigen Berechnung O(V + E) betrรคgt; hier steht O fรผr die Komplexitรคtsfunktion.

Raumkomplexitรคt: Wir brauchten O(V) Rรคume, um den topologischen Sortieralgorithmus auszufรผhren.

Hier sind die Schritte, bei denen wir den Platz fรผr das Programm benรถtigten:

  • Wir mussten alle Ingrade der im Diagramm vorhandenen Knoten berechnen. Da der Graph insgesamt V Knoten hat, mรผssen wir ein Array der GrรถรŸe V erstellen. Der benรถtigte Platz war also O (V).
  • Eine Queue-Datenstruktur wurde verwendet, um den Knoten mit einem Grad von Null zu speichern. Wir haben die Knoten mit dem Grad Null aus dem ursprรผnglichen Diagramm entfernt und sie in die Warteschlange gestellt. Dafรผr war der erforderliche Platz vorhanden O (V).
  • Das Array heiรŸt โ€žorderโ€œ. Dadurch wurden die Knoten in topologischer Reihenfolge gespeichert. Das war auch erforderlich O (V) Rรคume.

Dies war die individuelle Raumkomplexitรคt. Daher mรผssen wir diese Rรคume wรคhrend der Laufzeit maximieren.

Die rรคumliche Komplexitรคt steht fรผr O(V), wobei V die Nummer der Scheitelpunkte im Graphen bezeichnet.

Anwendung der topologischen Sortierung

Die topologische Sortierung bietet vielfรคltige Einsatzmรถglichkeiten. Hier sind einige davon:

  • Es wird verwendet, wenn Betriebssystem muss die Ressourcenzuweisung durchfรผhren.
  • Einen Zyklus im Diagramm finden. Mit der topologischen Sortierung kรถnnen wir รผberprรผfen, ob der Graph DAG ist oder nicht.
  • Satzreihenfolge in den Apps zur automatischen Vervollstรคndigung.
  • Es dient der Erkennung Deadlocks.
  • Verschiedene Arten der Terminplanung oder Kursplanung nutzen die topologische Sortierung.
  • Abhรคngigkeiten auflรถsen. Wenn Sie beispielsweise versuchen, ein Paket zu installieren, benรถtigt dieses Paket mรถglicherweise auch andere Pakete. Durch die topologische Reihenfolge werden alle erforderlichen Pakete ermittelt, um das aktuelle Paket zu installieren.
  • Linux verwendet die topologische Sortierung in โ€žaptโ€œ, um die Abhรคngigkeit der Pakete zu รผberprรผfen.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: