[알고리즘] BFS(너비 우선 탐색)
2021. 6. 5. 00:45ㆍProblem Solving/Algorithm
BFS와 DFS
대표적인 그래프 탐색 알고리즘
- 너비 우선 탐색(Breadth First Search): 정점들과 같은 레벨에 있는 노드들(형제 노드들)을 먼저 탐색하는 방식
- 깊이 우선 탐색(Depth First Search): 정점의 자식들을 먼저 탐색하는 방식
BFS/DFS 방식 이해를 위한 예제
1. BFS 방식: A - B - C - D - G - H - I - E - F - J
- 한 단계씩 내려가면서, 해당 노드와 같은 레벨에 있는 노드들 (형제 노드들)을 먼저 순회함
2. DFS 방식: A - B - D - E - F - C - G - H - I - J
- 한 노드의 자식을 타고 끝까지 순회한 후, 다시 돌아와서 다른 형제들의 자식을 타고 내려가며 순화함

Java로 그래프를 표현
- Java에서는
HashMap<String, List<String>>
자료구조를 사용해서 표현할 수 있음

import java.util.*;
public class GraphExample {
public static void main(String[] args) {
// 그래프 선언
Map<Character, List<Character>> graph = new HashMap<>();
graph.put('A', Arrays.asList('B', 'C'));
graph.put('B', Arrays.asList('A', 'D'));
graph.put('C', Arrays.asList('A', 'G', 'H', 'I'));
graph.put('D', Arrays.asList('B', 'E', 'F'));
graph.put('E', Arrays.asList('D'));
graph.put('F', Arrays.asList('D'));
graph.put('G', Arrays.asList('C'));
graph.put('H', Arrays.asList('C'));
graph.put('I', Arrays.asList('C', 'J'));
graph.put('J', Arrays.asList('I'));
// 그래프 출력
for (String node : graph.keySet()) {
System.out.println(node + " -> " + graph.get(node));
}
}
}
BFS 알고리즘 구현
자료구조 Queue를 활용함
needVisit
와visited
두 개의 큐를 생성
🤔 Queue를 사용하는 이유?
너비 우선 탐색은 탐색 시작점의 인접한 정점들을 먼저 모두 차례로 방문한 후에 방문했던 정점을 시작점으로 하여 다시 인접한 정점들을 차례로 방문하는 방식!
따라서 인접한 정점들에 대해 탐색을 한 후 차례로 다시 너비 우선 탐색을 진행해야 하므로 선입선출 형태의 자료구조인 큐를 활용하여 인접한 정점들을 저장해놔야 함

public static void bfs(Map<Character, List<Character>> graph, char start) {
Queue<Character> queue = new ArrayDeque<>();
Set<Character> visited = new HashSet<>();
queue.offer(start);
visited.add(start);
while (!queue.isEmpty()) {
char current = queue.poll();
System.out.print(current + " ");
for (char neighbor : graph.getOrDefault(current, new ArrayList<>())) {
if (!visited.contains(neighbor)) {
queue.offer(neighbor);
visited.add(neighbor);
}
}
}
}
탐색 결과
[A, B, C, D, G, H, I, E, F, J]

