Tarjan算法

1,关于Tarjan

\((1)\)\(Tarjan\)算法的目的:处理图上的连通性问题
\((2)\)\(Tarjan\)算法的用途:
     \((I)\)    在有向图中求解强连通分量(\(SCC\))问题,进而进行缩点,判环等操作
    \((II)\)   在无向图中求解双连通分量割点,割边
   \((III)\)  配合 DFS 树求最近公共祖先\((LCA)\)\((离线 Tarjan-LCA)\)
\((3)\) Tarjan介绍

2.有向图中\(Tarjan\)算法的原理及写法

\((1)\) 强连通分量\((SCC)\)
定义:图中最大的每个节点可以相互到达的子图
\((2)\) \(Tarjan\)算法求\(SCC\)
\((I)\) \(DFS\)树:

Tarjan2

上图即一个有向图的\(DFS\)
有向图的\(DFS\)树有\(4\)种边
1.树边\((tree\) \(edge)\) : 示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边.
2.返祖边\((back\) \(edge)\) :示意图中以绿色边表示(即\(7 \rightarrow 1\)),也被叫做回边,即指向祖先结点的边.
3.横叉边\((cross\) \(edge)\) :示意图中以红色边表示(即\(6\rightarrow 3\)),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点并不是当前结点的祖先.
4.前向边\((forward\) \(edge)\) :示意图中以蓝色边表示(即\(2\rightarrow 5\)),它是在搜索的时候遇到子树中的结点的时候形成的.
\((II)\) \(Tarjan\)算法求\(SCC\)原理
我们引入两个数组 \(dfn\) ,  \(low\)
对于任意的节点 \(u\) 我们有 :
\(dfn[u]\) :表示 \(u\) 节点的 \(DFS\) 序(即 \(u\) 节点在 \(DFS\) 中第几个被访问到)
\(low[u]\) :表示 \(u\) 节点最多通过一条非树边可以到达的最远祖先节点
所以在 \(DFS\) \(u\rightarrow v\) 这条边时,我们有:
1.若 \(v\) 没有被遍历(即 \(dfn[v]=0\) ) : \(low[u]=min(low[u],low[v])\)
2.若 \(v\) 被遍历了(即 \(dfn[v]!=0\) ),则 \(u\rightarrow v\) 是一条非树边 : \(low[u]=min(dfn[v],low[u])\) (因为 \(low[u]\) 的求解最多经过一条非树边,所以为 \(dfn[v]\)\(low[u]=dfn[v]\) 则说明 \(u\rightarrow v\)是我们选的唯一的那条非树边)
\(DFS\) 开始时,我们记 \(dfn[u]=low[u]=++tot;\)\(u\) 放入栈中
如果将 \(u\) 的子节点遍历完成后仍有 \(dfn[u]=low[u]\) 说明 \(u\) 无法通过最少一条非树边到达其祖先,所以 \(u\) 及其不属于其他 \(SCC\) 的孩子成为一个 \(SCC\) \(u\) 为其根结点,从栈中弹出 \(u\) 以上的点即可

核心代码

int vis[N],dfn[N],low[N];//vis[i]表示 i号节点是否在栈中 
int col[u],num,tot,siz[N];stack<int> st;vector<int> e[N];
//col[v]表示 v节点属于哪个强连通分量,siz[i]表示 i号SCC的大小 ,num为编号器兼SCC数目的统计 
void Tarjan(int u){
	dfn[u]=low[u]=++tot;
	vis[u]=1,st.push(u);
	for(auto v:e[u]){
		if(!dfn[v]){
			Tarjan(v);
			low[u]=min(low[u],low[v]);//因为 u->v 是一条树边 
		}
		else if(vis[v]) low[u]=min(low[u],dfn[v]);//u->v 为非树边 
	} 
	if(dfn[u]==low[u]){
		num++;
		while(!st.empty()){
			int v=st.top();st.pop();
			col[v]=num;
			siz[num]++;
			vis[v]=0;
			if(u==v) break;
		}
	} 
}

\(Tips:\) 因为\(SCC\)间不一定联通,所以这样写

for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);

3.有向图中Tarjan的应用

\((1)\) 缩点:缩点就是将一个图中的 \(SCC\) 缩成一个点,一般用于解决类似这类问题,在缩点之后,一般还会有重建边

//重建边
vector<int> g[N];
for(int i=1;i<=n;i++){
	for(auto y:e[i]) if(col[y]!=col[i]) g[col[i]].push_back(col[y]);
}

4.无向图中的Tarjan

\((1)\) 割点(割顶)
\((I)\)定义:删去该点后,该图不连通
\((II)\)性质:在\(DFS\)树中若对与节点\(u\)的所有儿子\(v\) 均有\(low[v]>=low[u]\)\(u\)是割点
求法:

void Tarjan(int u,int f){
	dfn[u]=low[u]=++tot;
	int son=0;
	for(auto y:e[u]){
		int v=y.first;
		if(!dfn[v]){
			son++;
			Tarjan(v,y.second);
			if(low[v]>=dfn[u]) is[u]=1;
			low[u]=min(low[u],low[v]);
		}
		else if(y.second!=f){
			low[u]=min(low[u],dfn[v]);
		} 
	}
	if(f==-1&&son<=1) is[u]=0;//一个二元环
	cnt+=is[u];
}

\((2)\) 点双连通分量
\((I)\) 点双连通定义:若对于一个无向图,其任意一个节点对于这个图本身而言都不是割点,则称其点双连通。也就是说,删除任意点及其相关边后,整个图仍然属于一个连通分量
\((II)\) 点双连通分量定义:对于一个无向图,点双连通分量即其上的极大点双联通子图
\((III)\)性质:1.两个点双最多只有一个公共点(即都有边与之相连的点);且这个点在这两个点双和它形成的子图中是割点

posted @ 2026-03-03 19:55  Underthetwilight  阅读(12)  评论(0)    收藏  举报