bcc, biconnected component, 이중 연결 요소, 단절점, 단절선, articulation point, bridge
정의
이중 연결 요소 (Biconnected Component, BCC) 는 무방향 그래프에서 정점 하나를 제거해도 연결성이 유지되는 최대 부분 그래프. 단절점 (articulation point) 이나 단절선 (bridge) 을 찾고, 그래프를 BCC 로 분해하는 알고리즘. 1972년 Tarjan 이 DFS low-link 기법으로 O(V+E) 선형 시간 해법 제시.
문제 상황과 동기
네트워크 신뢰성: 정점 하나 고장 시 전체 단절 가능? 어떤 간선이 bridge (제거 시 연결 요소 증가)?
naive: 모든 정점/간선 하나씩 지워보며 연결성 BFS/DFS. O(V·(V+E)) → 불가능.
Tarjan BCC: DFS 한 번에 low-link 배열 관리, O(V+E).
핵심 통찰: DFS tree 에서, 자식이 tree edge 로만 돌아올 때 그 부모가 단절점. 같은 원리로 bridge 도 O(1) 판정.
시각화
핵심 아이디어
DFS 순회 중 두 값을 추적:
disc[u]: DFS 도달 시각 (discovery time).
low[u]: u 서브트리에서 역방향 간선(back edge) 로 갈 수 있는 가장 이른 disc.
단절점 판정:
루트: 자식이 2개 이상 tree edge → 단절점.
비루트 u: 어떤 자식 v 가 low[v] >= disc[u] → u 가 단절점.
bridge 판정:
tree edge (u, v) 에서 low[v] > disc[u] → bridge.
BCC 분해:
단절점에서 그래프가 “쪼개진다.” stack 에 간선을 쌓다가 단절점 발견 시 BCC 하나씩 pop.
block-cut tree: BCC 를 블록 노드, 단절점을 컷 노드로 만든 이분 그래프. 원 그래프의 BCC 구조를 트리로 표현.
알고리즘
BCC_Tarjan(G): disc[] = -1, low[] = -1, time = 0 st = empty stack for u in V: if disc[u] < 0: dfs_bcc(u, -1)dfs_bcc(u, parent): disc[u] = low[u] = time++ children = 0 for v in adj[u]: if v == parent: continue if disc[v] < 0: children++ st.push((u, v)) dfs_bcc(v, u) low[u] = min(low[u], low[v]) # 단절점 조건 if (parent < 0 and children > 1) or (parent >= 0 and low[v] >= disc[u]): # u 는 단절점 pop BCC from stack until (u, v) # bridge 조건 if low[v] > disc[u]: # (u, v) 는 bridge elif disc[v] < disc[u]: # back edge st.push((u, v)) low[u] = min(low[u], disc[v])
구현
// BCC, 단절점/단절선 O(V+E)#include <bits/stdc++.h>using namespace std;int n, m, timer = 0;vector<int> adj[100005];int disc[100005], low[100005];bool is_cut[100005];vector<pair<int,int>> bridges;stack<pair<int,int>> st;vector<vector<pair<int,int>>> bcc_list;void dfs_bcc(int u, int p) { disc[u] = low[u] = timer++; int children = 0; for (int v : adj[u]) { if (v == p) continue; if (disc[v] < 0) { children++; st.push({u, v}); dfs_bcc(v, u); low[u] = min(low[u], low[v]); // 단절점 조건 if ((p < 0 && children > 1) || (p >= 0 && low[v] >= disc[u])) { is_cut[u] = true; vector<pair<int,int>> component; while (!st.empty()) { auto [a, b] = st.top(); st.pop(); component.push_back({a, b}); if (a == u && b == v) break; } bcc_list.push_back(component); } // bridge 조건 if (low[v] > disc[u]) { bridges.push_back({u, v}); } } else if (disc[v] < disc[u]) { st.push({u, v}); low[u] = min(low[u], disc[v]); } }}int main() { cin >> n >> m; fill(disc, disc + n, -1); for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; u--; v--; adj[u].push_back(v); adj[v].push_back(u); } for (int i = 0; i < n; i++) { if (disc[i] < 0) dfs_bcc(i, -1); } // 마지막 남은 간선들도 BCC if (!st.empty()) { vector<pair<int,int>> component; while (!st.empty()) { component.push_back(st.top()); st.pop(); } bcc_list.push_back(component); } int cut_cnt = 0; for (int i = 0; i < n; i++) if (is_cut[i]) cut_cnt++; cout << "단절점: " << cut_cnt << "\n"; cout << "단절선: " << bridges.size() << "\n"; cout << "BCC 개수: " << bcc_list.size() << "\n";}
# BCC, 단절점/단절선 O(V+E)import syssys.setrecursionlimit(200005)input = sys.stdin.readlinen, m = map(int, input().split())adj = [[] for _ in range(n)]for _ in range(m): u, v = map(int, input().split()) u -= 1; v -= 1 adj[u].append(v) adj[v].append(u)disc = [-1] * nlow = [-1] * nis_cut = [False] * nbridges = []st = []bcc_list = []timer = 0def dfs_bcc(u, p): global timer disc[u] = low[u] = timer timer += 1 children = 0 for v in adj[u]: if v == p: continue if disc[v] < 0: children += 1 st.append((u, v)) dfs_bcc(v, u) low[u] = min(low[u], low[v]) # 단절점 조건 if (p < 0 and children > 1) or (p >= 0 and low[v] >= disc[u]): is_cut[u] = True component = [] while True: a, b = st.pop() component.append((a, b)) if a == u and b == v: break bcc_list.append(component) # bridge 조건 if low[v] > disc[u]: bridges.append((u, v)) elif disc[v] < disc[u]: st.append((u, v)) low[u] = min(low[u], disc[v])for i in range(n): if disc[i] < 0: dfs_bcc(i, -1)# 마지막 남은 간선들도 BCCif st: bcc_list.append(st[:])cut_cnt = sum(is_cut)print(f"단절점: {cut_cnt}")print(f"단절선: {len(bridges)}")print(f"BCC 개수: {len(bcc_list)}")
// BCC, 단절점/단절선 O(V+E)import java.util.*;import java.io.*;public class Main { static int n, m, timer = 0; static List<Integer>[] adj; static int[] disc, low; static boolean[] isCut; static List<int[]> bridges = new ArrayList<>(); static Stack<int[]> st = new Stack<>(); static List<List<int[]>> bccList = new ArrayList<>(); static void dfsBcc(int u, int p) { disc[u] = low[u] = timer++; int children = 0; for (int v : adj[u]) { if (v == p) continue; if (disc[v] < 0) { children++; st.push(new int[]{u, v}); dfsBcc(v, u); low[u] = Math.min(low[u], low[v]); // 단절점 조건 if ((p < 0 && children > 1) || (p >= 0 && low[v] >= disc[u])) { isCut[u] = true; List<int[]> component = new ArrayList<>(); while (!st.isEmpty()) { int[] edge = st.pop(); component.add(edge); if (edge[0] == u && edge[1] == v) break; } bccList.add(component); } // bridge 조건 if (low[v] > disc[u]) { bridges.add(new int[]{u, v}); } } else if (disc[v] < disc[u]) { st.push(new int[]{u, v}); low[u] = Math.min(low[u], disc[v]); } } } @SuppressWarnings("unchecked") public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); n = Integer.parseInt(st.nextToken()); m = Integer.parseInt(st.nextToken()); adj = new ArrayList[n]; disc = new int[n]; low = new int[n]; isCut = new boolean[n]; Arrays.fill(disc, -1); for (int i = 0; i < n; i++) adj[i] = new ArrayList<>(); for (int i = 0; i < m; i++) { st = new StringTokenizer(br.readLine()); int u = Integer.parseInt(st.nextToken()) - 1; int v = Integer.parseInt(st.nextToken()) - 1; adj[u].add(v); adj[v].add(u); } for (int i = 0; i < n; i++) { if (disc[i] < 0) dfsBcc(i, -1); } // 마지막 남은 간선들도 BCC if (!Main.st.isEmpty()) { List<int[]> component = new ArrayList<>(); while (!Main.st.isEmpty()) component.add(Main.st.pop()); bccList.add(component); } int cutCnt = 0; for (boolean b : isCut) if (b) cutCnt++; System.out.println("단절점: " + cutCnt); System.out.println("단절선: " + bridges.size()); System.out.println("BCC 개수: " + bccList.size()); }}
stdin
6 71 22 33 13 44 55 66 4
결과
단절점: 1단절선: 1BCC 개수: 3
복잡도
항목
값
시간 (최선/평균/최악)
O(V + E)
공간
O(V + E) (DFS stack + adjacency list)
전처리
없음 (DFS 한 번)
block-cut tree
BCC 를 블록 노드로, 단절점을 컷 노드로 만든 이분 그래프.
원 그래프 정점 v → 컷 노드 (단절점) 또는 블록 내부.
간선: 컷 노드 ↔ 블록 노드 (v 가 속한 BCC 들).
성질:
트리 (사이클 없음).
경로 쿼리, LCA 등이 가능.
예: 두 정점 간 경로에 몇 개의 단절점?
변형
단절점만: 루트 조건 + low[v] >= disc[u].
bridge 만: low[v] > disc[u].
BCC 분해: stack 으로 간선 모아 단절점에서 pop.
3-edge-connected component: bridge 를 모두 제거한 뒤 남은 연결 요소. 간선 2개 제거에도 연결.
함정
1. parent 중복 간선
무방향 그래프에서 (u, v) 를 두 번 (u→v, v→u) 저장. DFS 시 v == parent skip 필요. 멀티 엣지가 있으면 parent 한 번만 skip 해야 하므로 카운터로.
💬 댓글