连通分量(Tarjan、Kosaraju-两遍bfs、Gabow)模版——图论

定义:

如果两个顶点可以相互通达,则称两个顶点强连通 (strongly connected)。如果有向图 G 的每两个顶点都强连通,称 G 是一个强连通图。有向图的极大强连通子图,称为强连通分量 (strongly connected components)。


Tarjan算法


  • 复杂度

可以发现,运行 tarjan 算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为 O (n+e)。其中 n 为有向图 G 的顶点数量,e 为 G 的边数。


代码

#include<iostream>
#include<vector>
using namespace std;

const int mxn = 5010;
int Stack[mxn], top = 0;        //Tarjan 算法中的栈
bool InStack[mxn];              //某个节点是否在栈中
int DFN[mxn];                   //递归搜索到这个节点时候的 次序/编号
int Low[mxn];                   //能够追溯到的最早的 次序/编号
int ComponentNum = 0;           //强连通分量的个数
int Index = 0;                  //次序/编号/索引号
vector<int> edge[mxn];          //存图
vector<int> component[mxn];     //每个强连通分量的组成
int InComponent[mxn];           //记录每个节点所在的强连通分量
int ComponentDegree[mxn];       //记录每个强连通分量的度


void Tarjan(int u)
{
    DFN[u] = Low[u] = ++ Index;
    InStack[u] = true;
    Stack[++ top] = u;
    int v;

    for(int i = 0; i < edge[i].size(); i ++)
    {
        v = edge[u][i];
        if(! DFN[v])
        {
            Tarjan(v);
            Low[u] = min(Low[u], Low[v]);
        }
        else if(InStack[v]) Low[u] = min(Low[u], DFN[v]);       //也可以是 Low[u] = min(Low[u], Low[v]); 仅在求强连通分量的时候
    }

    if(DFN[u] == Low[u])
    {
        ComponentNum ++;
        do
        {
            v = Stack[top --];
            InStack[v] = false;
            component[ComponentNum].push_back(v);
            InComponent[v] = ComponentNum;
        }
        while(u != v);
    }
}
void init(int n)
{
    Index = 0;
   for(int i = 1; i <= n; i ++) component[i].clear(), Low[i] = DFN[i] = 0; 
}

int main()
{
    int n, m;
    while(scanf("%d %d", &n, &m) & n)
    {
        int u, v;
        for(int i = 1; i <= m; i ++)
            scanf("%d %d", &u, &v), edge[u].push_back(v);

        for(int i = 1; i <= n; i ++)
            if(! DFN[i]) Tarjan(i);

    }

    return 0;
}


Kosaraju算法——两遍dfs


  • 复杂度

    由于算法的时间取决于两次 DFS,因此时间复杂度,对于稀疏图是 O (V+E),对于稠密图是 O (V²),


代码(待添加)

#include<iostream>
#include<vector>
using namespace std;

const int mxn = 1e5;
vector<int> G[mxn];
vector<int> G_t[mxn];

int n, m, idx;
int vis[mxn], tim[mxn];

void dfs(int u)
{
    vis[u] = 1;
    for(int i = 0; i < G[u].size(); i ++)
    {
        int v = G[u][i];
        if(! vis[v])
            dfs(v);
    }
    tim[++ idx] = u;
}
void rdfs(int u)
{
    vis[u] = 1;
    for(int i = 0; i < G_t[u].size(); i ++)
    {
        int v = G_t[u][i];
        if(! vis[v])
            rdfs(v);
    }
}

void init(int n)
{
    idx = 0;
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= n; i ++)
        G[i].clear(), \
            G_t[i].clear();
}

int main()
{
    while(scanf("%d %d", &n, &m) != EOF && n)
    {
        init(n);
        for(int i = 1, u, v; i <= m; i ++)
        {
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
            G_t[v].push_back(u);
        }

        for(int i = 1; i <= n; i ++)
        {
            if(! vis[i])
                dfs(i);
        }
        memset(vis, 0, sizeof(vis));
        int sum = 0;                        //强连通分量 的数量
        for(int i = n; i >= 1; i --)
        {
            if(! vis[tim[i]])
                rdfs(tim[i]), sum ++;
        }
        printf("%d\n", sum);
    }

    return 0;
}
/*
12
16
12 11
11 8
11 10
8 10
10 9
9 8
9 7
7 6
5 7
6 5
6 4
6 3
4 3
2 3
3 2
4 1
*/


