Computer >> 컴퓨터 >  >> 프로그래밍 >> 프로그래밍

타잔(Tarjan) 알고리즘으로 방향 그래프의 강한 연결 요소(SCC) 찾기

타잔(Tarjan) 알고리즘이란?

타잔(Tarjan) 알고리즘은 방향 그래프(directed graph)에서 강한 연결 요소(Strongly Connected Component, SCC)를 찾는 데 사용되는 대표적인 그래프 알고리즘입니다. 이 알고리즘의 가장 큰 장점은 단 한 번의 DFS(깊이 우선 탐색)만으로 그래프 내 모든 강한 연결 요소를 구할 수 있다는 점입니다.

타잔(Tarjan) 알고리즘으로 방향 그래프의 강한 연결 요소(SCC) 찾기

DFS 탐색을 수행하면 그래프의 DFS 트리(포레스트)를 얻을 수 있습니다. 이 DFS 트리로부터 강한 연결 요소들을 도출하게 되며, 어떤 서브트리의 루트가 발견되면 해당 서브트리 전체를 하나의 강한 연결 요소로 출력할 수 있습니다.

핵심 아이디어

타잔 알고리즘은 탐색 과정에서 각 정점에 대해 다음 정보를 유지합니다.

  • disc[]: 정점이 발견된 순서(발견 시간)
  • low[]: 해당 정점에서 도달 가능한 정점 중 가장 작은 발견 시간
  • 스택(stack): 현재 탐색 경로에 있는 정점들을 저장
  • stackItemFlag[]: 각 정점이 스택에 포함되어 있는지 여부를 추적

탐색 중 low[u] == disc[u]가 성립하는 순간, 정점 u는 하나의 강한 연결 요소의 루트입니다. 이때 스택에서 u가 나올 때까지 정점들을 차례로 꺼내어 출력하면, 그 집합이 곧 하나의 강한 연결 요소가 됩니다.

입력 및 출력

입력:
그래프의 인접 행렬
0 0 1 1 0
1 0 0 0 0
0 1 0 0 0
0 0 0 0 1
0 0 0 0 0

출력:
강한 연결 요소:
4
3
1 2 0

알고리즘

findComponent(u, disc, low, stack, stackItemFlag)

입력: 시작 노드 u, 발견 시간을 저장할 disc 배열, 서브트리 정보를 담을 low 배열, 정점을 보관할 스택, 스택 포함 여부를 추적하는 stackItemFlag 배열

출력: 강한 연결 요소(SCC)를 화면에 출력

Begin
    time := 0        // time 값은 다음 함수 호출 시 초기화되지 않음(static)
    set disc[u] := time+1 and low[u] := time + 1
    time := time + 1
    push u into stack
    stackItemFlag[u] := true

    for all vertex v which is adjacent with u, do
       if v is not discovered, then
          findComponent(v, disc, low, stack, stackItemFlag)
          low[u] = minimum of low[u] and low[v]
       else if stackItemFlag[v] is true, then
          low[u] := minimum of low[u] and disc[v]
    done

    poppedItem := 0
    if low[u] = disc[u], then
       while u is not in the stack top, do
          poppedItem := top of stack
          display poppedItem
          stackItemFlag[poppedItem] := false
          pop item from stack
       done

       poppedItem := top of stack
       display poppedItem
       stackItemFlag[poppedItem] := false
       pop item from stack
End

strongConComponent(graph)

입력: 주어진 그래프

출력: 그래프의 모든 강한 연결 요소

Begin
    initially set all items in the disc array to undiscovered
    for all elements in low to φ
    and mark no item is stored into the stack

    for all node i in the graph, do
       if disc[i] is undiscovered, then
          findComponent(i, disc, low, stack, stackItemFlag)
End

C++ 구현 예제

#include<iostream>
#include<stack>
#define NODE 5
using namespace std;

int graph[NODE][NODE] = {
   {0, 0, 1, 1, 0},
   {1, 0, 0, 0, 0},
   {0, 1, 0, 0, 0},
   {0, 0, 0, 0, 1},
   {0, 0, 0, 0, 0}
};

int min(int a, int b) {
   return (a<b)?a:b;
}

void findComponent(int u, int disc[], int low[], stack<int>&stk, bool stkItem[]) {
   static int time = 0;
   disc[u] = low[u] = ++time;     // 발견 시간과 low 값 초기화
   stk.push(u);
   stkItem[u] = true;     // u가 스택에 있음을 표시

   for(int v = 0; v<NODE; v++) {
      if(graph[u][v]) {
         if(disc[v] == -1) {   // v를 아직 방문하지 않은 경우
            findComponent(v, disc, low, stk, stkItem);
            low[u] = min(low[u], low[v]);
         } else if(stkItem[v])     // v가 스택에 있는 경우, u의 low 값 갱신
            low[u] = min(low[u], disc[v]);
      }
   }

   int poppedItem = 0;
   if(low[u] == disc[u]) {   // u가 SCC의 루트인 경우
      while(stk.top() != u) {
         poppedItem = stk.top();
         cout << poppedItem << " ";
         stkItem[poppedItem] = false;     // 팝된 항목으로 표시
         stk.pop();
      }
      poppedItem = stk.top();
      cout << poppedItem << endl;
      stkItem[poppedItem] = false;
      stk.pop();
   }
}

void strongConComponent() {
   int disc[NODE], low[NODE];
   bool stkItem[NODE];
   stack<int> stk;

   for(int i = 0; i<NODE; i++) {     // 모든 배열 초기화
      disc[i] = low[i] = -1;
      stkItem[i] = false;
   }

   for(int i = 0; i<NODE; i++)     // 아직 방문하지 않은 노드에서 탐색 시작
      if(disc[i] == -1)
         findComponent(i, disc, low, stk, stkItem);
}

int main() {
   strongConComponent();
}

실행 결과

4
3
1 2 0

시간 복잡도

타잔 알고리즘은 각 정점과 간선을 정확히 한 번씩 처리하므로 시간 복잡도는 O(V + E)입니다. 여기서 V는 정점의 수, E는 간선의 수를 의미합니다. 두 번의 DFS를 필요로 하는 코사라주(Kosaraju) 알고리즘과 달리, 타잔 알고리즘은 한 번의 DFS만으로 SCC를 구할 수 있어 실무에서도 널리 활용됩니다.