• 强连通分量


    https://www.cnblogs.com/31415926535x/p/10363253.html

    概述

    图的连通性是图论中的一个基础知识点,算法很简单,但是所所涉及的基础知识点也很多,后悔当初离散数学没有好好的学,QAQ,,,

    这篇主要是记录一下两种方法求强连通分量的算法,Tarjan和Kosaraju的模板,

    算法

    (具体的算法的证明和相关的内容来自这篇博客以及红书上的内容)

    强连通分量即 Strongly Connected Component,一个有向图中的人一两点若能相互到达,即为强连通图,若不为强连通图,则改图肯定由若干个小的强连通图组成,即为强连通分量,例如

    对于这个图,有三个强连通分量,{1,2,3,4},{5},{6},,,

    Kosaraju算法

    • 对原图进行一次深搜,计算出每一个节点被访问的次序(时间)st[i];
    • 对逆图进行一次深搜,遍历的起点为第一步节点结束时间从大到小进行,同是做标记cnt2
    • 最后标记值相同的点即为一个强连通分量,color[u]==color[v],说明u,v在用一个分量里,,(kuangbin的板子这里是用的belong[i]表示的)

    Tarjan算法

    Tarjan算法的思想:对于每一个强连通分量scc所构成的树一定为深搜时的dfs树,所以找到dfs树上的根即能确定一个scc

    • dfn[i]记录的是节点i在深搜中的访问次序(时间戳)
    • low[i]记录的是点i可以到达的访问时间的最早祖先
    • Stack是记录节点的栈

    1、深搜整个图,一路上标记dfn并把新节点压栈
    2、对于一个节点i,如果low[i]==dfn[i],,说明他无法到达他的任何一个祖先
    3、栈中i和i之后的点是相互可达的,所以可以组成一个极大强连通分量,可以整体弹出
    4、low的求法:根据定义,如果点u访问一个新店v,那么u也可以到达low[v],所以可以用low[v]来尝试更新low[u];如果点u访问一个祖先k,那么就直接用dfn[k]尝试更新low[u];
    (看那篇博客的图更好理解)

    例题和模板

    例题为红书上的推荐poj2189

    题目分析

    有这么一群牛,牛A可以认为牛B是受欢迎的,同时如果牛B认为牛C是受欢迎时,就可以理解为牛A认为牛C是受欢迎的,即这种关系具有传递性,然后问你这群牛中有多少头是被其他所有牛认为是受欢迎的。

    抽象成图论的样子来理解就是:对于给定的一个有向图,u->v表示牛u认为牛v是受欢迎的,问你在这个图中有几个点是其他所有点可以到达的。

    思路是先求出有向图的强连通分量,将同意分量的点“染色”成同一个编号,,然后“缩点”成一个DAG有向无环图,然后找出所有出度为0的点,如果这样的点只有一个,说明这个点是可以被其他的点到达的,同时也说明这个点(强连通分量)所包含的点也是原图中其他所有点可以到达的,答案就是这个强连通分量中点的个数;如果出度为0点有多个,及说明这些强连通分量块之间是没有可达的路径的,及原图中不存在任何一个其他所有点都能到达的点;

    Kosaraju实现

    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <vector>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e5 + 5;
    const int maxm = 2e5 + 5;
    const int mod = 1e9 + 7;
    inline ll read() {
        char c = getchar(); int x = 0, f = 1;
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    //kosaraju
    struct edge
    {
        int to, next;
    }edge1[maxn], edge2[maxn];
    //edge1为原图,edge2为逆图
    int head1[maxn], head2[maxn];
    bool mark1[maxn], mark2[maxn];
    int tot1, tot2;
    int cnt1, cnt2;//cnt2即为强连通分量的个数scc
    int st[maxn];//对原图进行dfs,点的结束时间从小到大的排序
    int belong[maxn];//每个点属于那个连通分量的编号(0~cnt2-1)
    int num;//中间变量,用来书某个连通分量中点的个数
    int setnum[maxn];//强连通分量中点的个数,编号0~cnt2-1
    void addedge(int u, int v)
    {
        edge1[tot1].to = v; edge1[tot1].next = head1[u]; head1[u] = tot1++;
        edge2[tot2].to = u; edge2[tot2].next = head2[v]; head2[v] = tot2++;
    }
    void dfs1(int u)
    {
        mark1[u] = true;
        for(int i = head1[u]; ~i; i = edge1[i].next)
            if(!mark1[edge1[i].to])
            dfs1(edge1[i].to);
        st[cnt1++] = u;
    }
    void dfs2(int u)
    {
        mark2[u] = true;
        ++num;
        belong[u] = cnt2;
        for(int i = head2[u]; ~i; i = edge2[i].next)
            if(!mark2[edge2[i].to])
            dfs2(edge2[i].to);
    }
    void kosaraju(int n)
    {
        memset(mark1, false, sizeof mark1);
        memset(mark2, false, sizeof mark2);
        cnt1 = cnt2 = 0;
        for(int i = 1; i <= n; ++i)
            if(!mark1[i])
                dfs1(i);
        for(int i = cnt1 - 1; i >= 0; --i)
            if(!mark2[st[i]])
        {
            num = 0;
            dfs2(st[i]);
            setnum[cnt2++] = num;
        }
    }
    void init()
    {
        tot1 = tot2 = 0;
        memset(head1, -1, sizeof head1);
        memset(head2, -1, sizeof head2);
    }
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n, m;
        while(scanf("%d%d", &n, &m) != EOF)
        {
            int u, v;
            init();
            while(m--)
            {
                scanf("%d%d", &u, &v);
                addedge(u, v);
            }
            kosaraju(n);
            int out[maxn];//缩点后每个强连通分量代表的点的出度
            memset(out, 0, sizeof out);
            for(int u = 1; u <= n; ++u)
                for(int i = head1[u]; ~i; i = edge1[i].next)
                {
                    int v = edge1[i].to;
                    if(belong[u] != belong[v])//缩点,同一编号的点即为同一个强连通分量
                    ++out[belong[u]];
                }
    
            int flag = -1;//出度为零的点的编号
            int num_ = 0;//出度为零的点的个数
            for(int i = 0; i < cnt2; ++i)
                if(!out[i])
                {
                    flag = i;
                    ++num_;
                }
            if(~flag && num_ == 1)
            {
                printf("%d
    ", setnum[flag]);
            }
            else
            {
                printf("0
    ");
            }
        }
        return 0;
    }
    

    Tarjan

    //kaungbin的板子
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <vector>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e5 + 5;
    const int maxm = 2e5 + 5;
    const int mod = 1e9 + 7;
    inline ll read() {
        char c = getchar(); int x = 0, f = 1;
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    struct edge
    {
        int to, next;
    }edge[maxn];
    int head[maxn], tot;
    int low[maxn], dfn[maxn], Stack[maxn], belong[maxn];
    int index, top;
    int scc;//强连通分量的个数
    bool instack[maxn];
    int num[maxn];//每个编号的强连通分量中点的个数
    
    void addedge(int u, int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        head[u] = tot++;
    }
    void tarjan(int u)
    {
        int v;
        low[u] = dfn[u]= ++index;
        Stack[top++] = u;
        instack[u] = true;
        for(int i = head[u]; ~i; i = edge[i].next)
        {
            v = edge[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                if(low[u] > low[v])low[u] = low[v];
            }
            else if(instack[v] && low[u] > dfn[v])
                low[u] = dfn[v];
        }
        if(low[u] == dfn[u])
        {
            ++scc;
            do
            {
                v = Stack[--top];
                instack[v] = false;
                belong[v] = scc;
                ++num[scc];
            }while(v != u);
        }
    }
    void solve(int n)
    {
        memset(dfn, 0, sizeof dfn);
        memset(instack, false, sizeof instack);
        memset(num, 0, sizeof num);
        index = scc = top = 0;
        for(int i = 1; i <= n; ++i)
            if(!dfn[i])
                tarjan(i);
    }
    void init()
    {
        tot = 0;
        memset(head, -1, sizeof head);
    }
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n, m;
        while(scanf("%d%d", &n, &m) != EOF)
        {
            int u, v;
            init();
            while(m--)
            {
                scanf("%d%d", &u, &v);
                addedge(u, v);
            }
            solve(n);
            int out[maxn];
            memset(out, 0, sizeof out);
            for(int u = 1; u <= n; ++u)
                for(int i = head[u]; ~i; i = edge[i].next)
                {
                    int v = edge[i].to;
                    if(belong[u] != belong[v])
                    ++out[belong[u]];
                }
    
            int flag = -1;
            int num_ = 0;
            for(int i = 1; i <= scc; ++i)
                if(!out[i])
                {
                    flag = i;
                    ++num_;
                }
            if(~flag && num_ == 1)
            {
                printf("%d
    ", num[flag]);
            }
            else
            {
                printf("0
    ");
            }
        }
        return 0;
    }
    
    //红书的板子,感觉不太友好,虽然看着舒服,但是没有上一个板子灵活,而且使用vector实现,耗时稍大
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <vector>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e5 + 5;
    const int maxm = 2e5 + 5;
    const int mod = 1e9 + 7;
    inline ll read() {
        char c = getchar(); int x = 0, f = 1;
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    struct scc
    {
        vector <int> &color;
        vector <int> Stack;
        int num_scc, colorcnt, curr, *instack, *dfn, *low, *info, *next, *to;
        void dfs(int x)
        {
            dfn[x] = low[x] = ++curr;
            Stack.push_back(x);
            instack[x] = true;
            for(int j = info[x]; j; j = next[j])
                if(!instack[to[j]])
                {
                    dfs(to[j]);
                    low[x] = min(low[x], low[to[j]]);
                }
                else if(instack[to[j]] == 1)
                {
                    low[x] = min(low[x], dfn[to[j]]);
                }
    
            if(low[x] == dfn[x])
            {
                while(Stack.back() != x)
                {
                    color[Stack.back()] = colorcnt;
                    instack[Stack.back()] = 2;
                    Stack.pop_back();
                }
                color[Stack.back()] = colorcnt++;
                instack[Stack.back()] = 2;
                Stack.pop_back();
                ++num_scc;
            }
        }
        //edge为图, n为点数, ans为染色的结果,及编号, ansn为scc的个数
        scc(const vector<pair<int, int> > &edge, int n, vector<int> &ans, int &ansn):color(ans)
        {
            color.resize(n);
            instack = new int[n];
            dfn = new int[n];
            low = new int[n];
            info = new int[n];
            next = new int[(int)edge.size() + 5];
            to = new int[(int)edge.size() + 5];
            fill_n(info, n, 0);
            for(size_t i = 0; i < edge.size(); ++i)
            {
                to[i + 1] = edge[i].second;
                next[i + 1] = info[edge[i].first];
                info[edge[i].first] = i + 1;
            }
            fill_n(instack, n, 0);
            colorcnt = 0;
            curr = 0;
            num_scc = 0;
            for(int i = 0; i < n; ++i)
                if(!instack[i])
                    dfs(i);
            ansn = num_scc;
            delete[] instack;
            delete[] dfn;
            delete[] low;
            delete[] info;
            delete[] next;
            delete[] to;
        }
    };
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n, m;
        while(scanf("%d%d", &n, &m) != EOF)
        {
            int u, v;
            vector<pair<int, int> > edge;
            edge.clear();
            while(m--)
            {
                scanf("%d%d", &u, &v);
                --u;--v;
                edge.push_back(make_pair(u, v));
            }
            vector<int> ans;
            ans.clear();
            int ansn;
            scc(edge, n, ans, ansn);
    
            int out[maxn];
            memset(out, 0, sizeof out);
            for(size_t i = 0; i < edge.size(); ++i)
                if(ans[edge[i].first] != ans[edge[i].second])
                    ++out[ans[edge[i].first]];
            int flag = -1;
            int num = 0;
            for(int i = 0; i < ansn; ++i)
                if(!out[i])
                {
                    flag = i;
                    ++num;
                }
            if(~flag && num == 1)
            {
                int res = 0;
                for(int i = 0; i < n; ++i)
                    if(ans[i] == flag)
                        ++res;
                printf("%d
    ", res);
            }
            else
            {
                printf("0
    ");
            }
        }
        return 0;
    }
    

    总结

    Tarjan和Kosaraju的时间复杂度基本相等,都为O(V + E),,,但是看很多人的建议是尽量用Tarjan做题,不易出现爆栈的情况,实际运行的时间也有时小一些,Kosaraju较容易理解;

    求强连通分量是一些其他算法的基础,,例如2-sat;
    (end)

  • 相关阅读:
    贪心算法与动态规划
    Linux重要目录结构
    博客园添加目录索引
    冒泡排序&插入排序&其他排序
    Linux下部署自己写的Web项目
    Java算法入门-数组&链表&队列
    Java集合-数据结构之栈、队列、数组、链表和红黑树
    Java集合-单例模式斗地主&Collections类的shuffle方法了解
    什么是反向代理服务器
    Linux信号处理
  • 原文地址:https://www.cnblogs.com/31415926535x/p/10363253.html
Copyright © 2020-2023  润新知