zoukankan      html  css  js  c++  java
  • 强连通分量2Tarjan算法 POJ1236

       这两天学习了一下求强连通分量的另一个算法Tarjan算法,相比于之前的Kosaraju算法,Tarjan算法只需要进行一次深搜即可,两个算法的时间复杂度都是O(n+m);

       算法基本思路是:从任一顶点开始进行深搜,强连通分量是一棵搜索子树,在搜索是将每一个未处理的点弹入栈中,然后在回溯时判断以该节点为根的搜索子树是否为一个强连通分量,若是,则将栈顶至该节点的元素弹出,这些元素即构成一个强连通分支,然后继续回溯,遇到强连通分量则弹出,最终便可获得有强连通分量。

      算法的关键在于如何判断以u为根的搜索子树是否为一个强连通分量,算法用一个DFN[u]记录节点u在深搜时的次序,并用一个Low[u]记录u所能回溯到的栈中节点的次序号,判断是否为强连通分量只需判断DFN[u]是否等于Low[u],Low[u]<=DFN[u],如果Low[u] != DFN[u](即小于),则说明u可以通过其子节点连接到栈中更早的祖先,而该祖先又能达到u,所以形成一个环,所以该环应是强连通分支的一部分,所以u必然不是该强连通分支的根节点。

       对于u进行搜索时,初始化Low[u] = DFN[u],然后对其子节点进行深搜并不断更新Low[u];以下分三种情况:

      1.若子节点v未遍历过,则继续Tarjan(v)深搜过程,完成后更新Low[u] = min{low[u], low[v]};  说明u通过其子节点v可以达到u的某一个栈中的祖先,此时Low[u] != DFN[u,]所以u必然不是强连通分支的根节点

      2.若子节点v已遍历过且v在栈中,那么Low[u] = min{Low[u], DFN[v]};

      3.若子节点v已遍历过且不在栈中,不作处理;

      具体的过程课参考,博主说得很清晰:https://www.byvoid.com/blog/scc-tarjan/

      以下是我C语言实现代码,用的是邻接矩阵(也可用其他实现):

    #include <stdio.h>
    #include <cstring>
    const int MAXN = 1010;
    bool map[MAXN][MAXN];
    int DFN[MAXN], Low[MAXN];
    int Stack[MAXN], top, index;
    bool ins[MAXN], vis[MAXN];
    int n;
    int min(int x, int y) {
      if (x < y) return x;
      return y;
    }
    
    void Tarjan(int u) {
      DFN[u] = Low[u] = index++;  
      vis[u] = 1;    //标记为遍历过 
      Stack[top++] = u;  //将未处理的点弹入栈中 
      ins[u] = 1;   //将该点标记为在栈中 
      //printf("%d\n", u);
      for (int i = 0; i < n; i++) {
          if (map[u][i] && !vis[i]) {     //子节点未遍历过,进行tarjan,并更新u的Low 
             Tarjan(i);
             Low[u] = min(Low[u], Low[i]);
          } else if (map[u][i] && ins[i])    //子节点遍历过且在栈中,更新Low 
             Low[u] = min(Low[u], DFN[i]);
      }
     // printf("%d %d %d\n", u, DFN[u], Low[u]);
      if (DFN[u] == Low[u]) {    //若相等,说明u为强连通分量的根节点,将u及u以上的元素弹出栈 
          while (Stack[top-1] != u) {
            printf("%d ", Stack[--top]);  //将同一个强连通分量的元素在同一行输出 
            ins[Stack[top]] = 0;  //标记为不在栈中 
          }
          printf("%d\n", Stack[--top]);
          ins[Stack[top]] = 0;
      }
    }
    
    int main()
    {
      int i, j, m, u, v;
      while (scanf("%d%d", &n, &m) != EOF) {
          memset(map, 0, sizeof(map));
          memset(ins, 0, sizeof(ins));
          memset(vis, 0, sizeof(vis));
          for (i = 0; i < m; i++) {
            scanf("%d%d", &u, &v);
            map[u][v] = 1;
          }
          index = 0; top = 0; 
          for (i = 0; i < n; i++) {
            if (!vis[i]) {
              Tarjan(i);
            }
          }
      }
      return 0;
    }

      接下来是POJ1236,

    Network of Schools
    Time Limit: 1000MS   Memory Limit: 10000K
    Total Submissions: 9279   Accepted: 3689

    Description

    A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B 
    You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school. 

    Input

    The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

    Output

    Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

    Sample Input

    5
    2 4 3 0
    4 5 0
    0
    0
    1 0
    

    Sample Output

    1
    2

    题目要求输出两个任务的结果,第一个任务是求最少需要给几个学校分批软件就可以让所有学校均获得,其实只要算出每个强连通分支,然后入度为0的强连通分支数即可;
    第二个任务其实是给图加边过程,需要加上几条边便可使整个图强连通,只需计算入度为0的分支数cnt1和出度为0的分支数cnt2,输出max{cnt1, cnt2}; 但严格的证明我还是不太懂,就只是这么感觉(ORZ,希望知道的能指导一下,谢谢~~)
    代码如下(用tarjan实现):

    #include <stdio.h>
    #include <vector>
    #include <string.h>
    using namespace std; 
    const int MAXN = 101;
    vector<int>vec[MAXN];
    vector<int>pre[MAXN];
    bool indre[MAXN], outdre[MAXN];
    int DFN[MAXN], Low[MAXN], Stack[MAXN], instack[MAXN], belong[MAXN];
    int cnt, n, top, index;
    
    int min(int x, int y) {
      if (x < y) return x;
      return y;
    }
    
    void Tarjan(int u) {
      DFN[u] = Low[u] = index++;
      Stack[top++] = u;
      instack[u] = 1;
      for (int j = 0; j < vec[u].size(); j++) {
          int v = vec[u][j];
          if (!DFN[v]) {
            Tarjan(v);
            Low[u] = min(Low[u], Low[v]);
          } else if(instack[v])
            Low[u] = min(Low[u], DFN[v]);
      }
      if (DFN[u] == Low[u]) {
          cnt++;
          do {
            belong[Stack[--top]] = cnt;
            instack[Stack[top]] = 0;
          }while(Stack[top] != u);
      }
    }
    
    int main()
    {
      int i, j, n, v;
      while (scanf("%d", &n) != EOF) {
          for (i = 1; i <= n; i++) {
            while (scanf("%d", &v) && v != 0) {
                vec[i].push_back(v);
                pre[v].push_back(i);
            }
          }
          memset(DFN, 0, sizeof (DFN));
          memset(instack, 0, sizeof(instack));
          top = 0; index = 1; cnt = 0;
          for (i = 1; i <= n; i++) {
            if (!DFN[i])
              Tarjan(i);
          }
          memset(indre, 0, sizeof (indre));
          memset(outdre, 0, sizeof(outdre));
          for (i = 1; i <= n; i++) {
            for (j = 0; j < vec[i].size(); ++j) {
              v = vec[i][j];
            if (belong[i] != belong[v]) {
              outdre[belong[i]] = 1;     
              break;
            }
            }
            for (j = 0; j < pre[i].size(); ++j) {
                v = pre[i][j];
                if (belong[i] != belong[v]) {
                  indre[belong[i]] = 1; break;
                }
            }
          }
          int cnt1 = 0, cnt2 = 0;
          for (i = 1; i <= cnt; i++) {
            if (!outdre[i])  cnt1++;
            if (!indre[i]) cnt2++;
          }
          printf("%d\n", cnt2);
          if (cnt == 1) printf("0\n");
          else printf("%d\n", cnt1>cnt2? cnt1 : cnt2);
          for (i = 0; i <= n; i++) {
            vec[i].clear();
            pre[i].clear();
          }
      }
      return 0;
    }
    View Code

    学算法之路任重而道远~~。






  • 相关阅读:
    CodeSmith注册错误的解决方法
    我是“坚守者”还是"背叛者"?
    拿什么留住你,我的程序员
    去除HTML代码得函数
    页面之间传递参数得几种方法
    nhibernate source code analyzed (abstract classes in nhibernate2.0)
    Web 2.0时代RSS的.Net实现
    Visual Studio.net 2003安装提示重启问题
    开放思路,综合考虑,心胸开阔,做一个合格的项目经理
    了解实际开发中 Hashtable 的特性原理 .NET, JAVA, PHP
  • 原文地址:https://www.cnblogs.com/Patrickcxt/p/3222841.html
Copyright © 2011-2022 走看看