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算法共同点还是挺多的,把他们特殊的地方记一下就行了,不要搞混了

浙公网安备 33010602011771号