Computer Science/알고리즘

너비 우선 탐색 (Breadth-First Search)

조용우 2021. 6. 9. 16:50

그래프 자료구조를 탐색하는 두 가지 알고리즘이 있다.

너비 우선 탐색(BFS)깊이 우선 탐색(DFS)이다.

 

BFS란?

너비 우선 탐색은 시작점으로부터 인접한 꼭짓점들을 탐색하는 방법이다. 시작점으로부터 가까운 점들 먼저 탐색하기에 뻗어나가는 모습이 된다.

 

BFS를 사용하는 경우는, 두 점 사이의 최단경로를 찾을 때 사용된다. 시작점으로부터 가까운 점들을 우선으로, 너비를 이용하여 탐색하기에 한 개 이상의 경로가 존재하더라도, 경로가 존재하기만 한다면 최단경로를 찾을 수 있다.

 

대신 BFS는 재귀호출을 할 수 없기에 탐색할 꼭짓점들을 큐(Queue)에 담기 위한 더 큰 저장공간이 필요하다. 또한 꼭짓점의 수가 증가할수록 탐색해야 하는 꼭짓점의 수 또한 증가하기에, 꼭짓점이 많은 그래프에서 실행할 때는 많은 시간이 소요될 수 있다.


PSEUDOCODE

BFS pseudocode Introduction to algorithms 3rd Edition

BFS 큐(queue)를 이용해 구현할 수 있다.

 

아래 수도코드에서는 한 꼭짓점마다 색(color), 거리(d), 부모(π)의 세가지 정보를 가진다.

 

 WHITE, GRAY, BLACK의 세가지 색이 있는데 각각

WHITE = 아직 탐색되지 않은 꼭짓점

GRAY = 탐색중인 꼭짓점

BLACK = 탐색 완료된 꼭짓점

이다. 즉 WHITE 큐에 한번도 들어가 보지 않은 꼭짓점, GRAY 큐에 들어가 있는 꼭짓점, BLACK 큐에 들어갔다 나온 꼭짓점이다.

 

거리는 시작점 s로부터 해당 꼭짓점까지의 거리를 나타낸다.

 

부모는 해당 꼭짓점을 탐색한 꼭짓점을 나타낸다.

 

위 코드를 하나씩 보면, 우선 입력 변수로 그래프 G와 시작 꼭짓점(vertex) s를 입력받는다. 

1-4. s 를 제외한 그래프의 모든 꼭짓점의 색(color), 거리(d), 부모(π)를 초기화 한다.

5-7. s의 색을 회색, 거리를 0, 부모를 NIL로 지정한다.

8-9. 빈 큐를 생성하고 s를 큐에 삽입한다.

10-17. 큐에서 s를 꺼내고, s에 인접한 모든 하얀색의 꼭짓점에 대해 색을 회색으로 변경, 거리를 1 증가, 부모를 s로 만든다. 그리고 해당 작업이 완료된 꼭짓점들을 큐에 넣는다.

 

Time Complexity

회색(GRAY)은 큐에 꼭짓점이 두 번 이상 들어가는 것을 방지한다. 따라서 도달 가능한 모든 정점은 한 번씩 큐에 삽입된다. 이때 O(V)의 시간이 소요된다(모든 꼭짓점을 찾아 삽입했기에). 또한 꼭짓점이 큐에 존재할 때만 인접한 꼭짓점을 탐색하므로, directed graph에서는 최대 한 번, undirected graph에서는 최대 두 번 O(E)의 시간으로 탐색한다. 따라서 시간 복잡도 총합은 O(V+E)이다.


EXAMPLE

BFS example Introduction to algorithms 3rd Edition

자세한 설명

더보기

(a)

1. 시작점: s

2. s 제외 나머지 꼭짓점 초기화

3. s의 색을 회색, 거리를 0, 부모는 NIL로 변경

4. 큐(Q)에 s 삽입

Q [s]

(b)

1. Q에서 s 제거

2. s와 인접한 w와 r를 탐색

3. w와 r의 색을 회색, 거리를 1, 부모를 s로 변경

4. Q에 w, r 삽입

5. s를 검은색으로 변경

Q [w, r]

(c)

1. Q에서 w 제거

2. w와 인접한 t와 x를 탐색

3. t와 x의 색을 회색, 거리를 2, 부모를 w로 변경

4. Q에 t, x 삽입

5. w를 검은색으로 변경

Q [r, t, x]

(d)

1. Q에서 r 제거

2. r와 인접한 v 탐색

3. v의 색을 회색, 거리를 2, 부모를 r로 변경

4. Q에 v 삽입

5. r를 검은색으로 변경

Q [t, x, v]

(e)

1. Q에서 t 제거

2. t와 인접한 u 탐색 (x는 회색이므로 탐색하지 않음)

3. u의 색을 회색, 거리를 3, 부모를 t로 변경

4. Q에 u를 삽입

5. t를 검은색으로 변경

Q [x, v, u]

(f)

1. Q에서 x제거

2. x와 인접한 y 탐색 (u는 회색이므로 탐색하지 않음)

3. y의 색을 회색, 거리를 3, 부모를 x로 변경

4. Q에 y를 삽입

5. x를 검은색으로 변경

Q [v, u, y]

(g)

1. Q에서 v제거

2. v와 인접한 꼭짓점 없음

3. v를 검은색으로 변경

Q [u, y]

(h)

1. Q에서 u제거

2. u와 인접한 하얀색 꼭짓점 없음

3. u를 검은색으로 변경

Q [y]

(i)

1. Q에서 y제거

2. y와 인접한 하얀색 꼭짓점 없음

3. y를 검은색으로 변경

Q []

Q가 비어서 BFS 종료


BFS 코드 (Python)

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
44
45
46
graph = {
    's': ['r''w'],
    'r': ['s''v'],
    'v': ['r'],
    'w': ['s''t''x'],
    't': ['w''x''u'],
    'x': ['w''t''u''y'],
    'u': ['t''x''y'],
    'y': ['x''u'],
}
 
def bfs(graph, s):
    
    visited = dict()
    withOutParent = list()
    parent = dict()
    queue = list()
    
    visited[s] = [0"NIL"]
    queue.extend(graph[s])
    for i in range(0len(graph[s])):
        parent[ graph[s][i] ] = s
 
    while queue:
        vertex = queue.pop(0)
        if vertex not in visited:
            visited[vertex] = [visited[parent[vertex]][0+ 1, parent[vertex]] #[거리, 부모]
            
            withOutParent = graph[vertex]
            withOutParent.remove(parent[vertex])
            
            for v in withOutParent:
                if v in queue:
                    withOutParent.remove(v)
            queue.extend(withOutParent)
            
            for i in range(0len(withOutParent)):
                parent[withOutParent[i]] = vertex
            
    return visited
 
print(bfs(graph,'s'))
 
 
#출력 
{'x': [2'w'], 'v': [2'r'], 's': [0'NIL'], 't': [2'w'], 'u': [3't'], 'y': [3'x'], 'w': [1's'], 'r': [1's']}
cs

그래프는 위 예시의 그래프를 이용했다. 결과는 { 꼭짓점: [거리, 부모] } 형식으로 출력된다.