1. 算法思想
如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。Tarjan算法是用来求有向图的强连通分量的。求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法。Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。(很容易理解,定义DFN(u)为节点u搜索的次序编号(时间戳), Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。DFN定义的是按照顺序可以到达的,例如v0是1 ,v1是2, v0可以到达v2。LOW定义的是u可以到达的子树,例如v1在栈中的子树可以到达的次序1,那么v2肯定可以到达v1。所以v1和v2是强连通。如果v0和v1在栈内之间还有v00,v11那么v0,v00,v11,v1之间也是强连通的)。
2. 实例
从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。
返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。
返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。
继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。
可以看出,采用的是深度优先的遍历,并且呢又给每个阶段增加了两个标记,判断是否是强连通。
3. 代码
4. #define M 5010//题目中可能的最大点数
5. int STACK[M],top=0;//Tarjan算法中的栈
6. bool InStack[M];//检查是否在栈中
7. int DFN[M];//深度优先搜索访问次序
8.
9. int Low[M];//能追溯到的最早的次序
10. int ComponentNumber=0;//有向图强连通分量个数
11. int Index=0;//索引号
12. vector<int> Edge[M];//邻接表表示
13. vector<int> Component[M];//获得强连通分量结果
14. int InComponent[M];//记录每个点在第几号强连通分量里
15. int ComponentDegree[M];//记录每个强连通分量的度
16.
17. void Tarjan(int i)
18. {
19. int j;
20. DFN[i]=Low[i]=Index++;
21. InStack[i]=true;STACK[++top]=i;
22. for (int e=0;e<Edge[i].size();e++)
23. {
24. j=Edge[i][e];
25. if (DFN[j]==-1)
26. {
27. Tarjan(j);
28. Low[i]=min(Low[i],Low[j]);
29. }
30. else
31. if (InStack[j]) Low[i]=min(Low[i],DFN[j]);
32. }
33. if (DFN[i]==Low[i])
34. {
35. ComponentNumber++;
36. do{
37. j=STACK[top--];
38. InStack[j]=false;
39. Component[ComponentNumber].
40. push_back(j);
41. InComponent[j]=ComponentNumber;
42. }
43. while (j!=i);
44. }
45. }