分解强连通分量

模板题目:http://poj.org/problem?id=2553

Kosaraju:按顺序对原图和反图DFS一遍,在第二次DFS时点u可达未DFS的点v则u,v属于同一个联通分量

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<stack>

using namespace std;

const int maxn = 5005;
int out[maxn];
int in[maxn];
vector<int> G[maxn];
vector<int> Gr[maxn];
vector<int> rG[maxn];
vector<int> P[maxn];
vector<int> ps;
int ID[maxn];
int n, m;
bool vis[maxn];

void DFS(int u)
{
    vis[u] = true;
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!vis[v]) DFS(v);
    }
    ps.push_back(u);
}

void RDFS(int u, int x)
{
    vis[u] = true;
    ID[u] = x;
    P[x].push_back(u);
    for(int i = 0; i < Gr[u].size(); i++)
    {
        int v = Gr[u][i];
        if(!vis[v]) RDFS(v, x);
    }
}

void init()
{
    memset(out, 0, sizeof(out));
    ps.clear();
    for(int i = 1; i <= n; i++)
    {
        G[i].clear();
        Gr[i].clear();
        rG[i].clear();
        P[i].clear();
    }
}

int Kosaraju()
{
    int res = 0;

    memset(vis, 0, sizeof(vis));
    for(int i =  1; i <= n; i++)
    {
        if(!vis[i]) DFS(i);
    }
    memset(vis, 0, sizeof(vis));
    for(int i = ps.size() - 1; i + 1; i--)
    {
        int v = ps[i];
        if(!vis[v]) RDFS(v, ++res);
    }
    return res;
}

void Get_DAG()
{
    for(int u = 1; u <= n; u++)
    {
        for(int i = 0; i < G[u].size(); i++)
        {
            int v = G[u][i];
            if(ID[u] != ID[v])
            {
                rG[ID[u]].push_back(ID[v]);
                out[ID[u]]++;
            }
        }
    }
}

int main()
{
    while(cin >> n, n)
    {
        cin >> m;
        init();
        for(int i = 0; i < m; i++)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
            Gr[v].push_back(u);
        }
        Kosaraju();
        Get_DAG();
        vector<int> ans;
        for(int i = 1; i <= n; i++)
        {
            if(!out[i] && P[i].size())
            {
                for(int j = 0; j < P[i].size(); j++)
                {
                    int v = P[i][j];
                    ans.push_back(v);
                }
            }
        }
        sort(ans.begin(), ans.end());
        for(int i = 0; i < ans.size(); i++)
        {
            int v = ans[i];
            cout << v << (v == ans.back() ? '\n' : ' ');
        }
    }
    return 0;
}

Tarjan:对于一个强连通分量,其中的第一个点被dfs搜到的时候,接着一定是整个联通分量被遍历完并且回到最开始的点,这时候就可以对该dfs子树进行整体编号

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<stack>

using namespace std;

const int maxn = 5005;
int out[maxn];
stack<int> S;
vector<int> G[maxn];
vector<int> rG[maxn];
vector<int> P[maxn];
bool isinstack[maxn];
int low[maxn], dfn[maxn];
int ID[maxn];
int n, m, ssc_num, index;


void Get_DAG()
{
    for(int u = 1; u <= n; u++)
    {
        for(int i = 0; i < G[u].size(); i++)
        {
            int v = G[u][i];
            if(ID[u] != ID[v])
            {
                rG[ID[u]].push_back(ID[v]);
                out[ID[u]]++;
            }
        }
    }
}

void init()
{
    memset(out, 0, sizeof(out));
    memset(dfn, -1, sizeof(dfn));
    memset(isinstack, 0, sizeof(isinstack));
    index = 1;
    for(int i = 1; i <= n; i++)
       P[i].clear(), G[i].clear(), rG[i].clear();
}

void TarDFS(int u)
{
    dfn[u] = index++;
    low[u] = dfn[u];
    isinstack[u] = 1;
    S.push(u);
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(dfn[v] == -1)
        {
            TarDFS(v);
            low[u] = min(low[u], low[v]);
        }
        else if(isinstack[v])
        {
            low[u] = min(low[u], dfn[v]);
        }
    }

    if(dfn[u] ==low[u])
    {
        ssc_num++;
        while(S.top() != u)
        {
            isinstack[S.top()] = 0;
            ID[S.top()] = ssc_num;
            P[ssc_num].push_back(S.top());
            S.pop();
        }
        isinstack[u] = 0;
        ID[u] = ssc_num;
        P[ssc_num].push_back(u);
        S.pop();
    }
}

