tarjan看了几次了,这次才感觉明白了。

本文详细介绍Tarjan算法的工作原理,该算法用于寻找有向图中的强连通分量。通过深度优先搜索并利用low值和dfn值来确定强连通分量的根节点,最终实现高效地分割出所有强连通分量。

Tarjan算法的操作原理如下:

Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。

可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。

这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。

强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。

如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。

算法框架

Dfn(u)为节点u搜索的次序编号(时间戳)。

Low(u)我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值,为u或者u的子树能够追溯到的最早的栈中的节点的次序号dfn。

1.数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。

2.堆栈:每搜索到一个点,将它压入栈顶。

3.当点u有与点v相连时,如果此时(时间为dfn[u]时)v不在栈中,u的low值为两点的low值中较小的一个。(这是顺着树边向下找)

4.当点u有与点v相连时,如果此时(时间为dfn[u]时)v在栈中,u的low值为u的low值和v的dfn值中较小的一个。(出现环了,这是一条返祖边,一个联通分量可能有多个环)

5.每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。

6.继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。

伪代码

tarjan(u){

  DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值

  Stack.push(u)   // 将节点u压入栈中

  for each (u, v) in E // 枚举每一条边

    if (v is not visted) // 如果节点v未被访问过

        tarjan(v) // 继续向下找

        Low[u] = min(Low[u], Low[v])

    else if (v in S) // 如果节点u还在栈内

        Low[u] = min(Low[u], DFN[v])

  if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根

  repeat v = S.pop  // 将v退栈,为该强连通分量中一个顶点

  print v

  until (u== v)

}

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。N为点数,M为边数。

#include<cstdio>
#include<algorithm>
#include<string.h>
#include<iostream> 
using namespace std;
struct node {
	int v,next;
} edge[1001];
int DFN[1001],LOW[1001];
int stack[1001],heads[1001],visit[1001],cnt,tot,index;
void add(int x,int y) {
	edge[++cnt].next=heads[x];
	edge[cnt].v = y;
	heads[x]=cnt;
	return ;
}
void tarjan(int x) { //代表第几个点在处理。递归的是点。
	DFN[x]=LOW[x]=++tot;// 新进点的初始化。
	cout<<x<<":"<<DFN[x]<<" "<<LOW[x]<<endl;	
	stack[++index]=x;//进站
	visit[x]=1;//表示在栈里
	for(int i=heads[x]; i!=-1; i=edge[i].next) {
		if(!DFN[edge[i].v]) {//如果没访问过
		    cout<<"  in u-v:   "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" low"<<edge[i].v<<"="<<LOW[edge[i].v]<<endl;
			tarjan(edge[i].v);//往下进行延伸,开始递归
			cout<<"      out:  "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" ";
			LOW[x]=min(LOW[x],LOW[edge[i].v]);//递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。
			cout<<"  low"<<edge[i].v<<"="<<LOW[edge[i].v]<<"=> low"<<x<<":"<<LOW[x]<<endl; 
		} 
		else if(visit[edge[i].v ]) { //出现了环:如果访问过,并且还在栈里。 
			cout<<"  else"<<edge[i].v<<":instack  "<<x<<"->"<<edge[i].v<<"  low"<<x<<"="<<LOW[x]<<" ";
			LOW[x]=min(LOW[x],DFN[edge[i].v]);//比较谁是谁的儿子/父亲。找出所有子树中最早出现的dfn 
			cout<<"  dfn"<<edge[i].v<<"="<<DFN[edge[i].v]<<"=>low"<<x<<"="<<LOW[x]<<endl;
		}
	}
	if(LOW[x]==DFN[x]) { //发现是整个强连通分量子树里的最小根。
	     cout<<"ssc:";
		do {
			printf("   %d dfn=%d, low=%d    \n",stack[index],DFN[stack[index]],LOW[stack[index]]);
			visit[stack[index]]=0;
			index--;
		} while(x!=stack[index+1]);//出栈,并且输出。
		printf("\n");
	}
	return ;
}
int main() {
	memset(heads,-1,sizeof(heads));
	int n,m;
	scanf("%d%d",&n,&m);
	int x,y;
	for(int i=1; i<=m; i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1; i<=n; i++){
		cout<<i<<endl; 
			if(!DFN[i])  tarjan(i);//当这个点没有访问过,就从此点开始。防止图没走完
	}
	   
	
	return 0;
}

数据:及执行结果

6 7

1 2

2 3

3 5

3 4

4 1

5 6

5 4

1

1:1 1

  inu-v:   1->2  low1=1 low2=0

2:2 2

  inu-v:   2->3  low2=2 low3=0

3:3 3

  inu-v:   3->4  low3=3 low4=0

