【算法】Tarjan算法 - 查找图的强连通分量、(割点)
[by_041]
Tarjan算法是基于由图建树加上DFS序(时间戳DFN)处理的一种算法
适用于both有向图和无向图
- 其理念图:
数据结构
- 对一个节点,其有树结点的数据结构,此外还需包含以下两个成员值;
- DFN序:对图DFS建树时产生的时间戳
- LOW值:对于每个点,能回溯到达的点中的最小DFN序(有向)/最多经过一条后向边能到达的点中的最小DFN序(无向)
理论基础
-
树枝边:DFS时经历的边(即是DFS搜索树上的边)
-
前向边:与DFS方向一致,从某结点指向其子孙结点的边(eg.(u,v):dfn(u)<dfn(v))
-
后向边:与DFS方向相反,从某结点指向其祖先结点的边(eg.(u,v):dfn(u)>dfn(v))
-
横叉边:从某结点指向搜索树上另一棵子树中某点的边
-
对于一条边:(u,v)
- 其为树枝边(v还未产生DFN序),u为v的父结点,则LOW(u)=Min{LOW(u),LOW(v)}
- 其为后向边或横向边(v已有DFN序),则LOW(u)=Min{LOW(u),DFN(v)}
-
当对一个结点u的搜索过程结束后,若DFN(u)=LOW(u),则以u为根结点的搜索子树上所有的结点(即u和在u之后进栈的结点)是一个强连通分量,可退栈。u为该强连通分量根,那么它子孙的最高祖先是他本身
实践过程
- 构造数据结构,申请变量空间
- Tarjan函数:
- 初始化:当首次搜索到u时,DFN(u)为结点u的搜索次序编号(时间戳)
- 堆栈:将u压入栈顶
- 更新:更新Low(u),遍历从u出发的点,对于一条边(u,v)参考上面的操作
- 优化:如果结点u的子树全部遍历后Low(u)=DFN(u),则将u和在u之后的所有结点弹出
- 继续搜索未被遍历的点
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
struct Tnode
{
int num;
int dfn;
vector<int>to;
};
int n,m;
vector<Tnode>tn;//tree nodes
int dfs=0;//use '++now_dfn' when need
stack<int>sta;
void init()
{
cin>>n;
tn.resize(n+1);
for(int i=1;i<=n;i++)
tn[i].num=i;
cin>>m;
//输入边构建图
for(int i=0,u,v;i<m;i++)
{
scanf("%d%d",&u,&v);
tn[u].to.push_back(v);
tn[v].to.push_back(u);
}
}
void tarjan(int u)
{
// - 初始化:当首次搜索到u时,DFN(u)为结点u的搜索次序编号(时间戳)
if(tn[u].dfn==0)
tn[u].dfn=++dfs;
// - 堆栈:将u压入栈顶
sta.push(u);
// - 更新:更新Low(u),遍历从u出发的点,对于一条边(u,v)参考上面的操作
for(int i=0,ii=tn[u].to.size();i<ii;i++)
{
;
}
// - 优化:如果结点u的子树全部遍历后Low(u)=DFN(u),则将u和在u之后的所有结点弹出
;
}
int main(void)
{
init();
//tarjan(rand()%n+1); //有时候防卡数据可以rand一下
for(int i=1;i<=n;i++)
if(tn[i].dfn==0)
tarjan(i);
return 0;
}