Tarjan算法作为一类算法,不同的应用写法不同,需要注意。
Tarjan求割点/桥(无向图):
low[u]表示点u及其后代通过返祖边可以访问到的最小时间戳,所以不能回到父节点去。
割点有两种情况:
(1)该点不是根节点,且该点的孩子结点不通过该点回不到祖先节点
(2)根节点显然不能通过上一条判断,所以需要对根节点进行特判,对于根节点,只要他有两个及以上的孩子结点,他就是一个割点
int n,m;
set<int>ans;
vector<int>vc[22222];
int tot,dfn[22222],low[22222];//时间戳以及连通分量中的最小时间戳
void tarjan(int x,int fx,int root)//当前结点,根节点,父结点
{
dfn[x]=low[x]=++tot;
int child=0;
for(auto v:vc[x])
{
if(v==fx) continue;
++child;
if(dfn[v]==0)
{
tarjan(v,x,root);
low[x]=min(low[x],low[v]);
if(x!=root&&low[v]>=dfn[x])//x不是根节点且v不通过x到达不了祖先
ans.insert(x);
if(x==root&&child>=2) //特判根节点//x是根节点且有两个以上的孩子
ans.insert(x);
}
else low[x]=min(low[x],dfn[v]);//此时x--->v即为一条返祖边
}
}
signed main()
{
tot=0; memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));
cin>>n>>m;
rpp(i,m)
{
int x,y;cin>>x>>y;
vc[x].push_back(y),vc[y].push_back(x);
}
rpp(i,n) if(dfn[i]==0) tarjan(i,i,i);
cout<<ans.size()<<endl;
for(auto it=ans.begin();it!=ans.end();++it) cout<<*it<<" ";
return 0;
}
Tarjan求强连通(有向图):
low[u]表示可以到达点u的结点的最小时间戳(即u还在栈中的最小祖先),所以需要回到父亲节点去。
其中有点需要注意:
如果访问到标记过时间戳的结点,需要先判断该点是否还在栈中(因为只要这点进过tarjan就说明一定已经把它所处的强连通分量找过,并且找全,但你当前所处理的点是没进过tarjan的,所以一定不属于一个强连通分量,如图:)
int n;
vector<int>mp[111];
int tot,dfn[111],low[111];//时间戳
int cnt,id[111];//为找到的连通分量编号
int inst[111];//是否还在栈中
stack<int>st;
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
st.push(x);
inst[x]=1;
for(auto v:mp[x])
{
if(!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);//如果lowv小于lowx说明
}
else if(inst[v])//还在栈中,说明v的强连通分量还没有找完
low[x]=min(low[x],dfn[v]);
}
if(dfn[x]==low[x])
{
int v=st.top();
++cnt;
do
{
v=st.top();st.pop();
id[v]=cnt,inst[v]=0;
} while (v!=x);
}
}