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

Cycle Detection: 그래프 사이클 탐지

· 수정 · 📖 약 2분 · 954자/단어 #algorithm #graph #cycle #dfs #union-find
Cycle Detection, 사이클 탐지, cycle finding, 사이클 판별

정의

그래프에 사이클이 존재하는가, 또는 어느 사이클인가 를 찾는 문제.

  • 무향 그래프: 한 정점에서 출발해 다시 그 정점으로 돌아오는 경로.
  • 유향 그래프: 방향을 따라가며 이미 방문 중인 정점으로 돌아오는 경로.

문제 상황

위상 정렬, 의존성 검사, DAG 판별 등에서 사이클 존재 여부가 핵심이다.

  • 패키지 의존성: A -> B -> C -> A 이면 설치 불가 (순환 의존).
  • 작업 스케줄링: 작업 간 선후관계에 사이클이 있으면 스케줄 불가능.
  • 코스 선수과목: 수강 순서에 사이클이 있으면 모순.

시각화

무향 그래프 사이클

flowchart LR
    A["1"] --- B["2"]
    B --- C["3"]
    C --- D["4"]
    D --- B

2 - 3 - 4 - 2 가 사이클. Union-Find 에서 4-2 간선 삽입 시 find(4) == find(2) 로 탐지.

유향 그래프 사이클 (DFS 3색)

flowchart LR
    A2["1 (흰)"] -->|"방문"| B2["2 (회색)"]
    B2 -->|"방문"| C2["3 (회색)"]
    C2 -->|"back edge!"| B2

3 -> 2 back edge: 회색(처리 중) 정점으로 가는 간선 = 사이클.

핵심 아이디어

무향 그래프: Union-Find

간선 (u, v) 를 삽입할 때 find(u) == find(v) 이면 u, v 가 이미 같은 컴포넌트 = 사이클.

for each edge (u, v):
    if find(u) == find(v): CYCLE!
    else: union(u, v)

유향 그래프: DFS 3색

  • 흰색 (0): 미방문
  • 회색 (1): 현재 DFS 스택에 있음 (처리 중)
  • 검은색 (2): 처리 완료

회색 정점으로 향하는 간선 = back edge = 사이클 존재.

유향 그래프: Kahn’s 알고리즘

위상 정렬로 처리된 정점 수 < N 이면 사이클 존재 (일부 정점의 indegree 가 0이 되지 않음).

알고리즘

무향 그래프: Union-Find

struct DSU {
    vector<int> par, rank_;
    DSU(int n) : par(n), rank_(n, 0) { iota(par.begin(), par.end(), 0); }

    int find(int x) {
        if (par[x] != x) par[x] = find(par[x]);  // path compression
        return par[x];
    }

    bool same(int a, int b) { return find(a) == find(b); }

    void merge(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return;
        if (rank_[a] < rank_[b]) swap(a, b);
        par[b] = a;
        if (rank_[a] == rank_[b]) rank_[a]++;
    }
};

bool hasCycle_undirected(int n, vector<pair<int,int>>& edges) {
    DSU dsu(n);
    for (auto [u, v] : edges) {
        if (dsu.same(u, v)) return true;
        dsu.merge(u, v);
    }
    return false;
}

유향 그래프: DFS 3색

vector<int> color;  // 0=흰, 1=회, 2=검

bool dfs(int u, vector<vector<int>>& adj) {
    color[u] = 1;  // 회색: 처리 시작
    for (int v : adj[u]) {
        if (color[v] == 1) return true;              // back edge: 사이클
        if (color[v] == 0 && dfs(v, adj)) return true;
    }
    color[u] = 2;  // 검은색: 처리 완료
    return false;
}

bool hasCycle_directed(int n, vector<vector<int>>& adj) {
    color.assign(n, 0);
    for (int i = 0; i < n; i++)
        if (color[i] == 0 && dfs(i, adj)) return true;
    return false;
}

유향 그래프: Kahn’s 알고리즘

bool hasCycle_kahn(int n, vector<vector<int>>& adj) {
    vector<int> indeg(n, 0);
    for (int u = 0; u < n; u++)
        for (int v : adj[u]) indeg[v]++;

    queue<int> q;
    for (int i = 0; i < n; i++)
        if (indeg[i] == 0) q.push(i);

    int cnt = 0;
    while (!q.empty()) {
        int u = q.front(); q.pop(); cnt++;
        for (int v : adj[u])
            if (--indeg[v] == 0) q.push(v);
    }
    return cnt < n;  // 처리 못한 정점 있으면 사이클
}

구현

#include <bits/stdc++.h>
using namespace std;

int n, m;
vector<vector<int>> adj;
vector<int> color;  // 0=흰, 1=회, 2=검

