SCC(强连通分量)

1.定义:

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(SC---strongly connected)。

有向图中的极大强连通子图,成为强连通分量(SCC---strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两个强连通分量。

2.tarjan算法-----九野讲解

        tarjan算法的基础是DFS。我们准备两个数组Low和Dfn。Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的 Dfn值,Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图(无需回溯)和 对栈的操作,我们就可以得到该有向图的强连通分量。

  1. 数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
  2. 堆栈:每搜索到一个点,将它压入栈顶。
  3. 当点p有与点p’相连时,如果此时(时间 = dfn[p]时)p’不在栈中,p的low值为两点的low值中较小的一个。
  4. 当点p有与点p’相连时,如果此时(时间 = dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。
  5. 每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
  6. 继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
///其时间复杂度也是O(N+M)
#include <stdio.h>
#include <string.h>
#include <vector>
#include <stack>
#include <iostream>
using namespace std;
#define N 10005            /// 题目中可能的最大点数
stack<int>sta;            /// 存储已遍历的结点
vector<int>gra[N];        /// 邻接表表示图
int dfn[N];        /// 深度优先搜索访问次序
int low[N];        /// 能追溯到的最早的次序
int InStack[N];
/// 检查是否在栈中(2:在栈中,1:已访问,且不在栈中,0:不在)
vector<int> Component[N]; /// 获得强连通分量结果
int InComponent[N];          /// 记录每个点在第几号强连通分量里
int Index,ComponentNumber;/// 索引号,强连通分量个数
int n, m;              /// 点数,边数

void init()///清空容器,数组
{
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(InStack, 0, sizeof(InStack));
    Index = ComponentNumber = 0;
    for (int i = 1; i <= n; ++ i)
    {
        gra[i].clear();
        Component[i].clear();
    }
    while(!sta.empty())
        sta.pop();
}

void tarjan(int u)
{
    InStack[u] = 2;
    low[u] = dfn[u] = ++ Index;
    sta.push(u);///寻找u所在的强连通分量
    for (int i = 0; i < gra[u].size(); ++ i)
    {
        int t = gra[u][i];
        if (dfn[t] == 0)///不在的继续递归
        {
            tarjan(t);///递归到头了就
            low[u] = min(low[u], low[t]);
        }
        else if (InStack[t] == 2)///在栈里
        {
            low[u] = min(low[u], dfn[t]);
        }
    }
    if(low[u] == dfn[u])///sta出栈就是一个强连通分量的
    {
        ++ComponentNumber;///强连通分量个数
        while (!sta.empty())
        {
            int j = sta.top();
            sta.pop();
            InStack[j] = 1;///已访问但不在栈中
            Component[ComponentNumber].push_back(j);
            ///用vector存储第ComponentNumber个强连通分量
            InComponent[j]=ComponentNumber;
            ///记录每个点在第几号强连通分量里
            if (j == u)
                break;
        }
    }
}

void input(void)
{
    for(int i=1; i<=m; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        gra[a].push_back(b);///有向图才有强连通分量
    }
}

void solve(void)
{
    for(int i=1; i<=n; i++)
        if(!dfn[i])
            tarjan(i);
    if(ComponentNumber > 1)
        puts("No");
    else
        puts("Yes");
}

int main()
{
    while(scanf("%d%d",&n,&m),n+m)
    {
        init();
        input();
        solve();
        /*每一个强连通分量的具体数字
        for(int i = 1; i <= ComponentNumber; i++)
        {
            for(int j = 0; j < Component[i].size(); j++)
                if(!j)
                    cout << Component[i][j];
                else
                    cout <<"-->"<< Component[i][j];
            cout<<endl;
        }
        */
    }
    return 0;
}
HDU1269也能做模板

 

  1 ///时间复杂度为O(n+m)
  2 ///调用
  3 ///1、init()
  4 ///2、把图用add 存下来,注意图点标为1-n,若是[0,n-1]则给所有点++;
  5 ///3、调用tarjan_init(n); 再调用suodian();
  6 ///4、新图就是vector<int>G[];  新图点标从1-tar ;
  7 ///5、对于原图中的每个点u,都属于新图中的一个新点Belong[u];
  8 ///新图一定是森林。
  9 ///6、新图中的点u 所表示的环对应原图中的vector<int> bcc[u];
 10 ///7、旧图中u在新图中所属的点是Belong[u];
 11 #include <cstdio>
 12 #include <cstring>
 13 #include <vector>
 14 #include <stack>
 15 #include <iostream>
 16 #include <algorithm>
 17 #define repu(i, a, b) for(int i = a; i < b; i++)
 18 using namespace std;
 19 #define N 30100
 20 ///N为最大点数
 21 #define M 150100
 22 ///M为最大边数
 23 int n, m;///n m 为点数和边数
 24 
 25 struct Edge
 26 {
 27     int from, to, nex;
 28     bool sign;///是否为桥
 29 } edge[M<<1];
 30 
 31 int head[N], edgenum;
 32 
 33 void add(int u, int v) ///边的起点和终点
 34 {
 35     Edge E = {u, v, head[u], false};
 36     edge[edgenum] = E;
 37     head[u] = edgenum++;
 38 }
 39 
 40 int DFN[N], Low[N], Stack[N], top, Time;
 41 ///Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
 42 int taj;///连通分支标号,从1开始
 43 int Belong[N];///Belong[i] 表示i点属于的连通分支
 44 bool Instack[N];
 45 vector<int> bcc[N]; ///标号从1开始
 46 
 47 void tarjan(int u ,int fa)
 48 {
 49     DFN[u] = Low[u] = ++ Time ;
 50     Stack[top ++ ] = u ;
 51     Instack[u] = 1 ;
 52     for (int i = head[u] ; ~i ; i = edge[i].nex )
 53     {
 54         int v = edge[i].to ;
 55         if(DFN[v] == -1)
 56         {
 57             tarjan(v , u) ;
 58             Low[u] = min(Low[u] ,Low[v]) ;
 59             if(DFN[u] < Low[v])
 60                 edge[i].sign = 1;///为割桥
 61         }
 62         else if(Instack[v])
 63             Low[u] = min(Low[u] ,DFN[v]) ;
 64     }
 65     if(Low[u] == DFN[u])///找到一个强连通分量
 66     {
 67         int now;
 68         taj ++ ;
 69         bcc[taj].clear();
 70         do
 71         {
 72             now = Stack[-- top] ;
 73             Instack[now] = 0 ;
 74             Belong [now] = taj ;
 75             bcc[taj].push_back(now);///存储一个强连通分量
 76         }
 77         while(now != u) ;
 78     }
 79 }
 80 
 81 void tarjan_init(int all)
 82 {
 83     memset(DFN, -1, sizeof(DFN));
 84     memset(Instack, 0, sizeof(Instack));
 85     memset(Low, 0, sizeof(Low));
 86     top = Time = taj = 0;
 87     for(int i=1; i<=all; i++)
 88         if(DFN[i] == -1 )
 89             tarjan(i, i); ///注意开始点标!!!
 90 }
 91 
 92 vector<int>G[N];
 93 int du[N];
 94 void suodian()
 95 {
 96     memset(du, 0, sizeof(du));
 97     for(int i = 1; i <= taj; i++)
 98         G[i].clear();
 99     for(int i = 0; i < edgenum; i++)
100     {
101         int u = Belong[edge[i].from], v = Belong[edge[i].to];
102         if(u!=v)
103             G[u].push_back(v), du[v]++;
104     }
105 }
106 
107 void init()
108 {
109     memset(head, -1, sizeof(head));
110     edgenum=0;
111 }
112 
113 int main()
114 {
115     while(scanf("%d%d",&n,&m) && (n + m))
116     {
117         init();
118         repu(i,0,m)
119         {
120             int a,b;
121             scanf("%d%d",&a,&b);
122             add(a,b);
123         }
124         tarjan_init(n);
125 //        repu(i,1,taj + 1)///输出每一个强连通分量
126 //        {
127 //            for(int j = 0; j < bcc[i].size(); j++)
128 //                cout<<bcc[i][j]<<" ";
129 //            cout<<endl;
130 //        }
131         if(taj > 1)
132             printf("No\n");
133         else
134             printf("Yes\n");
135     }
136     return 0;
137 }
九野模板--HDU1269

 

参考链接http://blog.csdn.net/xinghongduo/article/details/6195337

 

posted @ 2015-07-27 12:23  一麻袋码的玛侬  阅读(1254)  评论(0编辑  收藏  举报