본문으로 건너뛰기
김신건의 로그

강한 연결 요소 (SCC)

· 수정 · 📖 약 5분 · 1,294자/단어 #algorithm #graph #scc #tarjan #kosaraju
scc, strongly connected component, 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)

  1. 원래 그래프 G 를 DFS 하며 post-order (탐색 종료 순서) 를 스택에 쌓는다.
  2. 간선을 역방향으로 뒤집은 그래프 G^T 를 만든다.
  3. 스택의 역순 (post-order 역순) 으로 G^T 를 DFS. 한 번의 DFS 가 하나의 SCC.

시간 O(V + E), 구현 간단. 역방향 그래프 구축 필요.

  1. DFS 를 돌며 각 정점에 발견 시간 (disc)lowlink 를 기록.
  2. lowlink[u] = min(disc[u], lowlink[v] for v in subtree[u], disc[w] for back edge u → w).
  3. 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";
}
stdin
7 9
1 2
2 3
3 1
3 4
4 5
5 6
6 4
7 6
7 5
결과
SCC 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

복잡도

항목
KosarajuO(V + E) 시간, O(V + E) 공간
TarjanO(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 2150Strongly Connected Component47.2%kokoa-lab
BOJ 4196도미노46.8%kokoa-lab
BOJ 3977축구 전술38.5%kokoa-lab
BOJ 112802-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) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 가 있으면 정렬 결…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기