Gabow算法


  • 复杂度

O (v + e),Tarjan 算法和 Gabow 算法其实是同一个思想的不同实现,但是,Gabow 算法更精妙,时间更少 (不用频繁更新 Low [])。

代码

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

const int mxn = 1e5;
vector<int> edge[mxn];      //邻接表存图
int order[mxn];             //存储节点被递归遍历的到的次序,注意这个次序号的 大小 是作为判断:强连通分量的 根的依据
int idx = 0;                //遍历的次序
int part[mxn];              //某个节点是否已经被确定的了 在某个强连通分量中
int partnum = 0;            //强连通分量的 编号
stack<int> path;            //记录路径
stack<int> root;            //存储并不断的找,强连通分量的根

void Gabow(int u)
{
    order[u] = ++ idx;
    path.push(u);
    root.push(u);
    for(int i = 0; i < edge[u].size(); i ++)
    {
        int v = edge[u][i];
        if(order[v] == -1)          //v 没有被拜访过/编过号
            Gabow(v);
        else if(part[v] == -1)      //v 被拜访过,但是它所位于的强连通分量 还没有被分配编号
        {
            while(order[root.top()] > order[v]) root.pop();
        }
    }

    if(u == root.top())             //从节点u-> 出发 经过一些节点又回到了 -> u 说明找到一个 强连通分量
    {
        root.pop();
        partnum ++;                 //为找到的 这个强连通分量 分配编号
        int Top;
        do
        {
            Top = path.top();
            path.pop();
            part[Top] = partnum;
        } while(Top != u);
    }
}

void init(int n)
{
    idx = partnum = 0;
    for(int i = 1; i <= n; i ++)  order[i] = -1, part[i] = -1, edge[i].clear();
}

int main()
{

    return 0;
}

例题 A - 迷宫城堡 HDU - 1269

Tarjan代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;

const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
    int v, next;
} edge[mxm];
int head[mxn], vis[mxn];
stack<int> st;
int num, idx, tot;
int Low[mxn], DFN[mxn];
vector<int> res[mxn];
void Add(int u, int v)
{
    edge[++ tot] = (Edge){ v, head[u] }; head[u] = tot;
}

bool Tarjan(int u)
{
    Low[u] = DFN[u] = ++ idx;
    vis[u] = 1;         //是否在 栈 里面
    st.push(u);
    for(int i = head[u]; i; i = edge[i].next)
    {
        int v = edge[i].v;
        if(! vis[v])
        {
            Tarjan(v);
            Low[u] = min(Low[u], Low[v]);       //注意这的一是 min(Low[u], Low[v]) 这与Low[v] 的值会变小有关
        }
        else if(vis[v])
        {
            Low[u] = min(Low[u], DFN[v]);
        }
    }

    if(Low[u] == DFN[u])
    {
        num ++;
        if(num >= 2) return false;
        int Top;
        do
        {
            Top = st.top();
            st.pop();
            vis[Top] = false;
            /* res[num].pb(Top); */
        } while(Top != u);
    }
    if(num >= 2) return false;
    return true;
}


void init(int n)
{
    num = idx = tot = 0;
    for_(i, 1, n) DFN[i] = Low[i] = vis[i] = head[i] = 0;     //不要忘了给 head 数组进行初始化
    while(! st.empty()) st.pop();
}

int main()
{
    //fre();
    int n, m;
    while(sc("%d %d", &n, &m) && n)
    {
        init(n);
        int u, v;
        for_(i, 1, m)
        {
            sc("%d %d", &u, &v);
            Add(u, v);
        }
        int fg = 1;
        for_(i, 1, n)
        {
            if(DFN[i] == 0)
            {
                if(Tarjan(i) == false || i > 1)
                {
                    fg = 0;
                    break;
                }
            }
        }

        if(fg)
            pr("Yes\n");
        else
            pr("No\n");
    }

    return 0;
}

