tarjan学习笔记

主要是因为我tarjan老是写岔,这里总结一下每一个tarjan的模版,只简单讲一下思想。

这个算法在左程云的视频里被称之为“扎口袋算法”,我认为很贴切,具体地,我们对一个图dfs,然后能得到一个dfs树,每一条边就可以分为树边,回边,弃边
树边,递归时下一个节点的dfn==0
回边,递归时下一个节点的dfn<当前的dfn
弃边,递归时下一个节点的dfn>当前的dfn

low数组,表示节点u以及他的子树向上走回边跳一次最远能跳到哪

以强联通分量为例,如果low[u]<dfn[u],就说明跳出了口袋,那么u必然在栈的某个强联通分量里。反之,则说明扎起了口袋,我们就可以直接弹栈,直到弹到u,这样就把口袋包起来了。

话不多说,直接看强联通分量的模版:

int head[N], nex[M << 1], to[M << 1];//链式前向星
int dfn[N], low[N], belong[N], siz[N],scc = 1 ,idx = 1;
stack<int>st;

void tarjan(int u) {
	st.push(u);
	dfn[u] = low[u] = idx++;
	for (int e = head[u];e >= 0;e = nex[e]) {
		int v = to[e];
		if (dfn[v] == 0) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (dfn[u] > dfn[v]) {//处理回边,弃边不处理
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		int v;
		do {
			v = st.top();
			st.pop();
			belong[v] = scc;
			siz[scc]++;
		} while (v != u);
		scc++;
	}
}
//如果你要建缩点后的图,直接枚举每一条边:if(belong[u]!=belong[v]){连边}

然后是点双联通分量和割点的代码:

int head[N], nex[M << 1], to[M << 1];//链式前向星
int dfn[N], low[N] ,idx = 1;
bool is_cut[N];
vector<vector<int>>vbcc;
stack<int>st;

void tarjan(int u,bool is_rt) {
	st.push(u);
	dfn[u] = low[u] = idx++;
	int son = 0;
	for (int e = head[u];e >= 0;e = nex[e]) {
		int v = to[e];
		if (dfn[v] == 0) {
			son++;
			tarjan(v, false);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u]) {//求割点
				if (!is_rt || son >= 2) {//处理根
					is_cut[u] = true;
				}
				//在树边这里求点双,跟强联通分量不一样
				vector<int>temp = { u };
				int pop;
				do {
					pop = st.top();
					st.pop();
					temp.push_back(pop);
				} while (pop != v);//不弹出u,因为后面u还属于某个vbcc
				vbcc.push_back(temp);
			}
		}
		else {//回边弃边一起处理,弃边更新不动的
			low[u] = min(low[u], dfn[v]);
		}
	}
}

最后是边双连通分量的code:

int head[N], nex[M << 1], to[M << 1];//链式前向星
int dfn[N], low[N], belong[N], siz[N], cnt_ebcc = 1,idx = 1;
bool is_cut[M];
vector<vector<int>>ebcc;
stack<int>st;

void tarjan(int u,bool come) {
	st.push(u);
	dfn[u] = low[u] = idx++;
	for (int e = head[u];e >= 0;e = nex[e]) {
		if ((e ^ 1) == come)continue;//要去除回去的,点双不用因为去不去都不影响,但是边双会有影响
		int v = to[e];
		if (dfn[v] == 0) {
			tarjan(v, e);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u]) {//严格大于,因为如果等于并不是割边了
				is_cut[e] = true;
			}
		}
		else {
			low[u] = min(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		int v;
		do {
			v = st.top();
			st.pop();
			belong[v] = cnt_ebcc;
			siz[cnt_ebcc]++;
		} while (v != u);
		cnt_ebcc++;
		//或者
		/*vector<int>temp;
		int v;
		do {
			v = st.top();
			st.pop();
			temp.push_back(v);
		} while (v != u);
		ebcc.push_back(temp);*/
	}
}

强联通分量是有向图的,点双和边双是无向图的。
强联通分量缩点之后是一个DAG,常常与拓扑排序+dp相结合,点双缩点后是一个圆方树,边双缩点之后是一棵树
tarjan算法共同点还是挺多的,把他们特殊的地方记一下就行了,不要搞混了

posted @ 2026-09-25 19:27  DK_orz  阅读(2)  评论(0)    收藏  举报