有向连通分量的Tarjan算法

本文详细介绍Tarjan算法原理及实现过程,包括使用邻接矩阵和邻接表两种存储方式下的具体实现代码。通过Tarjan算法可以有效地求解有向图中的强连通分量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考博客:比较详细。

http://www.byvoid.com/blog/scc-tarjan/

算法伪代码描述:Tarjan(root)

1、初始化 def , low[], map[][]

2、stack<- root // 将 root 节点入栈

3、 对 与 root 相连的每个 节点 p 如果 p  在 stack 中 那么 low[root] = min(low[root], def[p]);并且 call  Tarjan(p)

4、 如果对 与 root 相连的每个 节点 p 如果 p 不在 stack 中 那么 low[root] = min( low[p], low[root])

5 、当 low[root] = def[roo] 时,输出栈里 在 root 之上的 所以 元素 为一个连通分量

6、继续搜索,直到所有的节点被遍历


代码如下:

1:邻接矩阵存储

#include <iostream>
#include<stack>
#include<vector>
using namespace std;

#define V 100

int map[V][V]; // 存放地图
int def[V];   // def[i] 记录 i 节点的访问时间
int low[V];  // 记录 i 节点连通分量 所在集合的能追朔到的最小 def
bool instack[V];// 标记 i 是否在 堆中, 在 为 1 , 不在 为 0
int count ;// 连通分量的个数
int temp;//

vector<int> result[V];// 保存连通分量的结果集

stack<int> stack_v ;

int index = 0;
int min ( int a, int b){
	return a>b?b:a;
}

void tarjan ( int map[][V], int def[V], int low[V],int root, int n){// n  表示节点的个数
	instack[root] = true;
	def[root] = low[root] = ++index;
	stack_v.push(root);
	//假设初始化已经完成
	// 从根节点 root (也就是 入度为 0 的节点进行 搜素)
	
	for ( int i = 1; i <= n; i++){
		if( map[root][i] == 1){// 表示 root 和 节点 i 连通
			if( def[i] == 0 ){ // 表示 i 点还没有被访问
				tarjan(map,def,low,i,n);
				low[root] = min( low[root],low[i]);
			}
			if( instack[i] == true){ // 如果 i 节点在 栈中
				low[root] = min( low[root], def[i]);
			}
		}
	}
	if( def[root] == low[root] ){
		count ++;
		do{
			temp = stack_v.top();
			stack_v.pop();
			instack[temp] = false;
			cout<< temp<<" ";
			result[count].push_back(temp);
		}while(temp != root);
		cout<<endl;
	}
}

int main(void){
	int n ,e, x, y;// n 为顶点个数, e 为表的个数
	cin>>n>>e;
	for( int i =0; i < e; i++){
		cin>>x>>y;
		map[x][y] = 1;
	}
	tarjan(map,def,low,1,n);
	cout<<count<<endl;
	return 0;
}

2:邻接表存储





#include<iostream>
#include<vector>
using namespace std;

#define  M 2000              //题目中可能的最大点数      
int STACK[M],top=0;          //Tarjan 算法中的栈
bool InStack[M];             //检查是否在栈中
int DFN[M];                  //深度优先搜索访问次序
int Low[M];                  //能追溯到的最早的次序
int ComponetNumber=0;        //有向图强连通分量个数
int Index=0;                 //索引号
vector <int> Edge[M];        //邻接表表示
vector <int> Component[M];   //获得强连通分量结果

int min(int a, int b){
	return a>b?b:a;
}
void Tarjan(int i)
{
    int j;
    DFN[i]=Low[i]=Index++;
    InStack[i]=true;
    STACK[++top]=i;
    for (int e=0;e<Edge[i].size();e++)
    {
        j=Edge[i][e];
        if (DFN[j]==-1) //如过点 j 还没有被访问
        {
            Tarjan(j);
            Low[i]=min(Low[i],Low[j]);
        }
        else if (InStack[j]) // 如果点j 在 
            Low[i]=min(Low[i],DFN[j]);
    }
    if (DFN[i]==Low[i])
    {
        cout<<"TT    "<<i<<"   "<<Low[i]<<endl;
        ComponetNumber++;

        do
        {
            j=STACK[top--];
            InStack[j]=false;
            Component[ComponetNumber].push_back(j);
        }
        while (j!=i);
    }
}

void solve(int N)     //此图中点的个数,注意是0-indexed!
{
    memset(STACK,-1,sizeof(STACK));
    memset(InStack,0,sizeof(InStack));
    memset(DFN,-1,sizeof(DFN));
    memset(Low,-1,sizeof(Low));
    for(int i=0;i<N;i++)
        if(DFN[i]==-1)
            Tarjan(i);   
}

/*
此算法正常工作的基础是图是0-indexed的。
*/
int main()
{
    Edge[0].push_back(1);Edge[0].push_back(2);
    Edge[1].push_back(3);
    Edge[2].push_back(3);Edge[2].push_back(4);
    Edge[3].push_back(0);Edge[3].push_back(5);
    Edge[4].push_back(5);
    int  N=6;
    solve(N);
    cout<<"ComponetNumber is "<<ComponetNumber<<endl;
    for(int i=0;i<N;i++)
        cout<<Low[i]<<" ";
    cout<<endl;
    for( i=0;i<N;i++)
    {
        for(int j=0;j<Component[i].size();j++)
            cout<<Component[i][j];
        cout<<endl;
    }
    return 0;
}




