2017. 8. 7. 23:36ㆍ[개발] 지식/알고리즘 문제풀이
깊이 우선 탐색(depth-first search, DFS)은 그래프의 모든 정점을 발견하는 가장 단순하고 고전적인 방법. 가능한 한 그래프 안으로 '깊이' 들어가려고 시도하며, 막힌 정점에 도달하지 않는 한 뒤로 돌아가지 않는다.
구현 (Java)
public class Code28_1 {
// 노드 개수 (임시 값)
public static int N = 30;
// 방문 여부
public static boolean[] visited = new boolean[N];
// 각 노드 별 인접한 노드들의 List (인접 List)
public static List<Integer>[] adj = new List[N];
public static void main(String[] args) {
// TODO Auto-generated method stub
// List 배열에 ArrayList 할당
for(int i=0; i<adj.length; i++){
adj[i] = new ArrayList<Integer>();
}
dfsAll();
}
public static void dfs(int here){
visited[here] = true;
for(int i=0; i<adj[here].size(); ++i){
int there = adj[here].get(i);
// 아직 방문한 적 없다면 방문한다.
if(!visited[there]){
dfs(there);
}
}
}
public static void dfsAll(){
// visited를 모두 false로 초기화한다.
Arrays.fill(visited, false);
// 모든 정점을 순회하면서, 아직 방문한 적 없으면 방문한다.
for(int i=0; i<adj.length; ++i){
if(!visited[i]){
dfs(i);
}
}
}
}
깊이 우선 탐색의 시간 복잡도
인접 리스트 사용 : dfs()는 한 정점마다 한 번씩 호출되므로, 정확히 |V|번 호출된다. dfs() 한 번의 수행 시간은 모든 인접 간선을 검사하는 for문에 지배되는데, 모든 정점에 대해 dfs()를 수행하고 나면 모든 간선을 정확히 한 번(방향 그래프의 경우) 혹은 두 번 (무향 그래프의 경우) 확인함을 알 수 있다. 따라서 깊이 우선 탐색의 시간 복잡도는 O(|V| + |E|)가 된다.
인접 행렬을 사용하는 겨웅에도 dfs() 호출 횟수는 그대로 |V|번이다. 하지만 dfs() 내부에서 다른 모든 정점을 순회하며 두 정점 사이에 간선이 있는가를 확인해야 하기 때문에 한 번의 실행에 O(|V|)의 시간이 든다. 따라서 O(|V^2|)
활용1 : 두 정점이 서로 연결되어 있는지 확인하기
활용2 : 연결된 부분집합의 개수 세기
무향 그래프가 간선으로 서로 연결되지 않은 몇 개의 조각으로 쪼개져 잇는 경우, 각 연결된 정점들의 부분집합을 컴포넌트(component) 혹은 요소라고 부른다. dfs()는 시작한 정점에서 갈 수 잇는 모든 정점들을 방문하기 때문에, dfsAll()에서 dfs() 호출 횟수를 센다.
활용3 : 위상정렬
위상 정렬은 깊이 우선 탐색으로 풀 수 있는 가장 유명한 문제들 중 하나. 위상 정렬은 의존성이 있는 작업들의 주어질 때, 이들을 어떤 순서로 수행해야 하는지 계산해 준다.
각 작업을 정점으로 표현하고, 작업 간의 의존 관계를 간선으로 표현한 방향 그래프를 의존성 그래프(dependency graph)라고 한다. 작업 v가 u에 의존한다면, 의존성 그래프는 간선(u, v)를 포함하게 된다.
의존성 그래프에서 가장 먼저 알 수 잇는 특성은 그래프에 사이클이 없다는 점... 따라서 이 그래프(위상정렬 그래프)는 사이클 없는 방향 그래프, DAG가 된다.
위상 정렬을 구현하는 가장 직관적인 방법은 들어오는 간선이 하나도 없는 정점들을 하나씩 찾아서 정렬 결과의 뒤에 붙이고, 그래프에서 이 정점을 지우는 과정을 반복하는 것.
깊이 우선 탐색을 이용하면 더 간단하게 문제를 해결할 수 있다. dfsAll()을 수행하며 dfs()가 종료할 때마다 현재 정점의 번호를 기록하는 것. dfsAll()이 종료한 뒤 기록된 순서를 뒤집으면 위상 정렬 결과를 얻을 수 있다. 따라서 dfs()가 늦게 종료한 정점일 수록 정렬 결과의 앞에 온다.
* 알고리즘 문제해결전략 2권
'[개발] 지식 > 알고리즘 문제풀이' 카테고리의 다른 글
[DFS] 오일러 서킷 (1) | 2017.08.20 |
---|---|
[DFS] 문제 : 고대어 사전 (0) | 2017.08.07 |
(작성중)Dynamic Programming (0) | 2017.06.26 |
소수들의 곱셈 (0) | 2017.06.19 |
[문제] 헬스보이 (0) | 2017.01.31 |