underthetwilight

[图论]Tarjan算法

\(1.\)关于\(\text{Tarjan}\)

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

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

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

Tarjan2

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

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

\(3.\)有向图中\(\text{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.\)无向图中的\(\text{Tarjan}\)

\((1)\) 割点(割顶)
\((\text i)\)定义:删去该点后,该图不连通
\((\text{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)\) 点双连通分量
\((\text{i}).\) 点双连通定义:若对于一个无向图,其任意一个节点对于这个图本身而言都不是割点,则称其点双连通。也就是说,删除任意点及其相关边后,整个图仍然属于一个连通分量
\((\text{ii}).\) 点双连通分量定义:对于一个无向图,点双连通分量即其上的极大点双联通子图
\((\text{iii}).\)性质:两个点双最多只有一个公共点(即都有边与之相连的点);且这个点在这两个点双和它形成的子图中是割点
求法:

void Tarjan(int u,int f){
	dfn[u]=low[u]=++tot;
	int son=0;st.push(u);
	for(auto y:e[u]){
		int v=y.first;
		if(!dfn[v]){
			son++;
			Tarjan(v,y.second);
			if(low[v]>=dfn[u]){
				num++;
				ans[num].push_back(u);
				while(!st.empty()){
					int x=st.top();st.pop();
					ans[num].push_back(x);
					if(x==v) break;
				}
			}
			low[u]=min(low[u],low[v]);
		}
		else if(y.second!=f){
			low[u]=min(low[u],dfn[v]);
		} 
	}
	if(son==0&&f==-1) ans[++num].push_back(u);
}

\((3).\)边双连通分量
即删去一条边,该图不连通,边双连通分量类似于上述

int dfn[N],low[N],col[N],num,tot,vis[N];
std::stack<int> st;
void Tarjan(int u,int ff){
    dfn[u]=low[u]=++tot;st.push(u);vis[u]=1;
    for(auto v:e[u]){
        if(v==ff) continue;
        if(!dfn[v]){
            Tarjan(v,u);low[u]=std::min(low[u],lo[v]);
        }else if(vis[v]) low[u]=std::min(dfn[v],low[u]);
    }if(dfn[u]==low[u]){
        num++;
        while(!st.empty()){
            int v=st.top();
            st.pop();col[v]=num;
            vis[v]=0;sz[num]+=a[v];
            if(v==u) break;
        }
    }
}

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

导航