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

Tarjan SCC: 강한 연결 요소 O(V+E)

· 수정 · 📖 약 3분 · 1,105자/단어 #algorithm #graph #scc #tarjan
Tarjan SCC, Tarjan's SCC, 강한 연결 요소, SCC

정의

Strongly Connected Component (SCC) 는 유향 그래프에서 서로 도달 가능한 정점들의 극대 집합입니다.

즉, SCC 내 임의 두 정점 u, v 에 대해 u → v 경로와 v → u 경로가 모두 존재.

Tarjan’s algorithm 은 단일 DFS 로 SCC 를 O(V + E) 에 구합니다.

  • 각 정점에 disc (방문 순서) 와 low (도달 가능한 최소 disc) 를 부여
  • DFS 스택으로 현재 SCC 후보군 관리

문제 상황

유향 그래프에서:

  • 서로 도달 가능한 정점 집합 파악
  • 2-SAT: 함의 그래프의 SCC
  • DAG 축약: SCC 를 단일 노드로 압축 → DAG
  • 의존성 사이클 탐지
알고리즘시간DFS 횟수특징
TarjanO(V+E)1회disc/low, 스택
KosarajuO(V+E)2회역그래프, 이해 쉬움
GabowO(V+E)1회두 스택 사용

시각화

5개 정점 그래프에서 SCC 탐색. 화살표는 간선 방향.

flowchart LR
    A((A))
    B((B))
    C((C))
    D((D))
    E((E))

    A --> B
    B --> C
    C --> A
    B --> D
    D --> E
    E --> D

    subgraph SCC1["SCC: {A, B, C}"]
        A
        B
        C
    end
    subgraph SCC2["SCC: {D, E}"]
        D
        E
    end

SCC 축약 후 DAG: SCC1 → SCC2.

핵심 아이디어

각 정점 v 에 두 값 부여:

  • disc[v]: DFS 방문 순서 (타임스탬프)
  • low[v]: v 의 서브트리에서 back edge / cross edge 를 이용해 도달 가능한 최소 disc

SCC 루트 조건: disc[v] == low[v] 인 정점 v 가 SCC 의 루트.

DFS 스택 (stk) 에 방문 정점 저장. 루트 감지 시 스택에서 v 까지 pop 하면 하나의 SCC.

DFS(v):
    disc[v] = low[v] = idx++
    stk.push(v); on_stack[v] = true
    for u in adj[v]:
        if not visited:   DFS(u); low[v] = min(low[v], low[u])
        elif on_stack[u]: low[v] = min(low[v], disc[u])
    // SCC 루트 감지
    if low[v] == disc[v]:
        pop from stk until v -> 하나의 SCC

알고리즘

DFS 기반 (재귀)

idx = 0, cnt = 0
disc = [-1]*V, low = [-1]*V, comp = [-1]*V
stk = [], on_stack = [false]*V

dfs(u):
    disc[u] = low[u] = idx++
    stk.append(u); on_stack[u] = true
    for v in adj[u]:
        if disc[v] == -1:
            dfs(v)
            low[u] = min(low[u], low[v])
        elif on_stack[v]:
            low[u] = min(low[u], disc[v])
    if low[u] == disc[u]:
        while true:
            v = stk.pop()
            on_stack[v] = false
            comp[v] = cnt
            if v == u: break
        cnt++

for u in 0..V:
    if disc[u] == -1: dfs(u)

반복 (스택 기반, 깊은 그래프용)

재귀 깊이 V 까지 가면 stack overflow. 명시적 스택으로 DFS 를 반복문으로 구현.

explicit_dfs(start):
    call_stack = [(start, 0)]  // (정점, adj[정점] 인덱스)
    while call_stack:
        u, i = call_stack[-1]
        if i == 0:
            disc[u] = low[u] = idx++
            stk.append(u); on_stack[u] = true
        found_child = false
        while i < len(adj[u]):
            v = adj[u][i]; i++
            if disc[v] == -1:
                call_stack[-1] = (u, i)
                call_stack.append((v, 0))
                found_child = true
                break
            elif on_stack[v]:
                low[u] = min(low[u], disc[v])
        if not found_child:
            call_stack.pop()
            if call_stack:
                parent = call_stack[-1][0]
                low[parent] = min(low[parent], low[u])
            if low[u] == disc[u]:
                // SCC pop

구현

강한 연결 요소 분리 + DAG 축약
C++ (Tarjan SCC)
#include <bits/stdc++.h>
using namespace std;

struct TarjanSCC {
  int V, idx_cnt = 0, scc_cnt = 0;
  vector<vector<int>>& adj;
  vector<int> disc, low, comp;
  vector<bool> on_stk;
  vector<int> stk;

  TarjanSCC(int V, vector<vector<int>>& adj)
      : V(V), adj(adj),
        disc(V, -1), low(V), comp(V, -1), on_stk(V, false) {}

  void dfs(int u) {
      disc[u] = low[u] = idx_cnt++;
      stk.push_back(u);
      on_stk[u] = true;
      for (int v : adj[u]) {
          if (disc[v] == -1) {
              dfs(v);
              low[u] = min(low[u], low[v]);
          } else if (on_stk[v]) {
              low[u] = min(low[u], disc[v]);
          }
      }
      if (low[u] == disc[u]) {
          while (true) {
              int v = stk.back(); stk.pop_back();
              on_stk[v] = false;
              comp[v] = scc_cnt;
              if (v == u) break;
          }
          scc_cnt++;
      }
  }