bool dfs(int u) {
  color[u] = 1;
  for (int v : adj[u]) {
      if (color[v] == 1) return true;
      if (color[v] == 0 && dfs(v)) return true;
  }
  color[u] = 2;
  return false;
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  cin >> n >> m;
  adj.resize(n + 1);
  for (int i = 0; i < m; i++) {
      int u, v; cin >> u >> v;
      adj[u].push_back(v);
  }

  color.assign(n + 1, 0);
  bool cycle = false;
  for (int i = 1; i <= n; i++)
      if (color[i] == 0 && dfs(i)) { cycle = true; break; }

  cout << (cycle ? "YES" : "NO") << "\n";
}
stdin
4 4
1 2
2 3
3 4
4 2
결과
YES

복잡도

방법시간공간적용 대상
Union-FindO(E * alpha(N))O(N)무향 그래프
DFS 3색O(V + E)O(V)유향 그래프
Kahn’s 알고리즘O(V + E)O(V)유향 그래프

Union-Find 는 alpha(N) (역 Ackermann): 사실상 O(1). DFS 3색과 Kahn’s 는 동일 복잡도지만 용도가 다름:

  • DFS 3색: 사이클 경로 자체를 복원할 때 유리.
  • Kahn’s: 위상 정렬을 동시에 얻을 때 유리.

함정

1. 무향 그래프에서 부모 방문을 사이클로 혼동

DFS 기반 무향 그래프 사이클 탐지 시, 간선 (u, v) 에서 v 를 방문할 때 부모 방향으로 돌아가는 것을 back edge 로 착각하는 실수.

// 잘못: 부모(par)로 돌아가는 것도 사이클 취급
bool dfs(int u, int par, ...) {
    visited[u] = true;
    for (int v : adj[u]) {
        if (!visited[v]) { dfs(v, u, ...); }
        else return true;  // 부모 방문도 true -> 오류!
    }
}

// 올바름: 부모 제외
bool dfs(int u, int par, ...) {
    visited[u] = true;
    for (int v : adj[u]) {
        if (v == par) continue;  // 부모 방향 skip
        if (!visited[v]) { if (dfs(v, u, ...)) return true; }
        else return true;  // 부모 아닌 방문 완료 정점 = back edge
    }
    return false;
}

WARNING

멀티 그래프 (같은 정점 사이 간선 여러 개) 는 간선 인덱스로 부모를 추적해야 한다.

2. 유향 그래프에서 흰색/검은색 혼동

검은색 (처리 완료) 정점으로 가는 간선은 forward edge 또는 cross edge 로 사이클이 아니다. 오직 회색 (처리 중) 정점으로 가는 back edge 만 사이클.

CAUTION

방문 여부만 체크 (visited[])하면 유향 그래프에서 오답. 반드시 3색 or 스택 상태 구분.

3. Kahn’s 로 경로 복원 불가

Kahn’s 알고리즘은 사이클 존재 여부만 알 수 있고, 어떤 정점이 사이클에 포함되는지 직접 알기 어렵다. 사이클 정점 목록이 필요하면 DFS 3색으로 복원.

BOJ 연습 문제

번호제목비고
BOJ 9466텀 프로젝트유향 그래프 사이클 (DFS)
BOJ 1516게임 개발위상 정렬 + 사이클 없음 보장
BOJ 2252줄 세우기위상 정렬 기본
BOJ 10451순열 사이클무향 그래프 사이클 카운트

참고

이 글의 용어 (5개)
그래프 이론 기초 (Graph Theory)discrete-math
정의 그래프 (Graph) $G = (V, E)$ 는 정점 (vertex) 의 집합 $V$ 와 간선 (edge) 의 집합 $E$ 로 이루어진 이산 구조입니다. 간선은 정점의 쌍 …
깊이 우선 탐색 (DFS)algorithm
정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
분리 집합 (Disjoint Set, Union-Find)algorithm
정의 분리 집합 (Disjoint Set, Union-Find) 은 서로 겹치지 않는 집합들을 관리하며, 다음 두 연산을 거의 상수 시간 (amortized O(α(N)), α는…
위상 정렬 (Topological Sorting)algorithm
정의 위상 정렬 (Topological Sorting) 은 방향 비순환 그래프 (DAG) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 가 있으면 정렬 결…
Tarjan SCC: 강한 연결 요소 O(V+E)algorithm
정의 Strongly Connected Component (SCC) 는 유향 그래프에서 서로 도달 가능한 정점들의 극대 집합입니다. 즉, SCC 내 임의 두 정점 u, v 에 대…

💬 댓글

사이트 검색 / 명령어

검색

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