Strongly connected component를 다뤄 봅시다.
SCC를 사용하는 문제 1
이번 문제는 이제 각 도미노 블록의 배치가 주어졌을 때, 모든 블록을 넘어뜨리기 위해 손으로 넘어뜨려야 하는 블록 개수의 최솟값을 구하는 문제이다.
SCC를 찾는 알고리즘은 크게 이 두가지 방법이다.
- 타잔 알고리즘
모든 정점에 대해 DFS를 수행하여 SCC를 찾는 알고리즘으로, 코사라주 알고리즘에 비해 적용이 더 쉽다고 한다.
① 인접 정점에 방문하며 자기 자신을 스택에 넣고, 재귀적으로 DFS를 수행한다.
② 인접 정점에 방문했지만, 아직 처리중인 상태일 경우, 작은 값으로 부모값을 갱신한다.
③ 부모 노드의 DFS가 끝난 경우에는, 자신의 id값이 스택에서 나올 때까지 스택에 있는 노드들을 pop하고 scc 배열에 추가한다.
④ 만들어진 하나의 scc를 전체 SCC 배열에 추가한다.
(구현이 더 어렵지만, 활용도는 더 높다고 한다.)
- 코사라주 알고리즘
주어진 방향 그래프의 역방향 그래프와 스택을 사용하여 SCC를 구하는 알고리즘이다. 방향, 역방향 그래프가 동일한 SCC를 구성한다는 것을 이용한 방법이다.
① 주어지는 방향 그래프와 그 그래프의 역방향 그래프를 만든다.
② 정점을 담을 스택을 만들고 임의의 정점부터 DFS를 수행한다.
③ DFS가 끝나는 순서대로 스택에 삽입하고, 아직 방문하지 않은 정점에 대해 DFS를 수행한다.
④ 모든 정점이 스택에 담긴 후에는 스택을 pop하여 나오는 정점부터 역방향 그래프에서 DFS를 수행한다.. 이미 방문한 정점은 pop만 시행한다.
⑤ 이때 탐색되는 모든 정점을 SCC로 묶는다.
이것을 스택이 빌 때까지 진행한다.
(타잔 알고리즘에 비해 구현이 더 쉬운 편이라고 한다.)
다음에 더 자세히 정리해봐야 겠다..
Java / Python
import java.io.*;
import java.util.*;
public class Main {
static int size, num;
static ArrayList<Integer>[] graph, result;
static int[] order, scc_arr;
static boolean[] visit; // SCC 확정된 정점 확인
static Stack<Integer> stack;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
while (T-- > 0) {
st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken()); // 정점의 개수
int M = Integer.parseInt(st.nextToken()); // 간선의 개수
graph = new ArrayList[N + 1];
result = new ArrayList[N + 1];
for (int i = 0; i < N + 1; i++) {
graph[i] = new ArrayList<>();
result[i] = new ArrayList<>();
}
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
graph[x].add(y);
}
scc_arr = new int[N + 1];
order = new int[N + 1];
visit = new boolean[N + 1];
size = num = 0;
stack = new Stack<>();
for (int i = 1; i < N + 1; i++) {
if (order[i] == 0)
SCC(i);
}
boolean[] indegree = new boolean[size];
for (int i = 1; i < N + 1; i++) {
int s = graph[i].size();
for (int j = 0; j < s; j++) {
int end = graph[i].get(j);
if (scc_arr[end] != scc_arr[i])
indegree[scc_arr[end]] = true;
}
}
int cnt = 0;
for (int i = 0; i < size; i++) {
if (!indegree[i])
cnt++;
}
bw.write(cnt + "\n");
}
bw.flush();
bw.close();
br.close();
}
private static int SCC(int idx) {
order[idx] = ++num;
stack.add(idx);
int root = order[idx];
for (int i = 0; i < graph[idx].size(); i++) {
int temp = graph[idx].get(i);
if (order[temp] == 0)
root = Math.min(root, SCC(temp));
else if (!visit[temp])
root = Math.min(root, order[temp]);
}
if (root == order[idx]) {
while (true) {
int top = stack.pop();
result[size].add(top);
scc_arr[top] = size;
visit[top] = true;
if (top == idx)
break;
}
size++;
}
return root;
}
}
import sys
sys.setrecursionlimit(10**6)
input = sys.stdin.readline
# 정방향 dfs, dfs 가 종료되는 노드를 stack에.
def dfs(node, visit, stack):
visit[node] = 1
for now in graph[node]:
if visit[now] == 0:
dfs(now, visit, stack)
stack.append(node)
# 역방향 dfs, 탐색하는 순서대로 stack에.
def reverse_dfs(node, visit, stack):
visit[node] = 1
ids[node] = idx
stack.append(node)
for now in reverse_graph[node]:
if visit[now] == 0:
reverse_dfs(now, visit, stack)
T = int(input())
for _ in range(T):
N, M = map(int, input().split())
graph = [[] for _ in range(N + 1)]
reverse_graph = [[] for _ in range(N + 1)]
for _ in range(M):
x, y = map(int, input().split())
# 정방향 그래프, 역방향 그래프 추가
graph[x].append(y)
reverse_graph[y].append(x)
stack = []
visit = [0] * (N + 1)
# 모든 노드에서 dfs를 수행.
for i in range(1, N + 1):
if visit[i] == 0:
dfs(i, visit, stack)
idx = 0
ids = [-1] * (N + 1)
visit = [0] * (N + 1)
result = []
while stack:
# pop되는 요소에서 역방향 dfs, scc를 결과에.
ssc = []
node = stack.pop()
if visit[node] == 0:
idx += 1
reverse_dfs(node, visit, ssc)
result.append(sorted(ssc))
scc_indegree = [0] * (idx + 1)
for i in range(1, N + 1):
for ed in graph[i]:
if ids[i] != ids[ed]:
scc_indegree[ids[ed]] += 1
cnt = 0
for i in range(1, len(scc_indegree)):
if scc_indegree[i] == 0:
cnt += 1
print(cnt)