图的连通性与连通分量——有向图的强连通分量SCC,缩点及无向图的双连通分量BCC,桥,衔接点

1.图的连通性与连通分量

无向图中若任意两个顶点都是可达的,则图是连通

有向图中若任意两个顶点都可以到达,则图是强连通

图的连通分量是顶点在“从......可达”关系下的等价类。即可以理解为其一个子图,所有的连通分量构成图的一个划分。

对于判断无向图连通性,直接用并查集(Union-and-FindSet)维护或者利用bfs、dfs即可

而有向图的连通性,根据起点选择不同结果不同,在起点处bfs、dfs即可判断连通性

2.强连通分量SCC

问题:将有向图分解为强连通分量

Korasaju算法

Korasaju算法利用了图G=(V,E)在拓扑排序后连通分量之间的关系

图的连通性与连通分量——有向图的强连通分量SCC,缩点及无向图的双连通分量BCC,桥,衔接点_第1张图片

上图有{A,B,C}、{D,F}、{E,G}、{H}四个强连通分量,编号为1,2,3,4

可以看到在下方的拓扑排序中强连通分量被排为了1-->3-->2-->4

引理1:设C_1,C_2为有向图G=(V,E)的两个不同强连通分量,若(u,v) \in E, u \in C_1, v \in C_2,那么C_1最后完成搜索。换句话说C_1在拓扑排序中一定有一个点在C_2左侧。

那么在转置图中由于C_1,C_2为不同强连通分量且有C_1C_2的一条边,那么转置图中一定没有C_1C_2的一条边,从而可以推证算法的准确性。

如果按照{H}-->{D,F}-->{E,G}-->{A,B,C}的顺序访问此图(即转置图G^T=(V,E^T)),即可以按次序分别标记4个强连通分量

算法流程:先将原图拓扑排序,从左向右访问转置图G^T=(V,E^T),每次访问得到一个连通块即为一个新的强连通分量

缩点:如果以每个强连通分量访问时已统计的强连通分量的数量cnt\_scc作为其强连通分量的编号,那么图可以化解为一个DAG图,DAG图中每条边可以在访问时直接加入

复杂度分析:空间上该算法需要基础存边\Theta (V+E),额外转置图\Theta (V+E),拓扑排序的栈\Theta(V),总空间为\Theta (V+E)

时间上拓扑排序一次\Theta (V+E),搜索转置图一次\Theta (V+E),总时间复杂度为\Theta (V+E)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAXN = 2e5 + 5;
int N, M;
struct edge {
  int next, to;
} e[MAXN], e_T[MAXN], e_scc[MAXN], e_scc_T[MAXN];
int head[MAXN], head_T[MAXN], head_scc[MAXN], head_scc_T[MAXN];
int cnt_adj, cnt_adj_T, cnt_adj_scc, cnt_adj_scc_T;
void add_edge(edge *, int *, int &, int, int);
bool vis[MAXN];
stack Topo;
void DFS(int op);//op=1时进行拓扑排序,op=2时寻找scc
void dfs(int u, int op);
int cnt_scc, tag_scc[MAXN], siz_scc[MAXN];

int main(){
  scanf("%d%d", &N, &M);
  for(int i = 1; i <= M; i++){
    int x, y;
    scanf("%d%d", &x, &y);
    add_edge(e, head, cnt_adj, x, y);
    add_edge(e_T, head_T, cnt_adj_T, y, x);
  }
  DFS(1), DFS(2);
  printf("%d\n", cnt_scc);
  for(int i = 1; i <= N; i++)
    printf("%d\n", tag_scc[i]);
  return 0;
}

void DFS(int op){
  memset(vis, 0, sizeof(vis));
  if(op == 1 || op == 2){
    for(int i = 1; i <= N; i++){
      int j;
      if(op == 1)
        j = i;
      else if(op == 2){
        j = Topo.top();
        Topo.pop();
      }
      if(!vis[j]){
        if(op == 2)
          cnt_scc++;
        dfs(j, op);
      }
    }
  }
}

void dfs(int u, int op){
  vis[u] = true;
  if(op == 1){
    for(int i = head[u]; i; i = e[i].next){
      int v = e[i].to;
      if(!vis[v])
        dfs(v, op);
    }
    Topo.push(u);
  }else if(op == 2){
    tag_scc[u] = cnt_scc;
    siz_scc[cnt_scc]++;
    for(int i = head_T[u]; i; i = e_T[i].next){
      int v = e_T[i].to;
      if(!vis[v])
        dfs(v, op);
      else if(tag_scc[v] != tag_scc[u]){
        add_edge(e_scc, head_scc, cnt_adj_scc, tag_scc[v], tag_scc[u]);//转置图中有u->v的边,那么原图即为v->u的边
        add_edge(e_scc_T, head_scc_T, cnt_adj_scc_T, tag_scc[u], tag_scc[v]);
      }
    }
  }
}

void add_edge(edge *e, int *head, int &cnt, int x, int y){
  e[++cnt].to = y;
  e[cnt].next = head[x];
  head[x] = cnt;
}

Tarjan算法

 

 

你可能感兴趣的:(图论-连通分量)