4:4 4

 else1:instack  4->1  low4=4  dfn1=1=>low4=1

     out:  3->4  low3=3  low4=1=> low3:1

  inu-v:   3->5  low3=1 low5=0

5:5 5

 else4:instack  5->4  low5=5  dfn4=4=>low5=4

  inu-v:   5->6  low5=4 low6=0

6:6 6

ssc:  6 dfn[]=6, low[]=6

 

     out:  5->6  low5=4  low6=6=> low5:4

     out:  3->5  low3=1  low5=4=> low3:1

      out: 2->3  low2=2   low3=1=> low2:1

     out:  1->2  low1=1  low2=1=> low1:1

ssc:  5 dfn[]=5, low[]=4

   4dfn[]=4, low[]=1

   3dfn[]=3, low[]=1

   2dfn[]=2, low[]=1

   1dfn[]=1, low[]=1

codevs1332 上白泽慧

Poj2186 Popular Cows


<think>嗯,用户想了解Tarjan算法的核心概念和用途。首先,我需要回忆一下Tarjan算法的相关知识。记得Tarjan算法是用来寻找有向图中的强连通分量的。强连通分量是指图中的一组顶点,每个顶点之间都有路径可达。Tarjan算法基于深度优先搜索(DFS),并且使用了栈和一些辅助数组来记录信息。 根据引用[3],Tarjan算法通过DFS遍历图,每个节点维护一个索引和一个低链接值。索引记录访问顺序,低链接值帮助确定强连通分量的根节点。当某个节点的低链接值等于自身索引时,说明找到了一个强连通分量。这个过程需要栈来跟踪当前路径上的节点。 然后,用户可能想知道Tarjan算法的用途。根据引用[1]和引用[3],它广泛应用于图论问题,如编译器优化、社交网络分析、电路设计等。Tarjan算法的时间复杂度是线性的,效率很高。 可能需要注意Tarjan算法的核心是后序遍历,如引用[5]提到的,类似于二叉树的后序遍历。递归处理相邻节点后再处理当前节点,通过比较低链接值来确定强连通分量。此外,引用[4]提到Tarjan算法可以不使用并查集,但并查集可以作为优化方案。 最后,用户的问题可能涉及与其他算法的比较,比如Kosaraju算法。虽然用户没问,但相关资料[1]的附录提到了Tarjan和Kosaraju的区别,可能需要准备相关知识点。不过当前问题只问核心概念和用途,所以这部分暂时不需要深入。 总结回答时要结构清晰,分点说明核心概念和用途,确保引用正确的资料,并在最后生成相关问题。</think>### Tarjan算法的核心概念及用途 #### **核心概念** 1. **强连通分量(SCC)** 强连通分量是有向图中的一组顶点,其中任意两个顶点之间均存在双向路径。例如,若存在路径$u \to v$和$v \to u$,则$u$和$v$属于同一个强连通分量[^3]。 2. **DFS遍历与栈结构** Tarjan算法基于深度优先搜索(DFS),通过维护一个**栈**记录当前路径上的节点。每个节点被访问时会被压入栈中,并在发现强连通分量时弹出。 3. **索引与低链接值** - **索引(Index)**:记录节点被访问的顺序,类似DFS的时间戳。 - **低链接值(Low Link)**:表示当前节点通过DFS树中的边或回边(指向栈中节点的边)能到达的最小索引。 当某节点的低链接值等于自身索引时,说明其是某个强连通分量的根节点,此时从栈中弹出节点构成一个SCC[^5]。 --- #### **主要用途** 1. **图结构分析** Tarjan算法用于高效分解有向图的强连通分量,帮助理解图的拓扑性质。例如,在编译器优化中可用于识别代码中的循环结构。 2. **算法优化** 其时间复杂度为$O(V + E)$(线性时间),优于其他SCC算法(如Kosaraju算法),适用于大规模图处理[^1][^2]。 3. **实际应用场景** - **社交网络**:分析用户群体的紧密联系。 - **电路设计**:检测电路中的反馈回路。 - **数据依赖分析**:在程序依赖图中识别强关联模块[^1]。 --- #### **示例:Tarjan算法伪代码框架** ```python def tarjan(u): index += 1 index_map[u] = low_link[u] = index stack.append(u) on_stack[u] = True for v in graph[u]: if index_map[v] == 0: tarjan(v) low_link[u] = min(low_link[u], low_link[v]) elif on_stack[v]: low_link[u] = min(low_link[u], index_map[v]) if low_link[u] == index_map[u]: scc = [] while True: v = stack.pop() on_stack[v] = False scc.append(v) if v == u: break result.append(scc) ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值