📜  Kahn 的拓扑排序算法

📅  最后修改于: 2022-05-13 01:57:53.904000             🧑  作者: Mango

Kahn 的拓扑排序算法

有向循环( DAG ) 的拓扑排序是顶点的线性排序,使得对于每个有向边 uv,顶点 u 在排序中位于 v 之前。如果图不是 DAG,则无法对图进行拓扑排序。
例如,下图的拓扑排序是“5 4 2 3 1 0?”。一个图可以有多个拓扑排序。例如,下图的另一种拓扑排序是“4 5 2 0 3 1”。拓扑排序中的第一个顶点始终是入度为 0 的顶点(没有入边的顶点)。

图形

让我们看几个正确解释的例子,
例子:

已经讨论了用于查找拓扑排序的基于 DFS 的解决方案。
解决方案在本文中,我们将看到另一种在有向无环图 (DAG) 中找到顶点线性排序的方法。该方法基于以下事实:
DAG G 至少有一个入度为 0 的顶点和一个出度为 0 的顶点
证明:对上述事实有一个简单的证明,即 DAG 不包含循环,这意味着所有路径的长度都是有限的。现在让 S 是从 u(source) 到 v(destination) 的最长路径。因为 S 是最长的路径,所以不能有到 u 的入边,也没有来自 v 的出边,如果发生这种情况,那么 S 就不是最长的路径
=> 入度(u) = 0 和出度(v) = 0
算法:找到 DAG 的拓扑排序所涉及的步骤:
步骤 1:计算 DAG 中存在的每个顶点的入度(传入边数),并将访问节点的计数初始化为 0。
Step-2:选取所有入度为0的顶点,加入队列(入队操作)
Step-3:从队列中移除一个顶点(出队操作)然后。

  1. 将访问节点的计数增加 1。
  2. 将其所有相邻节点的度数减少 1。
  3. 如果相邻节点的入度减少到零,则将其添加到队列中。

第 4 步:重复第 3 步,直到队列为空。
步骤 5:如果访问节点的数量等于图中的节点数,则对给定的图进行拓扑排序是不可能的。
如何找到每个节点的入度?
有两种方法可以计算每个顶点的入度:

  1. 取一个度数数组,它将跟踪
    遍历边数组,只需将目标节点的计数器加 1。
for each node in Nodes
    indegree[node] = 0;
for each edge(src, dest) in Edges
    indegree[dest]++
  1. 时间复杂度:O(V+E)
  2. 遍历每个节点的列表,然后将连接到它的所有节点的入度增加 1。
for each node in Nodes
        If (list[node].size()!=0) then
        for each dest in list
            indegree[dest]++;
  1. 时间复杂度:外部for循环将执行V次,内部for循环将执行E次,因此总体时间复杂度为O(V+E)。
    算法的整体时间复杂度为 O(V+E)

下面是上述算法的 C++ 实现。该实现使用上面讨论的方法 2 来查找度数。

C++
// A C++ program to print topological
// sorting of a graph using indegrees.
#include 
using namespace std;
 
// Class to represent a graph
class Graph {
    // No. of vertices'
    int V;
 
    // Pointer to an array containing
    // adjacency listsList
    list* adj;
 
public:
    // Constructor
    Graph(int V);
 
    // Function to add an edge to graph
    void addEdge(int u, int v);
 
    // prints a Topological Sort of
    // the complete graph
    void topologicalSort();
};
 
Graph::Graph(int V)
{
    this->V = V;
    adj = new list[V];
}
 
void Graph::addEdge(int u, int v)
{
    adj[u].push_back(v);
}
 
// The function to do
// Topological Sort.
void Graph::topologicalSort()
{
    // Create a vector to store
    // indegrees of all
    // vertices. Initialize all
    // indegrees as 0.
    vector in_degree(V, 0);
 
    // Traverse adjacency lists
    // to fill indegrees of
    // vertices.  This step
    // takes O(V+E) time
    for (int u = 0; u < V; u++) {
        list::iterator itr;
        for (itr = adj[u].begin();
             itr != adj[u].end(); itr++)
            in_degree[*itr]++;
    }
 
    // Create an queue and enqueue
    // all vertices with indegree 0
    queue q;
    for (int i = 0; i < V; i++)
        if (in_degree[i] == 0)
            q.push(i);
 
    // Initialize count of visited vertices
    int cnt = 0;
 
    // Create a vector to store
    // result (A topological
    // ordering of the vertices)
    vector top_order;
 
    // One by one dequeue vertices
    // from queue and enqueue
    // adjacents if indegree of
    // adjacent becomes 0
    while (!q.empty()) {
        // Extract front of queue
        // (or perform dequeue)
        // and add it to topological order
        int u = q.front();
        q.pop();
        top_order.push_back(u);
 
        // Iterate through all its
        // neighbouring nodes
        // of dequeued node u and
        // decrease their in-degree
        // by 1
        list::iterator itr;
        for (itr = adj[u].begin();
             itr != adj[u].end(); itr++)
 
            // If in-degree becomes zero,
            // add it to queue
            if (--in_degree[*itr] == 0)
                q.push(*itr);
 
        cnt++;
    }
 
    // Check if there was a cycle
    if (cnt != V) {
        cout << "There exists a cycle in the graph\n";
        return;
    }
 
    // Print topological order
    for (int i = 0; i < top_order.size(); i++)
        cout << top_order[i] << " ";
    cout << endl;
}
 
