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 ,如果是这种情况,我们可以说并得出结论,顶点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)并且非常类似于寻找图的拓扑排序。
如果您想与行业专家一起参加直播课程,请参阅Geeks Classes Live