<think>好的,我现在需要给用户介绍C++中的Tarjan连通分量算法。首先,我得回忆一下Tarjan算法的基本概念和步骤,确保自己理解正确。 Tarjan算法是由Robert Tarjan提出的,用于寻找有向图中的强连通分量(SCC)。强连通分量指的是图中最大的子图,其中任意两个顶点都可以互相到达。这个算法使用深度优先搜索(DFS)来遍历图,并通过维护一个栈和一些辅助数组来跟踪节点的访问状态。 接下来,我应该分步骤详细说明算法的实现过程。首先,每个节点都有一个索引(index)和低链接值(lowlink)。索引表示节点被访问的顺序,低链接值是该节点能回溯到的最早访问的节点的索引。当某个节点的低链接值等于其索引时,说明找到了一个强连通分量,需要将栈中的节点弹出,直到当前节点为止,这些节点组成一个SCC。 然后,我需要考虑如何在C++中实现这个算法。通常会用邻接表来表示图结构,使用栈来保存当前路径上的节点,以及两个数组来存储索引和低链接值。还需要一个布尔数组来记录节点是否在栈中,以避免重复处理。 可能会遇到的问题包括如何初始化这些数组,如何处理未访问的节点,以及在DFS过程中如何更新低链接值。例如,当访问一个已经在栈中的节点时,需要比较当前节点的低链接值和目标节点的索引,取较小值来更新当前的低链接值。 此外,需要注意递归DFS的实现细节,比如递归终止条件,以及何时将节点弹出栈并记录SCC。代码示例需要清晰,变量命名要合理,确保用户能够理解每一步的作用。 最后,我应该测试一下这个算法,比如使用一个简单的有向图来验证输出是否正确。例如,对于包含四个节点的图,边为0→1,1→2,2→0,1→3,算法应该输出两个SCC:{0,1,2}和{3}。 在编写代码示例时,要注意注释和格式,帮助用户理解关键步骤。同时,要提醒用户关于时间复杂度的部分,Tarjan算法的时间复杂度是O(V+E),适合处理较大的图结构。 总结下来,我需要结构清晰地介绍算法原理、步骤,并提供可运行的C++代码示例,确保用户能够理解并实现该算法。同时,要检查是否有遗漏的关键点,比如栈的使用和低链接值的更新逻辑,确保解释准确无误。</think>以下是关于Tarjan连通分量算法的逐步说明和C++实现: **算法核心思想** 1. 通过DFS遍历图时维护一个栈 2. 使用两个关键数组: - $index[]$ 记录访问顺序 - $lowLink[]$ 记录能回溯到的最早节点 3. 当$lowLink[u] == index[u]$时,栈顶到当前节点的元素构成一个SCC **算法步骤** 1. 初始化全局计数器和标记数组 2. 对未访问的节点进行DFS 3. 每次DFS时: - 设置当前节点的index和lowLink - 将节点压入栈并标记在栈中 4. 遍历邻接节点: - 如果未访问,递归访问并更新lowLink - 如果已在栈中,更新lowLink值 5. 发现根节点时弹出栈中元素构成SCC **C++实现代码** ```cpp #include <vector> #include <stack> #include <algorithm> using namespace std; vector<vector<int>> sccTarjan(vector<vector<int>>& graph) { int n = graph.size(), index = 0; vector<int> indexMap(n, -1), lowLink(n, -1); vector<bool> onStack(n, false); stack<int> st; vector<vector<int>> scc; auto dfs = [&](auto self, int u) -> void { indexMap[u] = lowLink[u] = index++; st.push(u); onStack[u] = true; for(int v : graph[u]) { if(indexMap[v] == -1) { // 未访问 self(self, v); lowLink[u] = min(lowLink[u], lowLink[v]); } else if(onStack[v]) { // 已在栈中 lowLink[u] = min(lowLink[u], indexMap[v]); } } if(lowLink[u] == indexMap[u]) { // 发现SCC vector<int> component; while(true) { int v = st.top(); st.pop(); onStack[v] = false; component.push_back(v); if(v == u) break; } scc.push_back(component); } }; for(int i = 0; i < n; ++i) { if(indexMap[i] == -1) { dfs(dfs, i); } } return scc; } ``` **算法特征** - 时间复杂度:$O(V + E)$(线性时间) - 空间复杂度:$O(V)$(栈和辅助数组) - 特点:单次DFS即可找出所有SCC **使用示例** ```cpp // 构造图:0→1→2→0,1→3 vector<vector<int>> graph = { {1}, // 0 {2,3}, // 1 {0}, // 2 {} // 3 }; auto components = sccTarjan(graph); // 输出结果:[[3], [0,1,2]] ``` **应用场景** 1. 编译器中的代码优化 2. 电子电路分析 3. 社交网络社群发现 4. 代码依赖分析 5. 强连通性检测 关键理解点:$lowLink[u]$的更新策略既包含前向边的更新(递归返回时),也包含横向边的更新(遇到栈中节点时),这保证了能正确识别环路结构。当某个节点的lowLink等于其index值时,说明找到了环路的根节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值