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

방향 비순환 그래프 (DAG)

· 수정 · 📖 약 4분 · 1,150자/단어 #algorithm #graph #dag #topological-sorting
dag, directed acyclic graph, DAG, 방향 비순환 그래프

정의

방향 비순환 그래프 (Directed Acyclic Graph, DAG) 는 사이클이 없는 방향 그래프. 어떤 정점에서 출발해도 자기 자신으로 돌아오는 경로가 없다.

DAG 는 위상 정렬이 가능하며, DP, 일정 관리, 의존성 해결 등 광범위하게 쓰인다.

문제 상황과 동기

선수 과목, 빌드 의존성, 프로젝트 작업 순서, 상속 관계, Git commit 히스토리 등 일방향 의존 이 있고 순환하면 안 되는 구조.

  • naive: 그래프를 순회하며 사이클 체크. O(V + E) 이지만 구조적 속성 미활용.
  • DAG 인식: 위상 정렬, DP, condensation 등 알고리즘이 O(V + E) 에 작동.

핵심 통찰: DAG 는 partial order 를 나타내며, 위상 순서는 그 total order extension. DP 는 의존성 순서로 자연스럽게 전개.

시각화

핵심 아이디어

속성

  1. 위상 정렬 존재: 모든 간선 u → v 에서 u 가 v 보다 앞에 오는 순서.
  2. 사이클 없음: DFS 로 back edge 가 없음을 확인.
  3. DP 적용 가능: 위상 순서로 dp[v] 를 채우면 의존성 만족.
  4. condensation: 임의 방향 그래프의 SCC 를 축약하면 DAG.

판별

DFS 중 back edge (재귀 스택에 있는 정점으로 가는 간선) 가 있으면 사이클, 없으면 DAG.

또는 위상 정렬 시도 시 결과 개수가 V 미만이면 사이클 존재.

알고리즘

DAG 판별 (DFS)

is_dag(G):
    visited = [false] × V
    rec_stack = [false] × V
    for v in V:
        if not visited[v]:
            if has_cycle_dfs(v):
                return false
    return true

has_cycle_dfs(v):
    visited[v] = true
    rec_stack[v] = true
    for u in neighbors[v]:
        if rec_stack[u]:
            return true          # back edge
        if not visited[u]:
            if has_cycle_dfs(u):
                return true
    rec_stack[v] = false
    return false

DP on DAG (최장 경로)

longest_path_dag(G, start):
    topo = topological_sort(G)
    dp[v] = -∞ for all v
    dp[start] = 0
    for v in topo:
        if dp[v] = -∞: continue
        for (v, u, w) in edges:
            dp[u] = max(dp[u], dp[v] + w)
    return max(dp)

구현

// DFS back edge 체크로 DAG 판별
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> adj;
vector<bool> vis, rec;
bool has_cycle(int u) {
  vis[u] = rec[u] = true;
  for (int v : adj[u]) {
      if (rec[v]) return true;       // back edge
      if (!vis[v] && has_cycle(v)) return true;
  }
  rec[u] = false;
  return false;
}
int main() {
  int v, e; cin >> v >> e;
  adj.resize(v + 1);
  vis.assign(v + 1, false);
  rec.assign(v + 1, false);
  for (int i = 0; i < e; i++) {
      int a, b; cin >> a >> b;
      adj[a].push_back(b);
  }
  bool cycle = false;
  for (int i = 1; i <= v; i++)
      if (!vis[i] && has_cycle(i)) { cycle = true; break; }
  cout << (cycle ? "CYCLE" : "DAG") << "\n";
}
stdin
4 4
1 2
1 3
2 4
3 4
결과
DAG

복잡도

항목
DAG 판별 (DFS)O(V + E)
위상 정렬O(V + E)
DP on DAGO(V + E)
공간O(V + E)

변형 / 활용

1. 최단/최장 경로

일반 그래프는 Dijkstra / Bellman-Ford 가 필요하지만, DAG 는 위상 순서로 O(V + E) DP 가능.

음수 간선도 허용 (사이클이 없으므로).

2. 경로 개수

dp[v] = Σ dp[u] for all u → v

시작점 dp[start] = 1, 끝점까지 경로 개수.

3. Critical Path Method (CPM)

작업 일정 그래프. 최장 경로가 프로젝트 최소 완료 시간, 그 경로가 critical path.

4. Condensation DAG

임의 방향 그래프 G 의 SCC 를 하나로 축약하면 DAG. SCC 간 의존 관계 분석.

5. Reachability / Transitive Closure

DAG 에서 u → v 경로 존재 여부. 위상 순서로 DP. O(V + E) ~ O(V²) (간선 밀도).

함정

1. 사이클 미감지

DAG 임을 가정하고 위상 정렬만 돌리면 사이클 있을 때 잘못된 결과. 반드시 사이클 체크.

2. disconnected DAG

여러 컴포넌트가 있으면 모든 컴포넌트 순회. DFS / 위상 정렬 시작점 모두 체크.

3. DP 초기화

dp[v] = -∞ 로 초기화 후 reachable 한 정점만 갱신. unreachable 은 제외.

4. 음수 간선

DAG 는 음수 간선 허용 (사이클 없음). 최장 경로는 음수 간선에서 min 으로 뒤집힌다는 점 주의.

BOJ 연습 문제

번호제목정답률링크
BOJ 2252줄 세우기49.8%kokoa-lab
BOJ 1516게임 개발43.7%kokoa-lab
BOJ 1005ACM Craft33.2%kokoa-lab
BOJ 9470Strahler 순서31.5%kokoa-lab

참고

이 글의 용어 (4개)
강한 연결 요소 (SCC)algorithm
정의 강한 연결 요소 (Strongly Connected Component, SCC) 는 방향 그래프에서 서로 도달 가능한 정점들의 최대 부분 집합. SCC 내 임의 두 정점 u…
깊이 우선 탐색 (DFS)algorithm
정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
동적 계획법 (Dynamic Programming)algorithm
정의 동적 계획법 (Dynamic Programming, DP) 은 큰 문제를 작은 부분 문제로 나누고, 각 부분 문제의 최적해를 저장하여 중복 계산을 제거하는 최적화 기법. R…
위상 정렬 (Topological Sorting)algorithm
정의 위상 정렬 (Topological Sorting) 은 방향 비순환 그래프 (DAG) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 가 있으면 정렬 결…

💬 댓글

사이트 검색 / 명령어

검색

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