Post

너비 우선 탐색(BFS)

An algorithm that explores a graph or tree level by level, visiting all neighbors of a node before moving to the next level.

◼︎ 너비 우선 탐색 BFS(Breadth First Search)

  • 너비를 우선으로 하여 탐색을 수행하는 탐색 알고리즘
  • 시작 정점(Vertex)를 방문한 이후, 인접한 정점들을 모두 우선적으로 방문하는 탐색 알고리즘
    • 즉, 깊게(Deep) 탐색하기 전에, 넓게(Wide) 탐색하는 알고리즘이다.

사용 상황

BFS(너비 우선 탐색)는 가장 가까운 노드부터 탐색하므로, 최단 경로를 찾는 문제에 적합하다.

두 노드 사이의 최단 경로를 찾을 때, 그래프에서 가장 가까운 아이템을 찾을 때 사용한다.

반면, DFS(깊이 우선 탐색)

그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘이므로, 모든 노드를 방문해야 하는 문제나, 경로의 깊이가 중요한 문제 등에 적합하다. 예를 들어, 미로 찾기 문제나 퍼즐을 해결하는 데 사용된다.


DFS와의 가장 큰 차이

DFS는 깊이 우선 탐색으로, 시작 노드에서 가장 멀리 떨어진 노드를 우선적으로 탐색한다. 반면에, BFS는 너비 우선 탐색으로, 시작 노드에서 가장 가까운 노드부터 탐색한다.


BFS의 특징

  • 재귀적으로 동작하지 않는다.
    • 이는 BFS가 모든 인접 노드를 먼저 방문하고, 그 인접 노드들의 인접 노드를 방문하는 방식으로 동작하기 때문이다. 이를 위해 BFS는 보통 큐를 사용하여 방문할 노드를 관리한다.

BFS는 왜 큐 자료 구조를 활용하는가?

알고리즘의 핵심 원칙, 즉 ‘가장 가까운 노드부터 탐색해 나가는 것’을 수행하기 위함이다.

큐는 ‘First In First Out’(FIFO)라는 원칙을 가진 자료구조로, 먼저 들어온 데이터가 먼저 나가는 구조를 가지고 있다. BFS에서는 시작 노드부터 시작하여 각 노드의 인접 노드들을 순서대로 방문하게 되는데, 이 때 방문할 노드들을 큐에 저장하게 된다.

예를 들어

시작 노드 A와 인접한 노드들 B, C, D가 있다고 가정해보자.

BFS를 수행하면, 먼저 A를 방문하고 그 다음으로 인접한 노드들 B, C, D를 큐에 넣는다.

이후 큐에서 노드를 하나씩 꺼내면서 그 노드의 인접 노드들을 다시 큐에 넣는다. 이 과정을 큐가 빌 때까지 반복하게 되면, 너비를 우선으로 모든 노드를 방문하게 된다.

큐가 빌 때까지

큐가 비어있다는 것은 더 이상 방문할 노드가 없다는 뜻이다.

방문한 각 노드의 인접 노드들을 큐에 추가한다. 따라서, 큐가 비어있다는 것 → 더 이상 방문할 인접 노드가 없다는 것

아래 표는 BFS 알고리즘의 동작 과정을 단계별로 표로 나타낸다면

단계방문 노드큐의 상태
1AB, C, D
2BC, D, B의 인접 노드들
3CD, B의 인접 노드들, C의 인접 노드들

BFS Java Code

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
    private static void bfs(int N) {
        //bfs는 큐로 구현
        Queue<Integer> que = new LinkedList<>();

        //큐에 현재 노드 삽입
        que.add(N);
        //방문 체크
        visited[N] = true;

        // 큐 빌 때까지
        while (!que.isEmpty()) {
            /**
             * @Point 현재 노드 큐에서 poll 해야 함
             */
            int nowNode = que.poll();
            System.out.print(nowNode + " ");
            for (int ele : list[N]) {
                if (!visited[ele]) {
                    visited[ele] = true;
                    que.add(ele);
                }
            }

        }
    }

큐에서 현재 노드를 poll하는 과정

  1. BFS 알고리즘을 시작할 때, 큐에는 시작 노드만 들어있다. 그리고 이 노드는 큐에서 가장 먼저 나가게 될 노드이다.
  2. 알고리즘의 각 단계에서, 큐의 앞쪽에 있는 현재 노드를 poll(제거)하고 방문한다. 그리고 방문한 노드의 모든 인접 노드들을 찾아 큐에 추가하는데, 이때, 아직 방문하지 않은 노드만 큐에 추가한다.
  3. 이런 식으로 큐가 비어있을 때까지 계속하여 현재 노드를 poll하고 방문하며, 그 인접 노드들을 큐에 추가한다.

BFS 시간 복잡도

BFS의 시간 복잡도는 노드의 수와 간선의 수에 의해 결정된다.

1
2
O(V+E)
//여기서 V는 그래프의 노드 수를 나타내고, E는 그래프의 간선 수를 나타냄

이러한 시간 복잡도가 결정되는 이유는?

BFS가 그래프의 모든 노드와 간선을 정확히 한 번씩 검사하기 때문에 노드 수(V)와 간선 수(E)의 합에 비례한다.


인접행렬과 인접리스트를 사용한 BFS의 시간복잡도

인접행렬과 인접리스트는 그래프를 표현하는 방식에 따라 BFS의 시간복잡도가 달라진다.

인접행렬을 사용한 BFS

1
2
O(V^2)
// V는 노드의 수

인접행렬은 2차원 배열을 사용하여 그래프를 표현한다. BFS를 수행할 때, 각 노드를 방문하고 해당 노드와 연결된 모든 노드를 확인하기 위해, 행렬의 모든 요소를 확인해야 한다.

인접리스트를 사용한 BFS

1
O(V+E)

인접리스트는 각 노드를 헤드로 하는 연결 리스트로 그래프를 표현한다. BFS를 수행할 때, 각 노드를 방문하고 해당 노드와 연결된 노드만 확인하면 된다.

따라서, 인접리스트는 인접행렬에 비해 공간 효율성이 높으며, BFS를 수행하는 데 필요한 시간 복잡도도 낮다는 장점이 있다.

그래서 그래프 내에 적은 숫자의 간선을 갖는 희소 그래프의 경우, 인접 행렬보다 인접 리스트를 사용하는 것이 시간 복잡도를 줄일 수 있다.


This post is licensed under CC BY 4.0 by the author.