这个题让我做得有些纠结。将我的做题过程思路写出来。 这是一道深度优先搜索的题目,目的是来求出无线连通图的割点。首先,要明白什么是割点,割点:在一个连通图中,如果去掉了某个点和所有与这个点相连的边后,是图分成了两个部分,变成了一个不连通的图。那么这个点就是割点。还有,就是如何来求割点?
搜索深度:如果节点k的搜索深度为j,则节点k为第j个搜索到的节点。
若有k的儿子为i,我们定义AnceDeep[i]为与结点i相连接的所有节点最小的搜索深度,deep[k]为k的搜索深度(时间戳),那么k为割点当且仅当k满足(1)(2)中的一个:
(1) 若k为深搜树的根Root,当且仅当k的儿子数(分支数)>=2时k为割点;
(2) 若k为搜索树的中间结点(即k既不为根也不为叶),那么k必然有father和son,若AnceDeep[son]>= deep[k],则k必然为割点。
这样看起来并不好懂,我来引用一个学长写的文章来解释一下上面的(1)、(2)。 根据割点的定义,割点将图分成了两个部分,这个两个部分是靠割点来连通的,如果没有割点存在,那么图将不连通。所以,换句话说,我们要从第一部分的点进入第二部分的话,我们必须经过这个割点。然后接着,我们再考虑图的dfs的时候。由于割点是通往第二部分的门户,那么当我们从第一部分开始对图进行dfs的时候,如果访问了割点,那么再一次回退到割点的时候,必须是等第二部分的所有的点都访问完了之后才可以,否则就相当于“大门”被关闭了。第二部分的点就无法再次访问到了。因为dfs遇到访问过的点就立刻返回。
对dfs来说,她遍历的最后是生成一棵树,其中树有一些叶子节点,形成叶子节点的原因是,他们能接触到的点都已经在他们之前访问过了。所以到了他们之后dfs不再继续递归了。联系到上面,我们可以看出dfs遍历所形成的树中的图的第二部分的点,与他们相连的只能是图中第二部分的点,或者是割点。所以我们如果对dfs访问的点按顺序来进行标号的话,那么第二部分的点的被访问序号一定要比割点的访问序号要大。
所以我们就想到了利用这个特性,用dfs求割点的方法:
首先从任意点开始,进行dfs,每递归到一个点,如果这点是没有被访问的,那么用dfs的序号给这个点的n1赋值,然后还要接着去访问与这个点相邻的其他的点,把其中最小的返回值返回,作为这个点的n2值。如果相连的点是被访问过的,那么直接把该店的n1作为返回值。然后,在访问的过程中,还要记录下序边,下序边是指,在生成的树中,由父节点指子节点的边。是有方向的。当dfs结束后。对于每一个点,从它的下序点中找到最小的n2的值,若果n1<=n2,那么就说明,他的下序点中,没有比它先被访问的点,那就说明这个点是一个割点。若果n1>n2,那么这个点不是割点。
(但要注意,这个算法有一个默认的前提假设,就是说,dfs的起始点是被割点分成的两个部分中的点,但是如果这个起始点开始就是割点。则最后的比较是找不到割点的,所以最后我们还需要一个单独对这个起始点的判断,用最简单的dfs或者bfs即可)。
解释清楚这些之后,然后就是写代码的过程了。我需要知道节点的数量N;需要用一个邻接矩阵M[][]来表示点与点之间的关系,并且在输入的过程中初始化;需要一个数组dfn[]来表示各个点的搜索深度,需要一个变量lbl随着访问节点数量的增加而自加并赋值给dfn[];需要一个数组low[]来表示节点所有子孙的祖先中最小的搜索深度;需要有一个数组ans[]来表示所有节点是不是割点,如果是割点,去掉这个割点之后分为几个连通分量了呢?这也可以用ans[]来表示。
那么该如何求N呢?在输入数据的过程中,最大的值就是N。如何求dfn[]呢?如果一个节点没有被访问到,那么这个节点的dfn值就是上一个访问的节点的dfn的值加1。如何来求low[r]呢?首先,对low[r]赋初值为dfn[r],在dfs的过程中,根据节点r的每个孩子节点的low[]值来更新自己的low[]值。low[r] = low[r]<low[i] ? low[r]:low[i];
大概就是这些,这个对我来说,真心不太好编。。唉!!我是一颗大白菜啊。。欢迎交流
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 const int MAX_VERTEX = 1024; // 图顶点个数最大值 6 int T, v1, v2; // 分别为测试数据组数和一条边的两个端点 7 int N, M[MAX_VERTEX][MAX_VERTEX]; // 分别为图的顶点个数和图的邻接矩阵 8 int dfn[MAX_VERTEX], lbl; // 各顶点的dfn值:在DFS期间,根据顶点v被首次访问的次序指定整数dfn(v),即若dfn(v)=i,则v是第i个首次被访问的顶点,dfn(v)称为v的深度优先搜索序数 9 int low[MAX_VERTEX]; // 各顶点的low函数值,表示顶点祖先中最小的dfn 10 int ans[MAX_VERTEX]; // 结果,即ans[i]>0时说明顶点i是割点,且ans[i]+1为去掉顶点i后连通子网络的个数 11 12 // DFS算法求割点函数 13 void SolveByDfs(int r); 14 15 int main() 16 { 17 T = 0; // 测试数据组数 18 while (cin>>v1 && v1!=0) // 第一个端点不为零时,继续 19 { 20 cin >> v2; 21 22 // 初始化 23 N = v1>v2 ? v1:v2; // 图的顶点个数为v1和v2的最大值 24 memset(M, 0, sizeof(M)); // 图的邻接矩阵初始化为0 25 memset(dfn, 0, sizeof(dfn)); 26 memset(low, 0, sizeof(low)); 27 memset(ans, 0, sizeof(ans)); 28 lbl = 0; 29 30 // 读入数据,求解并输出结果 31 M[v1][v2] = M[v2][v1] = 1; 32 while (cin>>v1 && v1!=0) 33 { 34 cin >> v2; 35 N = N>v1 ? N:v1; 36 N = N>v2 ? N:v2; 37 M[v1][v2] = M[v2][v1] = 1; 38 } 39 SolveByDfs(1); 40 --ans[1]; 41 ++T; 42 if (T > 1) 43 { 44 cout << endl; 45 } 46 cout << "Network #" << T << endl; 47 int ok = 1; 48 for (int i=1; i<=N; ++i) 49 { 50 if (ans[i] > 0) 51 { 52 cout << " SPF node " << i << " leaves " << ans[i] + 1 << " subnets" << endl; 53 ok = 0; 54 } 55 } 56 if (ok == 1) 57 { 58 cout << " No SPF nodes" << endl; 59 } 60 } 61 62 return 0; 63 } 64 65 void SolveByDfs(int r) 66 { 67 ++lbl; 68 low[r] = dfn[r] = lbl; // 求得顶点r的dfn值并为low(r)赋初值为dfn(r) 69 for (int i=1; i<=N; ++i) 70 { 71 if (M[r][i] != 0) // 顶点i是顶点r的邻接点 72 { 73 if (M[r][i] == 1) // 边ri未检查 74 { 75 M[r][i] = M[i][r] = 2; // 标记边ri已检查 76 if (dfn[i] == 0) // 前向边,若i还未被访问过 77 { 78 SolveByDfs(i); 79 low[r] = low[r]<low[i] ? low[r]:low[i]; 80 if (low[i] >= dfn[r]) 81 { 82 ++ans[r]; 83 } 84 } 85 else // 后向边,i已经被访问过 86 { 87 low[r] = low[r]<dfn[i] ? low[r] : dfn[i]; 88 } 89 } 90 } 91 } 92 }