📜  Tarjan和Kosaraju算法的比较

📅  最后修改于: 2021-04-22 09:39:17             🧑  作者: Mango

Tarjan算法 Tarjan算法是一种有效的图算法,用于通过仅使用一次线性时间复杂度的DFS遍历来查找有向图中的强连接组件( SCC )。

在职的:

  • 在节点上执行DFS遍历,以便在遇到强连接组件的子树时将其删除。
  • 然后分配两个值:
    • 第一个值是第一次浏览节点时的计数器值。
    • 第二值存储从初始节点可到达的最低节点值,该节点不是另一个SCC的一部分。
  • 探索节点时,将它们压入堆栈。
  • 如果有节点的任何未探索的子项,则对其进行探索,并分别更新分配的值。

以下是使用Tarjan算法查找给定图的SCC的程序:

C++
// C++ program to find the SCC using
// Tarjan's algorithm (single DFS)
#include 
#include 
#include 
#define NIL -1
using namespace std;
  
// A class that represents
// an directed graph
class Graph {
    // No. of vertices
    int V;
  
    // A dynamic array of adjacency lists
    list* adj;
  
    // A Recursive DFS based function
    // used by SCC()
    void SCCUtil(int u, int disc[],
                 int low[], stack* st,
                 bool stackMember[]);
  
public:
    // Member functions
    Graph(int V);
    void addEdge(int v, int w);
    void SCC();
};
  
// Constructor
Graph::Graph(int V)
{
    this->V = V;
    adj = new list[V];
}
  
// Function to add an edge to the graph
void Graph::addEdge(int v, int w)
{
    adj[v].push_back(w);
}
  
// Recursive function to finds the SCC
// using DFS traversal
void Graph::SCCUtil(int u, int disc[],
                    int low[], stack* st,
                    bool stackMember[])
{
    static int time = 0;
  
    // Initialize discovery time
    // and low value
    disc[u] = low[u] = ++time;
    st->push(u);
    stackMember[u] = true;
  
    // Go through all vertices
    // adjacent to this
    list::iterator i;
  
    for (i = adj[u].begin();
         i != adj[u].end(); ++i) {
        // v is current adjacent of 'u'
        int v = *i;
  
        // If v is not visited yet,
        // then recur for it
        if (disc[v] == -1) {
            SCCUtil(v, disc, low,
                    st, stackMember);
  
            // Check if the subtree rooted
            // with 'v' has connection to
            // one of the ancestors of 'u'
            low[u] = min(low[u], low[v]);
        }
  
        // Update low value of 'u' only of
        // 'v' is still in stack
        else if (stackMember[v] == true)
            low[u] = min(low[u], disc[v]);
    }
  
    // head node found, pop the stack
    // and print an SCC
  
    // Store stack extracted vertices
    int w = 0;
  
    // If low[u] and disc[u]
    if (low[u] == disc[u]) {
        // Until stack st is empty
        while (st->top() != u) {
            w = (int)st->top();
  
            // Print the node
            cout << w << " ";
            stackMember[w] = false;
            st->pop();
        }
        w = (int)st->top();
        cout << w << "\n";
        stackMember[w] = false;
        st->pop();
    }
}
  
// Function to find the SCC in the graph
void Graph::SCC()
{
    // Stores the discovery times of
    // the nodes
    int* disc = new int[V];
  
    // Stores the nodes with least
    // discovery time
    int* low = new int[V];
  
    // Checks whether a node is in
    // the stack or not
    bool* stackMember = new bool[V];
  
    // Stores all the connected ancestors
    stack* st = new stack();
  
    // Initialize disc and low,
    // and stackMember arrays
    for (int i = 0; i < V; i++) {
        disc[i] = NIL;
        low[i] = NIL;
        stackMember[i] = false;
    }
  
    // Recursive helper function to
    // find the SCC in DFS tree with
    // vertex 'i'
    for (int i = 0; i < V; i++) {
  
        // If current node is not
        // yet visited
        if (disc[i] == NIL) {
            SCCUtil(i, disc, low,
                    st, stackMember);
        }
    }
}
  
// Driver Code
int main()
{
    // Given a graph
    Graph g1(5);
    g1.addEdge(1, 0);
    g1.addEdge(0, 2);
    g1.addEdge(2, 1);
    g1.addEdge(0, 3);
    g1.addEdge(3, 4);
  
    // Function Call to find SCC using
    // Tarjan's Algorithm
    g1.SCC();
  
    return 0;
}


C++
// C++ program to print the SCC of the
// graph using Kosaraju's Algorithm
#include 
#include 
#include 
using namespace std;
  
class Graph {
    // No. of vertices
    int V;
  
    // An array of adjacency lists
    list* adj;
  
