📜  Tarjan 算法与 Kosaraju 算法的比较

📅  最后修改于: 2021-10-25 04:54:23             🧑  作者: Mango

Tarjan’s Algorithm Tarjan’s Algorithm 是一种高效的图算法,用于在线性时间复杂度中仅使用一次 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)并且非常类似于寻找图的拓扑排序。

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。