[置顶] 连通图小结(need to be updated)

连通图小结 A Summary for Connected Graph

Ⅰ.概念

  • 强连通
    • 强连通:有向图中 (u,v) 存在 uv, vu 的两条路径,称 (u,v) 为强连通
    • 强连通图:有向图中任意两个顶点强连通
    • 强连通分量:有向图的极大强连通子图
  • 弱连通
    • 弱连通:无向图中 (u,v) 存在 uv 的路径,称 (u,v) 为弱连通
    • 弱连通图:无向图中任意两个顶点弱连通,(可以将忽略方向的有向图看作无向图)
    • [弱]连通分量:无向图的极大[弱]连通子图
  • 割点与桥
    • 割点:无向图中点 u ,删去后连通分量数目增加(连通图则使得图不再连通),称 u 为割点
    • 桥:无向图中边 (u,v) ,删去后连通分量数目增加(连通图则使得图不再连通),称 (u,v) 为桥(割边)
  • 点双连通
    • 点双连通:无向图中 (u,v) 存在至少两条“点不重复”的路径,称 (u,v) 为点双连通
      这个要求等价于任意两条边都在同一个简单环中,即内部无割点
    • 点双连通分量:无向图的极大点双连通子图。
    • 性质:
      • 无向图每条边恰好属于一个点双连通分量
      • 不同的点双连通分量之间可能会有公共点,最多只有一个且一定是割点
      • 任意割点都是至少两个不同点双连通分量的公共点
  • 边双连通
    • 边双连通:无向图中 (u,v) 存在至少两条“边不重复”的路径,称 (u,v) 为边双连通
      这个要求低一点,只需要每条边都至少在一个简单环中,即所有的边都不是桥
    • 边双连通分量:无向图的极大边双连通子图。
    • 性质:
      • 除桥不属于任何边双连通分量外,其他每条边恰好属于一个边双连通分量
      • 把所有桥删除后,每个连通分量对应原图的一个边双连通分量

Ⅱ.连通图算法

  • 连通分量 Connected Component
    求解连通分量只需要 dfs 或者 bfs 把图搜一遍就好了,当然并查集也是可以的。
    一般不爆栈的情况, dfs 好写适用性强
//Connected Components
vector<int> G[N];
int id[N], cc;

void dfs(int u) {
    id[u] = cc;
    for(int v : G[u]) {
        if(id[v]) continue;
        dfs(v);
    }
}

void findCC() {
    cc = 0;
    for(int i = 1; i <= n; ++i) {
        if(id[i]) continue;
        ++cc;
        dfs(i);
    }
}
  • 割点 Cut
    基于 dfs 时间戳的 Tarjan 算法
    dfn[u]:= u 的时间戳, low[u]:= u 及其后代能连回的最早的祖先的时间戳
    如果 low[v]dfn[u] ,即 v 及其所在的子树都没有后向边 (back edge) 连回u的祖先
    如果删去 u ,那么 u 的祖先与 v 及其所在的子树不连通,根据“连通”关系的传递性,整个图就不连通了
//Tarjan-cut
vector<int> G[N];
int dfn[N], low[N], cut[N], dfsNum;

void tarjan(int u, int f) {
    dfn[u] = low[u] = ++dfsNum;
    int son = 0;
    for(int v : G[u]) {
        if(v == f) continue;
        if(!dfn[v]) {
            ++son;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]) cut[u] = true;
        } else low[u] = min(low[u], dfn[v]);
    }
    if(f < 0 && son == 1) cut[u] = false;
}

void init() {
    dfsNum = 0;
    memset(dfn, 0, sizeof dfn);
    memset(cut, false, sizeof cut);
    tarjan(root, -1);
}
  • Bridge
    基于 dfs 时间戳的 Tarjan 算法
    dfn[u]:= u 的时间戳, low[u]:= u 及其后代能连回的最早的祖先的时间戳
    如果 low[v]>dfn[u] ,即 v 及其所在的子树只能连回 v 自己
    如果删去 (u,v) ,那么 u v 及其所在的子树不连通,根据“连通”关系的传递性,整个图就不连通了
//Tarjan-bridge
vector<int> G[N];
vector<pair<int, int> > bridge;
int dfn[N], low[N], dfsNum;

void tarjan(int u, int f) {
    dfn[u] = low[u] = ++dfsNum;
    for(int v : G[u]) {
        if(v == f) continue;
        if(!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u]) bridge.push_back({u, v});
        } else low[u] = min(low[u], dfn[v]);
    }
}

void init() {
    dfsNum = 0;
    bridge.clear();
    memset(dfn, 0, sizeof dfn);
    tarjan(root, -1);
}
  • 点双连通分量 Biconnected Component or Block
    基于 dfs 时间戳的 Tarjan 算法
    dfn[u]:= u 的时间戳, low[u]:= u 及其后代能连回的最早的祖先的时间戳
  • 边双连通分量 Edgebiconnected Component
    基于 dfs 时间戳的 Tarjan 算法
    dfn[u]:= u 的时间戳, low[u]:= u 及其后代能连回的最早的祖先的时间戳
    边双连通缩点其实就是留下桥,把所有桥边构成了新图,新图是一棵树
//Tarjan-bcc
vector<int> G[N];
int dfn[N], low[N], in[N], id[N], bcc, dfsNum;
int stk[N], top;

void tarjan(int u, int f) {
    dfn[u] = low[u] = ++dfsNum;
    stk[++top] = u;
    in[u] = true;
    for(int v : G[u]) {
        if(v == f) continue;
        if(!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
        } else low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u]) {
        ++bcc;
        while(true) {
            int v = stk[top--];
            in[v] = false;
            id[v] = bcc;
            if(v == u) break;
        }
    }
}

void init() {
    bcc = dfsNum = 0;
    memset(dfn, 0, sizeof dfn);
    tarjan(root, -1);
}

你可能感兴趣的:(连通图小结)