上一篇介绍过题目大意和kosaraju算法
本篇介绍tarjan算法。
引用一下讲解:
此算法以一个有向图作为输入,并按照所在的强连通分量给出其顶点集的一个划分。图中的每个结点只在一个强连通分量中出现,即使是在有些结点单独构成一个强连通分量的情况下(比如图中出现了树形结构或孤立结点)。
算法的基本思想如下:任选一结点开始进行深度优先搜索(若深度优先搜索结束后仍有未访问的结点,则再从中任选一点再次进行)。搜索过程中已访问的结点不再访问。搜索树的若干子树构成了图的强连通分量。
结点按照被访问的顺序存入栈中。从搜索树的子树返回至一个结点时,检查该结点是否是某一强连通分量的根结点(见下)并将其从栈中删除。如果某结点是强连通分量的根,则在它之前出栈且还不属于其他强连通分量的结点构成了该结点所在的强连通分量。
算法的关键在于如何判定某结点是否是强连通分量的根。注意“强连通分量的根”这一说法仅针对此算法,事实上强连通分量是没有特定的“根”的。在这里根结点指深度优先搜索时强连通分量中首个被访问的结点。
为找到根结点,我们给每个结点v一个深度优先搜索标号v.index,表示它是第几个被访问的结点。此外,每个结点v还有一个值v.lowlink,表示从v出发经有向边可到达的所有结点中最小的index。显然v.lowlink总是不大于v.index,且当从v出发经有向边不能到达其他结点时,这两个值相等。v.lowlink在深度优先搜索的过程中求得,v是强连通分量的根当且仅当v.lowlink = v.index。
algorithm tarjan is input: 图 G = (V, E) output: 以所在的强连通分量划分的顶点集 index := 0 S := empty // 置栈为空 for each v in V do if (v.index is undefined) strongconnect(v) end if function strongconnect(v) // 将未使用的最小index值作为结点v的index v.index := index v.lowlink := index index := index + 1 S.push(v) // 考虑v的后继结点 for each (v, w) in E do if (w.index is undefined) then // 后继结点w未访问,递归调用 strongconnect(w) v.lowlink := min(v.lowlink, w.lowlink) else if (w is in S) then // w已在栈S中,亦即在当前强连通分量中 v.lowlink := min(v.lowlink, w.index) end if // 若v是根则出栈,并求得一个强连通分量 if (v.lowlink = v.index) then start a new strongly connected component repeat w := S.pop() add w to current strongly connected component until (w = v) output the current strongly connected component end if end function
变量index是深度优先搜索的结点计数器。S是栈,初始为空,用于存储已经访问但未被判定属于任一强连通分量的结点。注意这并非一个一般深度优先搜索的栈,结点不是在以它为根的子树搜索完成后出栈,而是在整个强连通分量被找到时。
最外层循环用于查找未访问的结点,以保证所有结点最终都会被访问。strongconnect进行一次深度优先搜索,并找到结点v的后继结点构成的子图中所有的强连通分量。
当一个结点完成递归时,若它的lowlink仍等于index,那么它就是强连通分量的根。算法将在此结点之后入栈(包含此结点)且仍在栈中的结点出栈,并作为一个强连通分量输出。
代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define nMax 110 #define Min(x,y) (x<y?x:y) int map[nMax][nMax]; int belong[nMax]; int DFN[nMax],Low[nMax],stack[nMax]; bool inStack[nMax]; int n,index,cnt,top; int indegree[nMax],outdegree[nMax]; void tarjan(int u)//tarjan算法 { DFN[u] = Low[u] = ++index;//时间戳及low的初始化 stack[top ++] = u;//堆栈 inStack[u] = true;//是否已经在栈中 for (int i = 1; i <= n; ++ i) { if (map[u][i])//如果有边 { if (!DFN[i])//如果还没访问过 { tarjan(i); Low[u] = Min(Low[u], Low[i]); } else if (inStack[i])//如果已经在堆栈中 { Low[u] = Min(Low[u], DFN[i]); } } } if (DFN[u] == Low[u])//强连通分量 { cnt ++; while (1) { int tmp = stack[-- top]; belong[tmp] = cnt;//记录属于cnt强连通分量的所有点tmp inStack[tmp] = false;//并出栈 if (tmp == u)//到达根节点 { break; } } } } void output() { int inNum = 0,outNum = 0; memset(indegree, 0, sizeof(indegree)); memset(outdegree, 0, sizeof(outdegree)); for (int i = 1; i <= n; ++ i)//求强连通分量的出度和入度 { for (int j = 1; j <= n; ++ j) { if (map[i][j] && belong[i] != belong[j]) { indegree[belong[j]] ++; outdegree[belong[i]] ++; } } } for (int i = 1; i <= cnt; ++ i)//求出出度和入度为0的点的个数 { if (!indegree[i]) { inNum ++; } if (!outdegree[i]) { outNum ++; } } if (cnt == 1)//如果这个图强连通 { printf("1\n0\n"); } else//否则 printf("%d\n%d\n",inNum, inNum > outNum ? inNum : outNum); } int main() { while (scanf("%d", &n) != EOF) { memset(map, 0, sizeof(map)); memset(inStack, false, sizeof(inStack)); memset(DFN, 0, sizeof(DFN)); index = cnt = top = 0; for (int i = 1; i <= n; ++ i) { int v; while (scanf("%d", &v) && v) { map[i][v] = 1; } } for (int i = 1; i <= n; ++ i) { if (!DFN[i]) { tarjan(i);//tarjan算法 } } output(); } return 0; }
虽然看了好多文章,理解了好多遍,但是在下还有一点不明白,忘大神指点一二:
就是求Low[u]的时候,为何不全是Min(Low[u], Low[i])为何还有在不在栈中之分???????????求解释。
最主要的是将Min(Low[u], DFN[i])改为Min(Low[u], Low[i])程序也能过,我相信这个地方肯定是有用的,也许我还没学习到吧。。。