Kosaraju-两遍dfs 代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;

const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
    int v, next;
} e1[mxm], e2[mxm];
int t1, t2;
int h1[mxn], h2[mxn];
void Add(int u, int v, int & tot, int h[], Edge e[])        //注意这里 tot 的声明必须是 引用!!!
{
    e[++ tot] = (Edge){ v, h[u] }; h[u] = tot;
}

int idx, vis[mxn], tim[mxn];
void dfs(int u)
{
    vis[u] = 1;
    for(int i = h1[u]; i; i = e1[i].next)
    {
        int v = e1[i].v;
        if(! vis[v])
            dfs(v);
    }
    tim[u] = ++ idx;
}
void rdfs(int u)
{
    vis[u] = 1;
    for(int i = h2[u]; i; i = e2[i].next)
    {
        int v = e2[i].v;
        if(! vis[v])
            rdfs(v);
    }
}

void init(int n)
{
    t1 = t2 = idx = 0;
    for_(i, 1, n) h1[i] = h2[i] = vis[i] = 0;    
}

int main()
{
    /* fre(); */
    int n, m;
    while(sc("%d %d", &n, &m) && n)
    {
        init(n);
        int u, v;
        for_(i, 1, m)
        {
            sc("%d %d", &u, &v);
            Add(u, v, t1, h1, e1);
            Add(v, u, t2, h2, e2);
        }
        for_(i, 1, n)
            if(! vis[i])
                dfs(i);

        for_(i, 1, n) vis[i] = 0;       //注意这里的初始化不要忘

        int fg = 0;
        for_(i, 1, n)
            if(! vis[i])
            {
                rdfs(i);
                fg ++;
                if(fg >= 2)
                    break;
            }

        if(fg == 2)
            pr("No\n");
        else
            pr("Yes\n");
    }

    return 0;
}

Gbow代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
/* #include <unordered_map> */
#include <bitset>
#include <vector>
#include <stack>
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sc scanf
#define pr printf
#define sd(a) scanf("%d", &a)
#define ss(a) scanf("%s", a)
using namespace std;

const int mxn = 10005;
const int mxm = 100005;
struct Edge
{
    int v, next;
} edge[mxm];
int tot, head[mxn];
void Add(int u, int v) { edge[++ tot] = (Edge){ v, head[u] }; head[u] = tot; }

int part[mxn], idx;
int order[mxn], partnum;
stack<int> path;
stack<int> root;

void Gabow(int u)
{
    path.push(u);
    root.push(u);
    order[u] = ++ idx;
    for(int i = head[u]; i; i = edge[i].next)
    {
        int v = edge[i].v;
        if(! order[v])
            Gabow(v);
        else if(! part[v])          //如果被访问过,且在 root栈中,那么v就可能是 v所属的强连通分量的根
        {
            while(order[root.top()] > order[v]) root.pop();
        }
    }
    if(u == root.top())
    {
        partnum ++;
        root.pop();
        int Top;

        do
        {
            Top = path.top();
            //vis[Top] = 0; 
            path.pop();
        } while(Top != u);
    }
}

void init(int n)
{
    tot = idx = partnum = 0;
    for_(i, 1, n) head[i] = order[i] = part[i] = 0;
}
int main()
{
    /* fre(); */
    int n, m;
    while(sc("%d %d", &n, &m) && n)
    {
        init(n);
        int u, v;
        for_(i, 1, m)
        {
            sc("%d %d", &u, &v);
            Add(u, v);
        }

        int fg = 0;
        for_(i, 1, n)
        {
            if(! order[i])
            {
                Gabow(i);
                fg += partnum;
                partnum = idx = 0;        //这里不要忘了初始化
                if(fg >= 2)
                    break;
            }
        }

        if(fg == 2)
            pr("No\n");
        else
            pr("Yes\n");
    }

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值