• 理解tarjan算法求强连通分量


    tarjan算法的基本框架就是dfs,其基本原理是有向图至少存在一棵深搜子树,其结点集合构成一个强连通分量,这是显然的,因为必定有一个强连通分量最后被dfs,这个强连通分量的结点构成深搜树的一棵子树。

    有了以上结论后,求强连通分量就有思路了,我们在每棵子树深搜完成后判断这棵子树是否构成强连通分量即可,关键在于如何判断一棵子树是否构成强连通分量。

    注意到最先搜索完的子树是那些叶子结点,要判断叶子结点是否构成强连通分量很简单,若存在叶子结点与其祖先结点的连边,则该叶子结点不构成强连通分量,否则构成强连通分量。tarjan算法用pre[V]数组和low[V]数组来判断子树是否构成强连通分量,pre[v]保存结点v在先序遍历中的访问顺序,以下统称深度优先数,low[v]保存从v出发能到达的所有结点的最小深度优先数,用叶子结点来解释,v为叶子结点,
    当low[v]<pre[v]时,表明存在v到其祖先结点的连边,v不构成强连通分量;
    当low[v]==pre[v]时,表明不存在v到其祖先结点的连边,v构成强连通分量。

    若深搜树中存在一个叶子结点构成强连通分量,则通过以上判断可以求出,也即求出了第一个强连通分量;
    若深搜树中所有叶子结点都不构成强连通分量,此时叶子结点必定与其父结点同在一个强连通分量中,可以将其缩到父结点中(具体操作是更新父结点的low数组),原来的父结点就变成了“大”叶子结点,还是通过low[v]=?pre[v]来判断这个“大”叶子结点是否构成强连通分量,一直这样下去,叶子结点将越来越大,直到求出第一个强连通分量为止。

    在求出第一个强连通分量后,我们将其包含的结点在原图中删除,问题又转化成了求第一个强连通分量的问题,理解还是一样的,以此类推,直到所有的强连通分量均被求出,此时算法结束。

    参考代码:
    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #define MIN(a,b) ((a)<(b)?(a):(b))
    using namespace std;
    #define N 100
    int pre[N],low[N],id[N],s[N],t,cnt,top,n,m;
    vector<int> g[N];
    //初始化
    void init()
    {
      t=cnt=top=0;
      memset(pre,0xff,sizeof(pre));
    }
    //tarjan算法主体
    void dfs(int u)
    {
      int min=pre[u]=low[u]=t++;
      int i,v;
      s[top++]=u;
      for(i=0;i<g[u].size();i++)
      {
        v=g[u][i];
        if(pre[v]==-1)  dfs(v);
        min=MIN(min,low[v]);
      }
      if(min<low[u])
      {
        low[u]=min;
        return;
      }
      do{
        id[v=s[--top]]=cnt;
        low[v]=n;
      }while(v!=u);
      cnt++;
    }
    //调用测试
    int main()
    {
      freopen("in.txt","r",stdin);
      freopen("out.txt","w",stdout);

      int u,v,i,kase=0;
      while(~scanf("%d%d",&n,&m))
      {
        for(i=0;i<n;i++)  g[i].clear();
        for(i=0;i<m;i++)
        {
          scanf("%d%d",&u,&v);
          g[u].push_back(v);
        }
        init();
        for(i=0;i<n;i++)
        {
          if(pre[i]==-1)  dfs(i);
        }
       
        for(i=0;i<cnt;i++)  g[i].clear();
        for(i=0;i<n;i++)
        {
          g[id[i]].push_back(i);
        }
       
        printf("Case %d: ",++kase);
        for(i=0;i<cnt;i++)
        {
          printf("{ ");
         
          for(int j=0;j<g[i].size();j++)
          {
            printf("%d ",g[i][j]);
          }
         
          printf("} ");
        }
        printf("\n");
      }
      return 0;
    }

  • 相关阅读:
    批量导入
    循环语句
    判断循环常见
    常见C运算符
    oc将字符串中单词按照出现次数(次数都不一样)降序排序,排序之后单词只出现一次,源字符串中单词用下划线连接,生成字符串也用下滑线连接
    把字符串中的字母大小写反转OC
    查找子串出现次数OC
    现有一个数组NSMutableArray, 数组有若干个NSString的元素,进行选择法排序
    终端的一些命令
    编程语言名字来历
  • 原文地址:https://www.cnblogs.com/algorithms/p/2580288.html
Copyright © 2020-2023  润新知