int Tarjan()
{
    ssc_num = 0;
    index = 1;
    for(int i = 1; i <= n; i++)
    {
        if(dfn[i] == -1)
        {
            TarDFS(i);
        }
    }
    return ssc_num;
}

int main()
{
    while(cin >> n, n)
    {
        cin >> m;
        init();
        for(int i = 0; i < m; i++)
        {
            int u, v;
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
        }
        Tarjan();
        Get_DAG();
        vector<int> ans;
        for(int i = 1; i <= n; i++)
        {
            if(!out[i] && P[i].size())
            {
                for(int j = 0; j < P[i].size(); j++)
                {
                    int v = P[i][j];
                    ans.push_back(v);
                }
            }
        }
        sort(ans.begin(), ans.end());
        for(int i = 0; i < ans.size(); i++)
        {
            int v = ans[i];
            cout << v << (v == ans.back() ? '\n' : ' ');
        }
    }
    return 0;
}

 

<think>首先,用户的问题是:“我想理解强连通分量的概念及其在有向图中的性质 请问强连通分量 在图论中的定义与判断条件”。所以,我需要解释强连通分量(SCC)的定义、性质以及如何判断它。 从系统指令中,我必须: - 使用$...$格式行内数学表达式。 - 独立公式使用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 在引用段落末尾添加引用标识,如[^1]。 参考提供的引用: - 引用[^1]:定义了强连通分量作为一组顶点,其中任意两个顶点相互可达。 - 引用[^2]:提到基本概念和测试点,但焦点在强连通图。 - 引用[^3]:区分连通图和强连通图,强调有向图和无向图的区别。 - 引用[^4]:讨论了强连通分量的性质,如存在一个点是其他所有点的祖先。 - 引用[^5]:定义了强连通图:任意两点相互可达。 用户的问题集中在定义和判断条件上。所以,我的回答应该结构清晰: 1. 定义强连通分量。 2. 解释在有向图中的性质。 3. 描述判断条件(如何找到SCC)。 关键点: - **定义**:强连通分量是有向图中一个最大子集,其中任意两个顶点相互可达(即存在从u到v和v到u的路径)。 - **性质**: - 整个图可以分解成多个强连通分量。 - 每个强连通分量是强连通的。 - 强连通分量之间形成有向无环图(DAG)。 - **判断条件**:常用算法是Kosaraju算法或Tarjan算法。我应该简要描述这些算法。 由于用户要求“理解概念”,我不需要深入算法细节,但可以提到算法名称。 使用中文回答。 结构回答: 1. **引言**:简要介绍强连通分量。 2. **定义**:正式定义。 3. **性质**:讨论关键性质。 4. **判断条件**:解释如何判断或找到SCC。 5. **总结**:简要总结。 6. **相关问题**:生成相关问题。 确保使用正确的LaTeX格式: - 行内数学表达式用$...$,如顶点$u$和$v$。 - 独立公式用$$...$$,如定义公式。 引用:当引用提供的引用时,在末尾添加[^n]。例如,如果基于引用[^1],添加[^1]。 提供的引用内容: - [^1]:定义SCC。 - [^2]:关于测试点,不直接相关。 - [^3]:区分连通和强连通。 - [^4]:性质,如存在祖先点。 - [^5]:定义强连通图。 我可以引用这些来支持我的解释。 最终回答应真实可靠,基于图论知识。</think>### 强连通分量的概念、性质与判断条件 强连通分量(Strongly Connected Components,简称SCC)是图论中的一个核心概念,主要用于分析有向图的结构。它描述了图中一组顶点的强连通特性,即在任意两个顶点之间都存在双向路径。以下我将从定义、性质到判断条件逐步解释,确保内容清晰可靠。参考了图论标准知识和相关引用[^1][^3][^5]。 #### 1. **强连通分量的定义** - **基本定义**:在有向图$G = (V, E)$中,一个强连通分量是顶点集$V$的一个最大子集$S \subseteq V$,其中对于任意两个顶点$u$和$v$($u, v \in S$),都存在从$u$到$v$的路径和从$v$到$u$的路径。这意味着$S$中的顶点相互可达,形成一个“强连通”的单元[^1][^5]。 - **数学表达**:强连通分量满足以下条件: - 强连通性:$\forall u, v \in S, \exists \text{路径 } u \to v \text{ 和 } v \to u$。 - 极大性:$S$不能被扩展,即添加任何额外顶点都会破坏强连通性。 - **示例**:考虑一个有向图,如果顶点集$\{A, B, C\}$中,$A \to B$、$B \to C$、$C \to A$都有路径,则$\{A, B, C\}$是一个强连通分量。但如果只有$A \to B$而没有$B \to A$,则它们不属于同一个SCC。 - **与连通分量的区别**:强连通分量专用于有向图,而连通分量用于无向图(无向图中任意两点只需单向路径即可连通)[^3]。例如,无向图的连通分量不要求双向路径,但强连通分量在有向图中强调双向可达性。 #### 2. **强连通分量在有向图中的性质** 强连通分量具有几个关键性质,这些性质有助于理解图的结构和算法设计: - **分解性质**:任何有向图都可以唯一分解为多个强连通分量,且这些分量之间形成有向无环图(DAG)。也就是说,将每个SCC视为一个“超顶点”后,图变为无环结构,便于分析图的层次[^4][^5]。 - **可达性性质**:在同一个SCC内,所有顶点相互可达;但在不同SCC之间,可达性是单向的(例如,从SCC A到SCC B可能有路径,但反之则不一定)。这体现了SCC的“强”特性[^1]。 - **祖先性质**:每个强连通分量中,存在一个顶点(称为“根”或“祖先”),它是分量中其他所有顶点的祖先(即存在从该顶点到其他顶点的路径)。这一性质在算法中用于高效识别SCC[^4]。 - **应用意义**:SCC常用于建模网络依赖关系,如编译器优化(循环检测)、社交网络(社区发现)或Web图分析(PageRank算法),其中强连通区域表示高度互连的组件[^3][^5]。 #### 3. **强连通分量的判断条件** 判断强连通分量的核心是验证图中任意两点是否双向可达。常用算法基于深度优先搜索(DFS),以下是两种主要方法的简要说明: - **Tarjan算法**:这是最常用的算法,时间复杂度为$O(|V| + |E|)$,其中$|V|$是顶点数,$|E|$是边数。它使用DFS遍历图,并维护两个关键值: - $dfn[u]$:顶点$u$的DFS序(访问时间戳)。 - $low[u]$:从$u$出发能到达的最小DFS序顶点。 - **判断条件**:当DFS回溯时,如果$dfn[u] = low[u]$,则$u$是一个SCC的根节点,该SCC包含所有从栈中弹出的顶点,直到$u$为止[^4][^5]。 - 伪代码摘要: ```python def tarjan(u): dfn[u] = low[u] = index++ stack.push(u) for each neighbor v of u: if v not visited: tarjan(v) low[u] = min(low[u], low[v]) elif v in stack: low[u] = min(low[u], dfn[v]) if dfn[u] == low[u]: scc = [] while True: v = stack.pop() scc.append(v) if v == u: break output scc ``` - **Kosaraju算法**:另一种高效方法,时间复杂度同样为$O(|V| + |E|)$。它分两步: 1. **第一次DFS**:对原图$G$进行DFS,记录顶点完成时间(后序)。 2. **第二次DFS**:对图的反图$G^T$(所有边反向)进行DFS,按完成时间逆序访问顶点。 - **判断条件**:在第二次DFS中,每个递归调用的连通块就是一个强连通分量[^1][^5]。 - **简单判断条件**:对于单个顶点对,可通过检查双向路径存在性来判断,但全图SCC识别需依赖上述算法以避免高复杂度(例如,暴力检查需$O(|V|^2)$时间)。 #### 总结 强连通分量定义了有向图中顶点相互可达的最大子集,具有分解性、祖先性和DAG结构等性质。判断条件主要通过TarjanKosaraju算法实现,这些算法高效可靠,适用于大规模图(如顶点数$n \leq 10^4$,边数$m \leq 5 \times 10^4$的常见场景)[^2][^4]。理解SCC有助于分析图的连通性和优化实际问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值