• zju 1119 SPF


       这个题让我做得有些纠结。将我的做题过程思路写出来。   这是一道深度优先搜索的题目,目的是来求出无线连通图的割点。首先,要明白什么是割点,割点:在一个连通图中,如果去掉了某个点和所有与这个点相连的边后,是图分成了两个部分,变成了一个不连通的图。那么这个点就是割点。还有,就是如何来求割点?

      搜索深度:如果节点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 }
  • 相关阅读:
    vim 命令详解
    Helloworld with c
    VS扩展开发框架
    Putty是一个专业的SSH连接客户端
    你的Java代码对JIT编译友好么?(转)
    java nio的一个严重BUG(转)
    JVM可支持的最大线程数(转)
    Java下获取可用CPU数
    java观察者模式(转)
    Android开发之控制Toast的开启与关闭
  • 原文地址:https://www.cnblogs.com/wangaohui/p/2776394.html
Copyright © 2020-2023  润新知