너비우선탐색(Breadth First Search)

너비우선탐색(BFS)은 트리나 그래프 혹은 다른 구조 상에서 탐색을 위한 알고리즘이다. 이 알고리즘은 루트 노드를 시작으로(그래프의 경우에는 임의의 노드를 루트 노드로 정하게 된다.) 다음 레벨로 가기전에 현재 레벨에서 인접한 모든 이웃노드들을 탐색한다.

더 이상 탐색할 노드가 없어 백트랙 하기 전까지 가지를 따라 가능한 한 깊이 탐색하는 깊이우선탐색과는 다르게 너비우선탐색은 해당 정점에서 인접한 정점들을 모두 방문하는 레벨 탐색 즉, 깊이보다는 넓게 탐색하는 전략을 쓴다. 이를 위해 너비우선탐색은 큐(Queue) 자료구조를 이용하여 탐색 방법을 구성하게 된다.

시간 및 공간 복잡도 분석은 많은 응용분야에 따라 다른데, 이론적인 컴퓨터 과학 분야에서 BFS는 전체 그래프를 탐색하는데 사용되기 때문에 수행 시간은 선형인 $O(|V|+|E| )$가 된다. 공간 복잡도면을 보면 BFS는 어떤 해당 정점에서 인접 정점들의 정보를 모두 큐에 저장하기 때문에, 최악의 설정으로 하나의 정점이 다른 모든 정점과 직접 연결되어 있다면 최악 $O(|V|)$의 공간을 요구한다. ($|V|$는 정점의 수, $|E|$는 간선의 수이다.)

너비우선탐색(BFS)의 시간 및 공간 상한은 깊이우선탐색(BFS)과 같기 때문에, 탐색 알고리즘의 사용면에서 두 알고리즘이 만드는 정점 방문 순서의 다름과 그 복잡성을 생각해 어느 알고리즘을 사용해야 할지 생각해야한다.

예제

다음 트리를 보고 너비우선탐색이 어떤 것인지 이해해보자.

Alt text

위에서 보이는 트리에서 정점들의 번호는 너비우선탐색을 적용한 뒤 정점들의 방문 순서와 같다.

1번 정점을 루트로 하여 탐색을 시작하면, 1번 정점을 방문하고 그 인접한 정점 2, 3, 4를 순서대로 방문한다. 그 다음 레벨의 5, 6, 7, 8을 순서대로 방문하고 그 다음 레벨 9, 10, 11, 12순으로 방문하게 된다.

너비우선탐색이 어떻게 동작하는지 알아보자.

가정

  • 현재 방문하고 있는 정점에서 인접한 왼쪽 간선을 오른쪽 간선보다 먼저 선택한다.
  • 정점들의 방문 순서를 위해 큐 자료구조를 사용한다.
  • 정점들의 방문은 중복되지 않는다.
  • 1번 정점 부터 탐색을 시작한다.

먼저 1번 정점을 루트로 하여 BFS 탐색을 시작한다.

Alt text

  • 1번 정점을 큐에 넣는다.
    Queue: [1]

Alt text

  • 큐에서 제일 앞에 있는 1번을 pop하고, 1번 정점을 방문한다.
    1번 정점과 인접한 정점 중에서 방문하지 않은 2, 3, 4번을 순서대로 큐에 넣는다.
    Queue: [2, 3, 4]

Alt text

  • 큐에서 제일 앞에 있는 2번을 pop하고, 2번 정점을 방문한다.
    2번 정점과 인접한 정점 중에서 방문하지 않은 5, 6번을 순서대로 큐에 넣는다.
    Queue: [3, 4, 5, 6]

Alt text

  • 큐에서 제일 앞에 있는 3번을 pop하고, 3번 정점을 방문한다.
    3번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [4, 5, 6]

Alt text

  • 큐에서 제일 앞에 있는 4번을 pop하고, 4번 정점을 방문한다.
    4번 정점과 인접한 정점 중에서 방문하지 않은 7, 8번을 순서대로 큐에 넣는다.
    Queue: [5, 6, 7, 8]

Alt text

  • 큐에서 제일 앞에 있는 5번을 pop하고, 5번 정점을 방문한다.
    5번 정점과 인접한 정점 중에서 방문하지 않은 9, 10번을 순서대로 큐에 넣는다.
    Queue: [6, 7, 8, 9, 10]

Alt text

  • 큐에서 제일 앞에 있는 6번을 pop하고, 6번 정점을 방문한다.
    6번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [7, 8, 9, 10]

Alt text

  • 큐에서 제일 앞에 있는 7번을 pop하고, 7번 정점을 방문한다.
    7번 정점과 인접한 정점 중에서 방문하지 않은 11, 12번을 순서대로 큐에 넣는다.
    Queue: [8, 9, 10, 11, 12]