    // Member Functions
    void fillOrder(int v, bool visited[],
                   stack& Stack);
    void DFSUtil(int v, bool visited[]);
  
public:
    Graph(int V);
    void addEdge(int v, int w);
    void printSCCs();
    Graph getTranspose();
};
  
// Constructor of class
Graph::Graph(int V)
{
    this->V = V;
    adj = new list[V];
}
  
// Recursive function to print DFS
// starting from v
void Graph::DFSUtil(int v, bool visited[])
{
    // Mark the current node as
    // visited and print it
    visited[v] = true;
    cout << v << " ";
  
    // Recur for all the vertices
    // adjacent to this vertex
    list::iterator i;
  
    // Traverse Adjacency List of node v
    for (i = adj[v].begin();
         i != adj[v].end(); ++i) {
  
        // If child node *i is unvisited
        if (!visited[*i])
            DFSUtil(*i, visited);
    }
}
  
// Function to get the transpose of
// the given graph
Graph Graph::getTranspose()
{
    Graph g(V);
    for (int v = 0; v < V; v++) {
        // Recur for all the vertices
        // adjacent to this vertex
        list::iterator i;
        for (i = adj[v].begin();
             i != adj[v].end(); ++i) {
            // Add to adjacency list
            g.adj[*i].push_back(v);
        }
    }
  
    // Return the reversed graph
    return g;
}
  
// Function to add an Edge to the given
// graph
void Graph::addEdge(int v, int w)
{
    // Add w to v’s list
    adj[v].push_back(w);
}
  
// Function that fills stack with vertices
// in increasing order of finishing times
void Graph::fillOrder(int v, bool visited[],
                      stack& Stack)
{
    // Mark the current node as
    // visited and print it
    visited[v] = true;
  
    // Recur for all the vertices
    // adjacent to this vertex
    list::iterator i;
  
    for (i = adj[v].begin();
         i != adj[v].end(); ++i) {
  
        // If child node *i is unvisited
        if (!visited[*i]) {
            fillOrder(*i, visited, Stack);
        }
    }
  
    // All vertices reachable from v
    // are processed by now, push v
    Stack.push(v);
}
  
// Function that finds and prints all
// strongly connected components
void Graph::printSCCs()
{
    stack Stack;
  
    // Mark all the vertices as
    // not visited (For first DFS)
    bool* visited = new bool[V];
    for (int i = 0; i < V; i++)
        visited[i] = false;
  
    // Fill vertices in stack according
    // to their finishing times
    for (int i = 0; i < V; i++)
        if (visited[i] == false)
            fillOrder(i, visited, Stack);
  
    // Create a reversed graph
    Graph gr = getTranspose();
  
    // Mark all the vertices as not
    // visited (For second DFS)
    for (int i = 0; i < V; i++)
        visited[i] = false;
  
    // Now process all vertices in
    // order defined by Stack
    while (Stack.empty() == false) {
  
        // Pop a vertex from stack
        int v = Stack.top();
        Stack.pop();
  
        // Print SCC of the popped vertex
        if (visited[v] == false) {
            gr.DFSUtil(v, visited);
            cout << endl;
        }
    }
}
  
// Driver Code
int main()
{
    // Given Graph
    Graph g(5);
    g.addEdge(1, 0);
    g.addEdge(0, 2);
    g.addEdge(2, 1);
    g.addEdge(0, 3);
    g.addEdge(3, 4);
  
    // Function Call to find the SCC
    // using Kosaraju's Algorithm
    g.printSCCs();
  
    return 0;
}


输出:
4
3
1 2 0

Kosaraju的算法 Kosaraju的算法也是深度优先搜索 基于算法的算法,该算法用于以线性时间复杂度在有向图中找到SCC。该算法的基本概念是,如果我们能够从顶点u开始初始到达顶点v ,那么我们应该能够从顶点v开始到达顶点u如果是这种情况,我们可以说并得出结论,顶点uv是强连通的,并且它们在强连通子图中。

在职的:

  • 在给定的图形上执行DFS遍历,跟踪每个节点的完成时间。可以通过使用堆栈来执行此过程。
  • 当在图形上运行DFS遍历的过程完成时,将源顶点放在堆栈上。这样,具有最高完成时间的节点将位于堆栈的顶部。
  • 通过使用邻接表来反转原始图形。
  • 然后在反向图上执行另一个DFS遍历,将源顶点作为堆栈顶部的顶点。当在反向图上运行的DFS完成时,所有被访问的节点将形成一个紧密连接的组件。
  • 如果剩余或未访问任何其他节点,则表明图中存在多个强连接组件。
  • 因此,从堆栈顶部弹出顶点,直到找到有效的未访问节点。在所有当前未访问的节点中,这将具有最高的完成时间。

