Tarjan算法:

  这是SCC问题的第一个算法,由Tarjan于1972年提出。算法仍然借助DFS,但它并不依靠遍历顺序来把不同的SCC分离到不同的DFS树中,而是让多个SCC并存于同一个DFS树中,用某种手段把他们分开。考虑一个强分量C,设其中第一个被发现的点为x,由白路径定理,C中其他点都是x的后代。我们希望在x访问完成时立刻输出C。(注意这里是一个严格的数学描述)。这样,就可以在同一棵DFS树中区分开所有的SCC了。因此问题的关键是:如何判断一个点是否为SCC中最先被发现的点。
 
    如图。7.1.2 有向图及其连通性_第1张图片假设我们正在判断u是否为某SCC中第一个被发现的节点。如果我们发现从u的儿子出发可以到达u的祖先w,显然u\v\w在同一个SCC中,因此u不是该SCC第一个被发现的节点。如果从v出现最多只能到u,那么u是该SCC中第一个被发现的节点(也许有同学会问,若所有子节点不能到达u本身,何以能说明u是和子树强联通的?其实由于DFS的特点,若这样的情况出现,实际上在u的子树上已经完成了一个强分量的寻找,u此时是只到它本身的“第一个”被发现节点,原书的描述是严格和归纳的)。这样,问题转化为求:一个点u最远能到达的祖先的d值。注意这里的“到达”可以通过后向边或交叉边,但是前提是只能通过栈里面的点而不是已经确定SCC编号的其他点。图中实线表示一条边,虚线表示一条或多条边。


      定义low[u]为u及其后代能追溯到的最早祖先v的发现时间戳pre[v],我们可以在计算low函数的同时完成SCC的计算,low函数的递推方法如下:
      利用全局栈_sta保存当前SCC中的节点(注意栈中节点形成树而不一定是链),cnt为开发当前点u的时间戳,scnt为强分量编号器,id[]为强分量编号数组。

      原始的Tarjan算法递推方式为:如果 pre[w]<pre[u]且w在栈中,则low[u]=min{pre[w],low[u]},注意后一个限制是为了保证w不是在另一个已经发现的SCC中。下面的代码更简洁,在标记强分量后,只需要将low[w]设为最大值,表明它不再是任何点的祖先,那么w就不会被其他强分量吸收了,想想为什么。

 1 void  dfs - scc( int  u) {
 2    int w,min;
 3    min=low[u]=pre[u]=cnt++;
 4     /**//* 初始化时间戳,low值,子节点最小祖先 为当前时间戳 */
 5     _sta.push(u);
 6     
 7     for each (u,w){
 8         if(pre[w]==-1) dfs-scc(w);
 9         /**//* 未开发节点 */
10         if( low[w]<min ) min=low[w];
11         /**//* 求出u所有儿子i最远能到达的祖先 */
12     }

13     
14     if(min<low[u]){ low[u]=min; return ; }
15     /**//* 所有的儿子能到达的最远祖先是u的祖先,因此u不是SCC
16         第一个被发现的节点,通过子节点,u应能到达这样的第一个节点 */

17     do{
18         w=_sta.pop(w);
19         id[w]=scant;
20         low[w]=0x7fffffff/**//* 锁定low,保证w不会被其他强分量吸收 */
21     }
while(w!=u)
22     /**//* 此时,u的所有子节点必能且最远仅能到达u,他们沟通构成一个SCC */
23     scant++;
24}