홈 그래프 이론. 강한 연결 요소(SCC)
글
취소

그래프 이론. 강한 연결 요소(SCC)

강한 연결 요소

방향 그래프에서 강한 연결 요소(SCC)는 모든 정점이 서로 도달 가능한 정점들의 최대 집합입니다. 서로 도달 가능하다는 것은 양방향 도달을 뜻합니다. 같은 SCC에 속한 임의의 두 정점 u와 v에 대해 u에서 v로 가는 방향 경로와 v에서 u로 가는 방향 경로가 모두 존재합니다. 한쪽 방향으로만 경로가 있다고 해서 두 정점이 같은 요소에 속하는 것은 아닙니다.

타잔 알고리즘

타잔 알고리즘은 한 번의 깊이 우선 탐색으로 모든 SCC를 찾습니다. 아래 함수는 인접 리스트를 입력으로 받습니다. graph[u]에는 정점 u에서 나가는 간선이 도달하는 0부터 시작하는 정점 인덱스가 들어 있습니다. 반환값은 각 요소가 0부터 시작하는 정점 인덱스 배열인 배열입니다. 요소와 정점의 순서는 중요하지 않습니다.

각 정점에서 index는 발견 순서를 기록하고, lowLink는 DFS 트리 간선을 따라가고 필요한 경우 아직 활성 스택에 남아 있는 정점으로 간선을 하나 따라갔을 때 도달할 수 있는 가장 작은 발견 인덱스를 기록합니다. 자식 탐색이 끝나면 트리 간선은 자식의 lowLink 값을 전달합니다. 활성 정점으로 향하는 역방향 간선은 그 정점의 lowLink가 아니라 index를 사용합니다. 이미 스택에서 제거된 정점으로 향하는 간선은 무시합니다. 이 스택 포함 여부 규칙은 탐색이 끝난 SCC가 이후 SCC에 잘못 합쳐지는 것을 방지합니다.

lowLink[v] === index[v]이면 v는 SCC의 루트입니다. 스택에서 v가 나올 때까지 정점을 꺼내면, 꺼낸 정점들이 하나의 요소를 이룹니다. 바깥 반복문은 아직 발견하지 않은 모든 정점에서 DFS를 시작하므로 그래프의 서로 분리된 부분도 빠짐없이 처리됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function stronglyConnectedComponents(graph) {
  const n = graph.length;
  const index = Array(n).fill(-1);
  const lowLink = Array(n).fill(0);
  const onStack = Array(n).fill(false);
  const stack = [];
  const components = [];
  let nextIndex = 0;

  function visit(v) {
    index[v] = nextIndex;
    lowLink[v] = nextIndex;
    nextIndex += 1;
    stack.push(v);
    onStack[v] = true;

    for (const w of graph[v]) {
      if (index[w] === -1) {
        visit(w);
        lowLink[v] = Math.min(lowLink[v], lowLink[w]);
      } else if (onStack[w]) {
        lowLink[v] = Math.min(lowLink[v], index[w]);
      }
    }

    if (lowLink[v] === index[v]) {
      const component = [];
      let w;
      do {
        w = stack.pop();
        onStack[w] = false;
        component.push(w);
      } while (w !== v);
      components.push(component);
    }
  }

  for (let v = 0; v < n; v += 1) {
    if (index[v] === -1) visit(v);
  }

  return components;
}

정점 수를 V, 방향 간선 수를 E라고 할 때 시간 복잡도는 O(V + E), 보조 공간 복잡도는 O(V)입니다. 이 구현은 재귀를 사용하므로 호출 깊이가 O(V)까지 늘어날 수 있습니다. 매우 깊은 그래프에서는 JavaScript 실행 환경의 호출 스택 한도를 넘을 수 있으므로 반복형 구현이 필요할 수 있습니다.

이 글은 저자가 CC BY 4.0 라이선스로 배포합니다.