Tarjan求割点与桥

参考博客:Tarjan算法(中):求割点与桥

割点与桥的概念

割点:

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。一个结点称为割点(或者割顶)当且仅当去掉该节点极其相关的边之后的子图不连通。

简单来说就是:

这些点维持着图的连通性,去掉这些点,这个连通分量就无法在维持下去,分成好几个连通分量。

桥:

一条边称为桥(或者割边)当且仅当去掉该边之后的子图不连通。

求割点

算法思想:

首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)

一、对于根节点

  • 如果有2棵即以上的子树,就是割点(因为如果去掉这个点,这两棵子树就不能互相到达)

二、对于非根节点

  • 如果节点 U 的所有孩子节点可以不通过父节点 U 而访问到 U 的祖先节点,那么说明:即使去掉节点 U 也不影响图的连通性,U不是割点

  • 如果节点U至少存在一个孩子节点,必须通过父节点 U 才能访问到 U 的祖先节点,那么去掉节点U后,顶点U的祖先节点和孩子节点就不连通了,此时 U 是一个割点

维护数组

  • dfn[] :依然表示顶点 u 被首次搜索到的序号

  • low[u] :表示顶点 u 及其子树中的点,通过非父子边(回边),能够绕到的最早的点(dfn最小)的 dfn 值(但不能通过连接 u 与其父节点的边)。这里不再是最早的祖先了,因为是无向图,所以并没有意义

  • cut[u]:表示 u 是否为割点

代码(链式前向星存图):

struct edge {
  int next;
  int to;
} E[2 * maxn];

void add(int x, int y) {
  E[cnt].to = y;
  E[cnt].next = head[x];
  head[x] = cnt++;
}

void tarjan(int u, int from) {
  DFN[u] = LOW[u] = ++idx;
  int child = 0;
  for (int i = head[u]; i != 0; i = E[i].next) {
    int nx = E[i].to;
    if (!DFN[nx]) {
      tarjan(nx, from);
      LOW[u] = min(LOW[u], LOW[nx]);
      //如果不经过u无法到达u的祖先,那么一定是割点
      if (LOW[nx] >= DFN[u] && u != from) cut[u] = true;
      if (u == from) child++;
    }
    LOW[u] = min(LOW[u], DFN[nx]);
  }
  if (child >= 2 && u == from) cut[u] = true;
}

// 主函数中:
  for (int i = 0; i < n; i++)
    if (DFN[i] == 0) tarjan(i, i);

例题:PTA L2-013 红色警报 25分(邻接矩阵存图)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int maxm = 5005;

inline int read() {
  int x = 0, k = 1; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  return x * k;
}

int n, m, k, cnt;
int low[maxm], dfn[maxm], idx;
bool cut[maxm];
int mp[maxm][maxm];

inline void tarjan(int u, int from) {
  dfn[u] = low[u] = ++idx;
  int child = 0;
  for (int v = 0; v < n; v++) {
    if (mp[u][v]) {
      if (!dfn[v]) {
  	tarjan(v, from);
  	low[u] = min(low[u], low[v]);
  	if (low[v] >= dfn[u] && u != from) cut[u] = true;
  	if (u == from) child++;
      }
      low[u] = min(low[u], dfn[v]);
    }
  }
  if (child >= 2 && u == from) cut[u] = true;
}

inline void init() {
  idx = 0;
  memset(dfn, 0, sizeof(dfn));
  memset(low, 0, sizeof(low));
  memset(cut, false, sizeof(cut));
  for (int i = 0; i < n; i++) 
    if (dfn[i] == 0) tarjan(i, i);
}

int main() {
  n = read(); m = read();
  for (int i = 1; i <= m ;i++) {
    register int u, v;
    u = read(); v = read();
    mp[u][v] = 1; mp[v][u] = 1;
  }
  k = read();
  for (int i = 1; i <= k; i++) {
    register int x; x = read();
    init();
    if (cut[x] == false) printf("City %d is lost.\n", x);
    else printf("Red Alert: City %d is lost!\n", x);
    for (int j = 0; j < n; j++) {
      mp[j][x] = 0;
      mp[x][j] = 0;
    }
  }
  if (k == n) printf("Game Over.");
  return 0;
}

求桥

桥的求法其实也是类似的,它的求法可以看成是割点的一种特殊情况,当结点 u 的子结点 v 的后代通过反向边只能连回 v,那么删除这条边(u, v)就可以使得图G非连通了。用 Tarjan 算法里面的时间戳表示这个条件,就是low[v] > dfn[u]。

int n, stamp, dfn[1005], low[1005];
int cnt, ansx[10005], ansy[10005];
vector<int> vec[1005];
int rank[1005];

void addAns(int x, int y) {
  if (x > y) swap(x, y);
  ansx[cnt] = x, ansy[cnt] = y;
  cnt++;
}

void tarjan(int index, int from) {
  int tmp;
  dfn[index] = low[index] = ++stamp;
  for (int i = 0; i < vec[index].size(); i++) {
    tmp = vec[index][i];
    if (!dfn[tmp]) {
      tarjan(tmp, index);
      low[index] = min(low[index], low[tmp]);
      if (low[tmp] > dfn[index]) addAns(index, tmp);
    }
    else if (dfn[tmp] < dfn[index] && tmp != from) {
      low[index] = min(low[index], dfn[tmp]);
    }
  }
}

例题等遇到了再补充orz

posted @ 2021-04-10 22:54  Moominn  阅读(198)  评论(0编辑  收藏  举报