以下是使用Kosaraju算法查找给定图的SCC的程序:

C++

// C++ program to print the SCC of the
// graph using Kosaraju's Algorithm
#include 
#include 
#include 
using namespace std;
  
class Graph {
    // No. of vertices
    int V;
  
    // An array of adjacency lists
    list* adj;
  
    // Member Functions
    void fillOrder(int v, bool visited[],
                   stack& Stack);
    void DFSUtil(int v, bool visited[]);
  
public:
    Graph(int V);
    void addEdge(int v, int w);
    void printSCCs();
    Graph getTranspose();
};
  
// Constructor of class
Graph::Graph(int V)
{
    this->V = V;
    adj = new list[V];
}
  
// Recursive function to print DFS
// starting from v
void Graph::DFSUtil(int v, bool visited[])
{
    // Mark the current node as
    // visited and print it
    visited[v] = true;
    cout << v << " ";
  
    // Recur for all the vertices
    // adjacent to this vertex
    list::iterator i;
  
    // Traverse Adjacency List of node v
    for (i = adj[v].begin();
         i != adj[v].end(); ++i) {
  
        // If child node *i is unvisited
        if (!visited[*i])
            DFSUtil(*i, visited);
    }
}
  
// Function to get the transpose of
// the given graph
Graph Graph::getTranspose()
{
    Graph g(V);
    for (int v = 0; v < V; v++) {
        // Recur for all the vertices
        // adjacent to this vertex
        list::iterator i;
        for (i = adj[v].begin();
             i != adj[v].end(); ++i) {
            // Add to adjacency list
            g.adj[*i].push_back(v);
        }
    }
  
    // Return the reversed graph
    return g;
}
  
// Function to add an Edge to the given
// graph
void Graph::addEdge(int v, int w)
{
    // Add w to v’s list
    adj[v].push_back(w);
}
  
// Function that fills stack with vertices
// in increasing order of finishing times
void Graph::fillOrder(int v, bool visited[],
                      stack& Stack)
{
    // Mark the current node as
    // visited and print it
    visited[v] = true;
  
    // Recur for all the vertices
    // adjacent to this vertex
    list::iterator i;
  
    for (i = adj[v].begin();
         i != adj[v].end(); ++i) {
  
        // If child node *i is unvisited
        if (!visited[*i]) {
            fillOrder(*i, visited, Stack);
        }
    }
  
    // All vertices reachable from v
    // are processed by now, push v
    Stack.push(v);
}
  
// Function that finds and prints all
// strongly connected components
void Graph::printSCCs()
{
    stack Stack;
  
    // Mark all the vertices as
    // not visited (For first DFS)
    bool* visited = new bool[V];
    for (int i = 0; i < V; i++)
        visited[i] = false;
  
    // Fill vertices in stack according
    // to their finishing times
    for (int i = 0; i < V; i++)
        if (visited[i] == false)
            fillOrder(i, visited, Stack);
  
    // Create a reversed graph
    Graph gr = getTranspose();
  
    // Mark all the vertices as not
    // visited (For second DFS)
    for (int i = 0; i < V; i++)
        visited[i] = false;
  
    // Now process all vertices in
    // order defined by Stack
    while (Stack.empty() == false) {
  
        // Pop a vertex from stack
        int v = Stack.top();
        Stack.pop();
  
        // Print SCC of the popped vertex
        if (visited[v] == false) {
            gr.DFSUtil(v, visited);
            cout << endl;
        }
    }
}
  
// Driver Code
int main()
{
    // Given Graph
    Graph g(5);
    g.addEdge(1, 0);
    g.addEdge(0, 2);
    g.addEdge(2, 1);
    g.addEdge(0, 3);
    g.addEdge(3, 4);
  
    // Function Call to find the SCC
    // using Kosaraju's Algorithm
    g.printSCCs();
  
    return 0;
}
输出:
0 1 2 
3 
4

时间复杂度
Tarjan算法和Kosaraju算法的时间复杂度为O(V + E) ,其中V表示顶点集, E表示图的边集。与Kosaraju算法相比,Tarjan算法的常数因子要低得多。在Kosaraju的算法中,图的遍历至少完成了2次,因此常数因子可以是两倍的时间。在执行第二个DFS时,我们可以使用Kosaraju的算法打印进行中的SCC。在执行Tarjan算法时,在找到SCC子树的头部之后,需要额外的时间来打印SCC。

总结
两种方法都具有相同的线性时间复杂度,但是SCC计算的技术或过程却大不相同。 Tarjan的方法仅依赖于DFS中的节点记录来对图进行分区,而Kosaraju的方法在图上执行两个DFS(如果要保持原始图不变,则执行3 DFS),并且与查找图的方法非常相似。图的拓扑排序。