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 ,如果是这种情况,我们可以说并得出结论,顶点u和v是强连通的,并且它们在强连通子图中。
在职的:
- 在给定的图形上执行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),并且与查找图的方法非常相似。图的拓扑排序。