인접 리스트 vs 인접 행렬: BFS 구현으로 알아보기
1) 인접 리스트 기반 BFS
인접 리스트란?
- 각 정점마다 연결된 노드의 리스트를 저장
- 공간 복잡도:
O(V + E)
- 간선이 많지 않은 희소 그래프(Sparse Graph)에 적합
구조 설명
adjList[i]
는 정점i
에 연결된 노드들을 연결 리스트로 표현- Node 클래스는 vertex(정점 번호)와 link(다음 노드)를 포함
- 연결된 모든 정점을 순회하며 큐에 추가
+ 인접 리스트로 BFS 구현하는 두 가지 방식
1. Node 클래스
를 이용한 연결 리스트 방식
구조: 각 정점마다 연결된 노드를 노드 객체(Node)로 연결해 관리
private static class Node {
int vertex; // 연결된 정점
Node link; // 다음 노드
public Node(int vertex, Node link) {
this.vertex = vertex;
this.link = link;
}
// alt + shift + s
@Override
public String toString() {
return "Node [vertex=" + vertex + ", link=" + link + "]";
}
}
구현 코드 요약
adjList[from] = new Node(to, adjList[from]); // 유향
adjList[to] = new Node(from, adjList[to]); // 무향
BFS 탐색
for (Node temp = adjList[current]; temp != null; temp = temp.link) {
if (!visited[temp.vertex]) {
queue.offer(temp.vertex);
visited[temp.vertex] = true;
}
}
장점 |
|
단점 |
|
2. List<Integer>[] 배열
을 사용하는 방식
구조: 각 정점에 대해 연결된 정점들을 ArrayList로 관리
List<Integer>[] adjList = new ArrayList[V];
구현 코드 요약
adjList[from].add(to); // 유향
adjList[to].add(from); // 무향
BFS 탐색
for (int vertex : adjList[current]) {
if (!visited[vertex]) {
queue.offer(vertex);
visited[vertex] = true;
}
}
장점 |
|
단점 |
|
코드 예시(공통 BFS 로직)
private static void bfs(int start) { // start: 시작 정점
Queue<Integer> queue = new ArrayDeque<>();
boolean[] visited = new boolean[V]; // 중복 방문 피하기 위해
queue.offer(start); // 큐에 넣어 방문 예약
visited[start] = true; // 큐에 넣을 땐 반드시 방문 체크
while (!queue.isEmpty()) {
int current = queue.poll();
System.out.print((char)(current + 65) + " "); // 정점 방문했을 때 할 일 수행
// 인접 정점 순회
for (...) {
if (!visited[neighbor]) { // 이동할 정점을 방문 했는지 확인
queue.offer(neighbor); // 큐에 넣어 방문 예약
visited[neighbor] = true; // 큐에 넣을 땐 반드시 방문 체크
}
}
}
}
2) 인접 행렬 기반 BFS
인접 행렬이란?
- 2차원 배열을 이용하여
adjMatrix[i][j] = 1
이면i
와j
가 연결되었음을 나타냄 - 공간 복잡도:
O(V²)
- 노드 간 연결이 많은 밀집 그래프(Dense Graph)에 적합
구조 설명
adjMatrix[i][j]
가1
이면 두 정점 간 간선이 존재함- 각 정점의 모든 열을 순회하면서 연결된 정점을 탐색
코드 예시
private static void bfs(int start) { // start: 시작 정점
Queue<Integer> queue = new ArrayDeque<>();
boolean[] visited = new boolean[V];
queue.offer(start); // 큐에 넣어 방문 예약
visited[start] = true; // 큐에 넣을 땐 반드시 방문 체크
while (!queue.isEmpty()) {
int current = queue.poll();
System.out.print((char) (current + 65) + " "); // 정점을 방문했을 때 할 일 수행
for (int i = 0; i < V; i++) {
// 인접하고 방문하지 않은 정점이면
if (adjMatrix[current][i] != 0 && !visited[i]) {
queue.offer(i); // 큐에 넣어 방문 예약
visited[i] = true; // 큐에 넣을 땐 반드시 방문 체크
}
}
}
}
인접 리스트 vs 인접 행렬 비교 요약
항목 | 인접 리스트 | 인접 행렬 |
공간 복잡도 | O(V + E) | O(V²) |
간선 탐색 속도 | 연결 리스트 순회 (보통 빠름) | 배열 순회 (V개 비교 필요) |
적합한 경우 | 간선 수가 적은 희소 그래프 | 간선 수가 많은 밀집 그래프 |
구현 난이도 | 비교적 복잡 (Node 클래스 필요) | 단순한 2차원 배열 사용 |
BFS 시간 복잡도 | O(V + E) | O(V²) (행 전체를 탐색) |
시간 복잡도
- 일반적인 BFS 시간 복잡도:
O(V + E)
- 노드 수: V / 간선 수: E
- 즉 values의 총 개수
- 각 노드는 최대 한 번만 큐에서 꺼내져 방문됨 →
O(V)
- 각 간선은 양쪽 끝 노드에서 한 번씩 큐에 추가될 수 있음 →
O(E)
'Problem Solving > Algorithm' 카테고리의 다른 글
[알고리즘] 탐욕(Greedy) 알고리즘 (0) | 2021.06.05 |
---|---|
[알고리즘] DFS(깊이 우선 탐색) (0) | 2021.06.05 |
[알고리즘] #6_1 그래프 (0) | 2021.06.04 |
[알고리즘] 이진 탐색 (0) | 2021.06.02 |
[알고리즘] 순차 탐색 (0) | 2021.06.02 |