昨天学到了一个新的算法tarjan算法,感觉最近都没有怎么学习了。。。(最近有个感悟啊,就是学习一定的通过实践来进步的。 现在才明白为什么高中的时候老师强调一定要刷题,当然刷完题目之后的总结也非常地重要!
这个tarjan算法用来求强联通分量,在网上看了几篇blog,然后做了一个题目,感觉这个算法很nice啊。。。
如果没有学这个算法, 我肯定会想直接dfs吧orz... dfs看看是不是每个点能到达连通分量的其他点,好像这样非常麻烦啊,还要记录这个点从哪里来的。。。这样一想,好像直接dfs我做不来啊。。。(orz 太菜了。
那就看看tarjan打野的算法吧。。。
这个算法用到了两个数组(low 和 dfn数组)和 数据结构栈 还有一个标记数组stk[]用来记录一个点是否在栈中,个人认为这两个数组才是这个算法的关键之处! low用来记录当前连通分量的子树的下标(子树的下标最小,因为我们也是从下标小的点开始dfs 的),dfn用来记录当前这个点被访问的时间,所以在后面的操作中,我们不要去修改dfn的值(因为一个点的被访问时间的确定的啊),我们要修改的是low的值。
算法的步骤大概如下:
step1:初始化low,dfn, low[u]=dfn[u]=++time;
step2:将u压栈并且标记为在栈中 stk[u]=1;
step3:一个for循环找与u相邻的点v,这里分两个情况 (这里就是dfs的模拟)
1)如果v未被访问,那么就去dfs(v),如果存在环的话,会导致 low[v]被更新为子树的根的dfn值,所以dfs结束的时候,我们更新一下low[u]=min(low[v], low[u]),有人会问了,如果没有环呢?没有环就更好办了,没有环的话,v是从u搜索过来的 low[v]的初始值大于low[v]的初始值,所以low[u]=min(low[u], low[v])并不变。
2)如果v已经被访问过,就看看它在不在当前的栈中,如果在,说明v和u同属于一棵子树(那我们更新一下low[v]=min(dfn[u], low[v]) )
step4:如果一个点的邻边都已经搜索完了,那就从step3跳出来了,这个时候判断一个这个点u的low[u]和dfn[u]是否相等,相等的话说明它是一个强连通分量的根,可以证明一下:如果它不是根的话,那么u的low在step3就已经被更新成了根的dfn,而根的dfn值肯定比low[u]更小的,所以low[u]肯定被更新了且不等于dfn[u]。当low[u]和dfn[u]相等的时候,我们就把压在栈中的同属于一个连通分量的点全部pop,并设stk[u]=0;(表示当前元素已经不在栈中了)。。。因为我们是从节点下标小的点开始dfs的,所以一个联通分量的根节点肯定是节点小下标的点,这样就可以保证pop操作能成功将所有联通分量的点pop出来!