一:定义
定义:在有向图G中,如果两个顶点u、v互相连通,那么我们称这两个顶点是强连通的。
定义:在有向图G中,如果任意两个顶点互相连通,那么我们称这个图是一个强连通图。
定义:在有向图G中,如果有几个顶点互相连通,那么我们称这几个顶点组合起来的子图为极大强连通子图,也叫做强连通分量,注意:每一个顶点都可以被作为强连通分量。
性质:如果将有向图G中的强连通分量都缩为一个点,可以原图G就可以变成一个DAG(有向无环图)。
二:算法之 Tarjan
Tarjan算法是基于对图的DFS的算法,每个强连通分量为搜索树中的一颗子树。
搜索时,把当前搜索树中未处理的节点加入一个栈,回溯是可以判断栈顶到栈中的节点是否构成一个强连通分量。
在搜索时,我们会遇到四种边:
- 树枝边:搜索树上的边。
- 前向边:与DFS方向一致,从某个节点指向某个祖先的边。
- 后向边:与DFS方向相反。
- 横叉边:从每个节点指向搜索树中另一子树的边。
定义:dfn[u]为节点u的搜索次序编号,low[u]为u或者u的子树能回溯到的最早的栈中的dfn的值
那么,由定义可以得出:
- 如果(u,v)为树枝边,那么low[u]=min(low[u],low[v])。
- 如果(u,v)为后向边或者横叉边,那么low[u]=min(low[u],dfn[v]);
为什么要这样做?因为若u为强连通分量的跟,那么它的子孙的最高祖先就应该为它本身。
三:代码实现
还要流程吗?
直接发代码吧。
inline void tarjan(int u)//inline只是提速用的,不用理会。
{
dfn[u]=low[u]=++index1;//记录搜索次序编号
st[++top]=u;//将当前搜索到的压入队列;
vis[u]=1;//用过了。
for(register int i=head[u];i;i=edge[i].next)//前向星遍历图
{
int v=edge[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
int now;
if(low[u]==dfn[u])
{
++cnt;//下一个强连通分量;
do{
now=st[top--];//将队列顶端弹出
size[cnt]++;//记录这一个强连通分量所包含的节点
f[now]=cnt;//缩点,表示成同一个强连通分量
vis[now]=0;//取消使用
}while(u!=now);
}
}