강한 연결 요소 (SCC)
정의
강한 연결 요소 (Strongly Connected Component, SCC) 는 방향 그래프에서 서로 도달 가능한 정점들의 최대 부분 집합. SCC 내 임의 두 정점 u, v 에 대해 u → v, v → u 경로가 모두 존재.
방향 그래프를 SCC 로 축약하면 DAG 가 된다 (condensation graph).
문제 상황과 동기
2-SAT, 게임 이론 (cycle 감지), 웹 페이지 랭킹 (PageRank 의 SCC 축약), 의존성 그래프 단순화 등 방향 그래프의 순환 구조 분석.
- naive: 모든 정점 쌍에 BFS, O(V² (V + E)). 비현실적.
- Tarjan / Kosaraju: 한 번의 DFS (또는 두 번) 로 O(V + E).
핵심 통찰: DFS tree 의 lowlink (Tarjan) 또는 역방향 그래프의 post-order (Kosaraju) 가 SCC 를 자연스럽게 구분.
시각화
핵심 아이디어
알고리즘 1: Kosaraju (두 번 DFS)
- 원래 그래프 G 를 DFS 하며 post-order (탐색 종료 순서) 를 스택에 쌓는다.
- 간선을 역방향으로 뒤집은 그래프 G^T 를 만든다.
- 스택의 역순 (post-order 역순) 으로 G^T 를 DFS. 한 번의 DFS 가 하나의 SCC.
시간 O(V + E), 구현 간단. 역방향 그래프 구축 필요.
알고리즘 2: Tarjan (한 번 DFS + lowlink)
- DFS 를 돌며 각 정점에 발견 시간 (disc) 과 lowlink 를 기록.
- lowlink[u] = min(disc[u], lowlink[v] for v in subtree[u], disc[w] for back edge u → w).
- u 가 루트 (disc[u] = lowlink[u]) 이면 스택에서 u 까지 pop, 하나의 SCC.
시간 O(V + E), 한 번 DFS, 구현 약간 복잡.
알고리즘
Kosaraju
kosaraju_scc(G):
# 1. 원래 그래프 DFS, post-order 스택
visited = [false] × V
stack = []
for v in V:
if not visited[v]:
dfs1(v)
# 2. 역방향 그래프
G_T = reverse(G)
# 3. stack 역순으로 G_T DFS
visited = [false] × V
scc_id = 0
for v in reverse(stack):
if not visited[v]:
dfs2(v, scc_id)
scc_id += 1
dfs1(v):
visited[v] = true
for u in neighbors[v]:
if not visited[u]:
dfs1(u)
stack.push(v)
dfs2(v, id):
visited[v] = true
scc[v] = id
for u in neighbors_T[v]:
if not visited[u]:
dfs2(u, id)
Tarjan
tarjan_scc(G):
disc = [0] × V
lowlink = [0] × V
on_stack = [false] × V
stack = []
time = 0
scc_id = 0
for v in V:
if disc[v] = 0:
tarjan_dfs(v)
tarjan_dfs(v):
disc[v] = lowlink[v] = ++time
stack.push(v)
on_stack[v] = true
for u in neighbors[v]:
if disc[u] = 0:
tarjan_dfs(u)
lowlink[v] = min(lowlink[v], lowlink[u])
else if on_stack[u]:
lowlink[v] = min(lowlink[v], disc[u])
if lowlink[v] = disc[v]:
# v 가 SCC 루트
while true:
u = stack.pop()
on_stack[u] = false
scc[u] = scc_id
if u = v: break
scc_id += 1
구현
// Kosaraju, 두 번 DFS
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> adj, radj;
vector<bool> vis;
vector<int> stk, scc_id;
int scc_cnt = 0;
void dfs1(int u) {
vis[u] = true;
for (int v : adj[u])
if (!vis[v]) dfs1(v);
stk.push_back(u);
}
void dfs2(int u, int id) {
vis[u] = true;
scc_id[u] = id;
for (int v : radj[u])
if (!vis[v]) dfs2(v, id);
}
int main() {
int v, e; cin >> v >> e;
adj.resize(v + 1);
radj.resize(v + 1);
vis.assign(v + 1, false);
scc_id.assign(v + 1, -1);
for (int i = 0; i < e; i++) {
int a, b; cin >> a >> b;
adj[a].push_back(b);
radj[b].push_back(a);
}
for (int i = 1; i <= v; i++)
if (!vis[i]) dfs1(i);
vis.assign(v + 1, false);
reverse(stk.begin(), stk.end());
for (int u : stk) {
if (!vis[u]) {
dfs2(u, scc_cnt++);
}
}
cout << "SCC count: " << scc_cnt << "\n";
for (int i = 1; i <= v; i++)
cout << "node " << i << " -> SCC " << scc_id[i] << "\n";
}7 9
1 2
2 3
3 1
3 4
4 5
5 6
6 4
7 6
7 5SCC count: 4
node 1 -> SCC 0
node 2 -> SCC 0
node 3 -> SCC 0
node 4 -> SCC 1
node 5 -> SCC 1
node 6 -> SCC 1
node 7 -> SCC 2복잡도
| 항목 | 값 |
|---|---|
| Kosaraju | O(V + E) 시간, O(V + E) 공간 |
| Tarjan | O(V + E) 시간, O(V) 공간 |
| SCC 개수 | 최악 O(V) (모든 정점이 별도 SCC) |
변형 / 활용
1. Condensation DAG
SCC 를 하나의 정점으로 축약하면 DAG. SCC 간 의존 관계 분석, 위상 정렬 가능.
condensation[u] = scc_id[u]
간선 u → v 가 scc_id[u] ≠ scc_id[v] 이면 condensation edge.
2. 2-SAT
불리언 변수 x_i 에 대해 (x_i, ¬x_i) 정점. implication graph 의 SCC. x_i 와 ¬x_i 가 같은 SCC 면 unsat, 아니면 sat. 위상 역순으로 값 할당.
3. 게임 이론
상태 그래프의 SCC. SCC 내 사이클은 무승부 (draw).
4. 웹 그래프 분석
PageRank 에서 SCC 축약으로 수렴 속도 개선.
함정
1. 1-indexed vs 0-indexed
Tarjan 의 disc, lowlink 초기값 0. 정점 번호 1-indexed 면 disc[0] 사용 안 함.
2. stack 관리
Tarjan 에서 on_stack 플래그 누락 시 cross edge 와 back edge 혼동.
3. 역방향 그래프 구축
Kosaraju 에서 radj 빌드 시 간선 방향 주의. a → b 이면 radj[b].push(a).
4. SCC 번호 순서
Tarjan 은 역위상 순서 (late SCC 가 작은 번호), Kosaraju 도 비슷. condensation DAG 만들 때 순서 확인.
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 2150 | Strongly Connected Component | 47.2% | kokoa-lab |
| BOJ 4196 | 도미노 | 46.8% | kokoa-lab |
| BOJ 3977 | 축구 전술 | 38.5% | kokoa-lab |
| BOJ 11280 | 2-SAT (3) | 39.1% | kokoa-lab |
참고
이 글의 용어 (4개)
- 2-SAT (2-Satisfiability)algorithm
- 정의 2-SAT (2-Satisfiability) 는 각 절(clause)이 정확히 두 리터럴(literal)의 OR 로 이루어진 CNF (Conjunctive Normal Fo…
- 깊이 우선 탐색 (DFS)algorithm
- 정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
- 방향 비순환 그래프 (DAG)algorithm
- 정의 방향 비순환 그래프 (Directed Acyclic Graph, DAG) 는 사이클이 없는 방향 그래프. 어떤 정점에서 출발해도 자기 자신으로 돌아오는 경로가 없다. DAG…
- 위상 정렬 (Topological Sorting)algorithm
- 정의 위상 정렬 (Topological Sorting) 은 방향 비순환 그래프 (DAG) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 가 있으면 정렬 결…
💬 댓글