一些概念
割点
在无向图中,如果有一个结点x,当我们去掉x结点已经与其相连的边后,该图被分成若干个不相连子图(或者说这个图不联通了),那x即为割点
桥
又叫割边, 如果有一条边edge,我们去掉这条边后,图被分成两个不相连的子图,那这条边就叫做桥
强连通分量
有向图的极大强连通子图
Tarjan求强连通分量
经典tarjan = _ =,一直学的不大明白,加深一下印象
dfn[x] / low[x]
这俩变量是理解tarjan算法比较重要的东西
dfn[x]是时间戳,这个时间戳的意思就是dfs时访问的顺序
low[x]是x以及x的子树能够追溯到的最早的栈中节点的dfn
(low[]这个概念弄的我一直比较蒙,其实通俗点来说,从x出发,延伸到后面又回到了自己,那肯定是存在一个环的,这个环上的点都是强连通的,add:强连通分量是极大强连通子图
假设从x出发,那我们怎么知道退到x时,它下面的有所有和它构成强连通分量的点呢
开个栈
:update 5/15 为什么要开栈?
根据这个dfs过程,好像回到打过标记的点就构成强连通分量了,那么为什么还要开栈呢?
随手捏了一个,大概是因为这样,所以要开栈。
并且,在回溯的时候,如果长这样:
我们这里回溯的时候,因为dfn[x] == low[x],所以每个点都是一个强连通,正常弹出。
但如果长这样
我们会在回溯4 3时不执行弹出栈的操作,当回溯到2时,low[2] == dfn[2],这时,弹出2及其顶部的元素3 ,4,而2,3,4构成一个强连通分量,(类似于2是该强连通分量的根。
这下代码就会好理解许多
如果u下面要遍历的点v已经在栈中了,那low[u] = dfn[v]
如果不在栈中,low[u] = min(low[u],low[v] )
dfn[x] = low[x]说明x是强连通分量的根
代码
void tarjan(int u){
q.push(u);
in[u] = 1;
dfn[u] = low[u] = ++Time;//时间戳
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].v;
if(dfn[v] == 0){
tarjan(v);//没遍历过就继续
low[u] = min(low[u], low[v]);//回溯的时候更新最小值
}
else if(instack[v] == 1){
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u] == low[u]){
while(!q.empty()){
int t = q.top();
q.pop();
instack[t] = 0;
fa[t] = u;
if(t == u)break;
}
}
}
这里染了一下色,令强连通分量中的结点fa都为强连通分量的根结点
割点
u不是树根:如果当前结点为u,接下来的结点为v,如果dfn[u] <= low[v],说明了v这个点回溯不到u之前的点,那么去掉u结点,u之前的结点与v将不再联通,u即割点
u是树根:如果fa[u] == u,说明u是根,那就需要特判了,我们记录son[u]的数量,son[u] >= 2,说明u是割点,这里我本来有疑问,如果它的儿子存在联通怎么办
我们是递归去求的,递归回来之后再去更新信息,如果和上图一样,根结点递归下去之后dfn[左儿子]和dfn[右儿子]已经打上标记了,son[root] == 1
桥
思路其实和割点蛮像的,并且没有什么需要特判的地方
在无向图中,对于边i,其两端点为u,v,如果dfn[u] < low[v],说明边i为桥
这里式子和割点很像,但是不能取等,因为如果dfn[u] == low[v],说明v能回溯到u,该边i就不是v通到u的唯一线路了。