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\)树:

上图即一个有向图的\(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.两个点双最多只有一个公共点(即都有边与之相连的点);且这个点在这两个点双和它形成的子图中是割点

浙公网安备 33010602011771号