  int solve() {
      for (int u = 0; u < V; u++)
          if (disc[u] == -1) dfs(u);
      return scc_cnt;
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int V, E;
  cin >> V >> E;
  vector<vector<int>> adj(V);
  for (int i = 0; i < E; i++) {
      int u, v; cin >> u >> v; u--; v--;
      adj[u].push_back(v);
  }
  TarjanSCC tarjan(V, adj);
  int scc_cnt = tarjan.solve();
  cout << "SCC 개수: " << scc_cnt << "\n";
  // comp[v] = v 의 SCC 번호 (역위상 순서)
  for (int i = 0; i < V; i++)
      cout << "V" << i+1 << " -> SCC " << tarjan.comp[i] << "\n";

  // DAG 축약 (SCC 간 간선)
  vector<set<int>> dag(scc_cnt);
  for (int u = 0; u < V; u++)
      for (int v : adj[u])
          if (tarjan.comp[u] != tarjan.comp[v])
              dag[tarjan.comp[u]].insert(tarjan.comp[v]);
  cout << "DAG 간선:\n";
  for (int s = 0; s < scc_cnt; s++)
      for (int t : dag[s])
          cout << "SCC " << s << " -> SCC " << t << "\n";
  return 0;
}
결과
// 입력: 5 정점, 6 간선
// 1->2, 2->3, 3->1, 2->4, 4->5, 5->4
SCC 개수: 3
V1 -> SCC 2
V2 -> SCC 2
V3 -> SCC 2
V4 -> SCC 0
V5 -> SCC 0
DAG 간선:
SCC 2 -> SCC 0

복잡도

연산시간공간
Tarjan SCCO(V+E)O(V)
DAG 축약O(V+E)O(V+E)
위상 정렬 (DAG)O(V+E)O(V)

Tarjan 의 comp 번호는 역위상 순서: comp 번호가 작을수록 위상 정렬 상 나중에 처리. DAG 를 위상 순서로 처리하려면 scc_cnt - 1 - comp[v] 또는 comp 순서 반전.

함정

WARNING

comp 번호 방향 혼동: Tarjan 의 comp 번호는 DFS finish 역순 → 위상 정렬의 역방향. 2-SAT 에서 comp[x] > comp[~x] 조건 검사 시 방향이 헷갈림.

WARNING

on_stack 조건 누락: on_stack[v] 확인 없이 disc[v] != -1 만 체크하면 cross edge 를 back edge 로 잘못 처리 → low 값 오류 → SCC 오분류.

CAUTION

깊은 그래프에서 재귀 스택 오버플로: V = 10^5, 체인형 그래프 → 재귀 깊이 10^5 → default stack 2MB 초과. 반복 구현 사용 또는 ulimit -s unlimited.

  • Kosaraju 는 2번 DFS 이지만 코드가 직관적, 빠른 구현에 유리.
  • SCC 개수 = 1 이면 그래프 전체가 강연결 (strongly connected).

응용

2-SAT

변수 x 에 대해 x (참) 와 ~x (거짓) 을 정점으로 하는 함의 그래프 구성. SCC 로 해결:

  • comp[x] == comp[~x] 이면 모순 → UNSAT
  • 그렇지 않으면 SAT. comp[x] > comp[~x] 이면 x = true

DAG 최단경로 / 최장경로

SCC 축약 후 DAG 에서 DAG 위상정렬 + DP.

의존성 순환 탐지

빌드 시스템, 패키지 의존성 그래프에서 순환 = SCC 크기 > 1.

BOJ

문제설명
BOJ 2150 Strongly Connected ComponentSCC 기본
BOJ 4196 도미노SCC + DAG 축약
BOJ 11279 2-SAT - 42-SAT
BOJ 3747 완전 이분 매칭SCC 응용
BOJ 1671 상어의 저녁식사SCC 기반 최적화

관련 위키

이 글의 용어 (6개)
2-SAT (2-Satisfiability)algorithm
정의 2-SAT (2-Satisfiability) 는 각 절(clause)이 정확히 두 리터럴(literal)의 OR 로 이루어진 CNF (Conjunctive Normal Fo…
강한 연결 요소 (SCC)algorithm
정의 강한 연결 요소 (Strongly Connected Component, SCC) 는 방향 그래프에서 서로 도달 가능한 정점들의 최대 부분 집합. SCC 내 임의 두 정점 u…
깊이 우선 탐색 (DFS)algorithm
정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
방향 비순환 그래프 (DAG)algorithm
정의 방향 비순환 그래프 (Directed Acyclic Graph, DAG) 는 사이클이 없는 방향 그래프. 어떤 정점에서 출발해도 자기 자신으로 돌아오는 경로가 없다. DAG…
위상 정렬 (Topological Sorting)algorithm
정의 위상 정렬 (Topological Sorting) 은 방향 비순환 그래프 (DAG) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 가 있으면 정렬 결…
Cycle Detection: 그래프 사이클 탐지algorithm
정의 그래프에 사이클이 존재하는가, 또는 어느 사이클인가 를 찾는 문제. - 무향 그래프: 한 정점에서 출발해 다시 그 정점으로 돌아오는 경로. - 유향 그래프: 방향을 따라가며…

💬 댓글

사이트 검색 / 명령어

검색

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