Alt text

  • 큐에서 제일 앞에 있는 8번을 pop하고, 8번 정점을 방문한다.
    8번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [9, 10, 11, 12]

Alt text

  • 큐에서 제일 앞에 있는 9번을 pop하고, 9번 정점을 방문한다.
    9번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [10, 11, 12]

Alt text

  • 큐에서 제일 앞에 있는 10번을 pop하고, 10번 정점을 방문한다.
    10번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [11, 12]

Alt text

  • 큐에서 제일 앞에 있는 11번을 pop하고, 11번 정점을 방문한다.
    11번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [12]

Alt text

  • 큐에서 제일 앞에 있는 12번을 pop하고, 12번 정점을 방문한다.
    12번 정점과 인접한 정점 중에서 방문하지 않은 정점이 없으므로 다음 스탭으로 간다.
    Queue: [ ]

Alt text

  • 큐가 비었으므로 너비우선탐색(BFS)가 완료되었다.

의사코드

Input: 그래프 G, 그래프 G의 시작 루트 정점 v

다음은 Queue를 이용한 BFS의 의사코드이다.

1 procedure BFS(G, start_v) is
2      let Q be a queue
3      label start_v as discovered
4      Q.enqueue(start_v)
5      while Q is not empty do
6          v := Q.dequeue()
7          for all edges from v to w in G.adjacentEdges(v) do
8             if w is not labeled as discovered then
9                 label w as discovered
10                Q.enqueue(w)

한 구문씩 보자.

  • let Q be a queue: Queue를 생성한다.

  • label start_v as discovered: 시작 루트 정점을 방문표시 한다.

  • Q.enqueue(start_v): 시작 루트 정점을 Queue에 넣는다.

  • while Q is not empty do: Queue가 빈 공간이 아닐 때 까지 아래를 반복한다.

  • v := Q.dequeue(): 현재 방문할 정점 v를 Queue에서 꺼낸다.

  • for all edges from v to w in G.adjacentEdges(v) do: v와 인접한 모든 정점 w에 대하여

  • if w is not labeled as discovered then: w가 방문표시 되지 않았으면

    • label w as discovered: w를 방문표시 하고

    • Q.enqueue(w): w를 Queue에 넣는다.

위에서 살펴본 예제와 이 의사코드의 한 가지 차이점이 있다면 예제에서는 정점을 큐에서 꺼낼 때 방문표시를 하는 반면, 의사코드 방식에서는 인접 정점들을 큐에 넣을 때 같이 방문표시를 한다는 것이다.

정점의 방문표시를 큐에 꺼낼 때 하거나 큐에 넣을 때 하는것은 어느 구현을 하느냐에 따라 정해진다.

구현

프로그래밍 언어로의 구현은 다음 글을 참고하면 좋다.

Applications

너비우선탐색(BFS)은 그래프 이론 분야에서 많은 문제를 해결하는데에 쓰이고 있다.

  • 두 정점 u, v간의 최단 경로 찾기 (경로의 길이는 간선의 수에 따라 정해질 때)

  • 플로우 네트워크에서 Edmonds–Karp algorithm을 사용하여 최대 유량을 계산 할 때

  • 문자열 알고리즘 아호-코라식(Aho-Corasick) 패턴 매칭의 실패 함수(failure function)를 구축할 때

  • 그래프에서 정점들이 두개의 독립적인 서로소 그룹으로 나누어질 수 있는가 하는 이분성(bipartiteness) 테스팅

  • Copying garbage collection, Cheney’s algorithm

시간복잡도 및 공간복잡도

시간복잡도: $O(|V|+|E|)$ - ($O(|E|)$ may vary between $O(1)$ and $O(|V^2|)$)

너비우선탐색(BFS)의 시간복잡도는 최악의 경우 모든 정점과 간선을 탐색하기 때문에 $O(|V|+|E|)$으로 표현 될 수 있다. $O(|E|)$는 그래프가 얼마나 조밀한가에 따라 $O(1)$와 $O(|V^2|)$사이의 값이 될 수 있다.($|V|$는 정점의 수, $|E|$는 간선의 수이다.)

공간복잡도: $O(|V|)$

그래프의 정점들의 수를 미리 알고 있고, 어떤 정점들이 Queue에 추가되었는지와 같은 정보를 저장하기 위한 추가적인 자료구조를 사용하므로, 공간복잡도는 $O(|V|)$로 표현될 수 있다. 이 공간복잡도는 BFS의 탐색을 위한 공간 요구사항 이므로, 그래프 구축을 위해 사용되는 공간 요구사항과는 별개이다.

References

Leave a comment