// Driver program to test above functions
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\n";
    g.topologicalSort();
 
    return 0;
}


Java
// A Java program to print topological
// sorting of a graph using indegrees
import java.util.*;
 
// Class to represent a graph
class Graph {
    // No. of vertices
    int V;
 
    // An Array of List which contains
    // references to the Adjacency List of
    // each vertex
    List adj[];
    // Constructor
    public Graph(int V)
    {
        this.V = V;
        adj = new ArrayList[V];
        for (int i = 0; i < V; i++)
            adj[i] = new ArrayList();
    }
 
    // Function to add an edge to graph
    public void addEdge(int u, int v)
    {
        adj[u].add(v);
    }
    // prints a Topological Sort of the
    // complete graph
    public void topologicalSort()
    {
        // Create a array to store
        // indegrees of all
        // vertices. Initialize all
        // indegrees as 0.
        int indegree[] = new int[V];
 
        // Traverse adjacency lists
        // to fill indegrees of
        // vertices. This step takes
        // O(V+E) time
        for (int i = 0; i < V; i++) {
            ArrayList temp
                = (ArrayList)adj[i];
            for (int node : temp) {
                indegree[node]++;
            }
        }
 
        // Create a queue and enqueue
        // all vertices with indegree 0
        Queue q
            = new LinkedList();
        for (int i = 0; i < V; i++) {
            if (indegree[i] == 0)
                q.add(i);
        }
 
        // Initialize count of visited vertices
        int cnt = 0;
 
        // Create a vector to store result
        // (A topological ordering of the vertices)
        Vector topOrder = new Vector();
        while (!q.isEmpty()) {
            // Extract front of queue
            // (or perform dequeue)
            // and add it to topological order
            int u = q.poll();
            topOrder.add(u);
 
            // Iterate through all its
            // neighbouring nodes
            // of dequeued node u and
            // decrease their in-degree
            // by 1
            for (int node : adj[u]) {
                // If in-degree becomes zero,
                // add it to queue
                if (--indegree[node] == 0)
                    q.add(node);
            }
            cnt++;
        }
 
        // Check if there was a cycle
        if (cnt != V) {
            System.out.println(
                "There exists a cycle in the graph");
            return;
        }
 
        // Print topological order
        for (int i : topOrder) {
            System.out.print(i + " ");
        }
    }
}
// Driver program to test above functions
class Main {
    public static void main(String args[])
    {
        // Create a graph given in the above diagram
        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(
            "Following is a Topological Sort");
        g.topologicalSort();
    }
}


Python3
# A Python program to print topological sorting of a graph
# using indegrees
from collections import defaultdict
 
# Class to represent a graph
class Graph:
    def __init__(self, vertices):
        self.graph = defaultdict(list) # dictionary containing adjacency List
        self.V = vertices # No. of vertices
 
    # function to add an edge to graph
    def addEdge(self, u, v):
        self.graph[u].append(v)
 
 
    # The function to do Topological Sort.
    def topologicalSort(self):
         
        # Create a vector to store indegrees of all
        # vertices. Initialize all indegrees as 0.
        in_degree = [0]*(self.V)
         
        # Traverse adjacency lists to fill indegrees of
           # vertices.  This step takes O(V + E) time
        for i in self.graph:
            for j in self.graph[i]:
                in_degree[j] += 1
 
        # Create an queue and enqueue all vertices with
        # indegree 0
        queue = []
        for i in range(self.V):
            if in_degree[i] == 0:
                queue.append(i)
 
        # Initialize count of visited vertices
        cnt = 0
 
        # Create a vector to store result (A topological
        # ordering of the vertices)
        top_order = []
 
        # One by one dequeue vertices from queue and enqueue
        # adjacents if indegree of adjacent becomes 0
        while queue:
 
            # Extract front of queue (or perform dequeue)
            # and add it to topological order
            u = queue.pop(0)
            top_order.append(u)
 
            # Iterate through all neighbouring nodes
            # of dequeued node u and decrease their in-degree
            # by 1
            for i in self.graph[u]:
                in_degree[i] -= 1
                # If in-degree becomes zero, add it to queue
                if in_degree[i] == 0:
                    queue.append(i)
 
            cnt += 1
 
        # Check if there was a cycle
        if cnt != self.V:
            print ("There exists a cycle in the graph")
        else :
            # Print topological order
            print (top_order)
 
 
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()
 
# This code is contributed by Neelam Yadav


Javascript


输出:

Following is a Topological Sort
4 5 2 0 3 1

复杂性分析:

  • 时间复杂度: O(V+E)。
    外部 for 循环将执行 V 次,内部 for 循环将执行 E 次。
  • 辅助空间: O(V)。
    队列需要存储图的所有顶点。所以所需的空间是 O(V)