본문 바로가기

큐와 너비 우선 탐색 BFS

거리 층을 만들고 부모 기록으로 최단 간선 경로를 복원합니다.

가중치가 없는 지도에서 가장 적은 이동 횟수로 도착하는 길을 찾고 싶습니다. 목표는 큐의 FIFO 약속으로 왜 가까운 정점부터 처리하는지 설명하고 경로를 복원하는 것입니다. 그래프·스택·딕셔너리를 복습하세요.

개념과 약속

큐는 먼저 넣은 항목이 먼저 나오는 FIFO 구조입니다. collections.deque의 append는 뒤에 넣고 popleft는 앞에서 꺼냅니다. 리스트의 pop(0)처럼 나머지를 모두 밀지 않습니다. BFS는 시작 거리 0에서 이웃 거리 1, 그다음 거리 2를 차례로 발견합니다.

distance에 기록하는 순간 방문으로 표시하고 큐에 넣어 같은 정점을 중복 삽입하지 않습니다. parent에는 처음 발견하게 한 이전 정점을 기록합니다. 도착점에서 부모를 따라 출발점까지 거슬러 간 뒤 뒤집으면 경로가 됩니다. 도달하지 못한 정점은 사전에 없고 복원 결과는 빈 목록입니다. 모든 간선 비용이 같을 때만 이 거리를 비용 최소와 연결할 수 있습니다.

BFS는 거리 층을 넓힌다

출발점에서 이웃을 발견한다. 더 발견할 정점이 없다. 각 단계의 값과 전환 이유를 비교합니다.

자료구조·알고리즘 · 상태와 관계

BFS는 거리 층을 넓힌다

A에서 한 간선인 B,C를 먼저 처리한 뒤 두 간선인 D를 발견합니다.

BFS는 거리 층을 넓힌다무방향 그래프 A-B,A-C,B-D,C-D에서 A거리0,B와C거리1,D거리2이며 이웃순서에 따라 D의부모는B다.ABCD거리 0거리 1거리 2부모 A → B → D

선은 무방향 간선입니다. 세 거리 층과 경로 A→B→D는 간선 개수를 기준으로 합니다. 다른 동률 최단 경로 A→C→D도 가능합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 A 방문부터 D 방문까지 진행합니다.
관계도의 보충 설명
전체 도표의 표기·관계·조건
구분전체 도표의 내용
설명·조건A에서 한 간선 떨어진 B,C를 먼저 발견한 뒤 두 간선 떨어진 D를 발견합니다.
관계 설명무방향 그래프 A-B,A-C,B-D,C-D에서 A의 거리는 0, B와 C의 거리는 1, D의 거리는 2이며 이웃 순서에 따라 D의 부모는 B다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
A 방문

출발점에서 이웃을 발견한다.

  • 큐·발견 집합 1: dist A=0; B=C=1
  • 큐·발견 집합 2: queue [B,C]
  • B,C 등록
B 방문

D를 처음 발견한다.

  • 큐·발견 집합 1: dist D=2; parent D=B
  • 큐·발견 집합 2: queue [C,D]
  • D 등록
C 방문

D는 이미 발견되어 다시 넣지 않는다.

  • 큐·발견 집합 1: queue [D]
  • 큐·발견 집합 2: dist D=2
  • C 제거; D 상태 유지
D 방문

더 발견할 정점이 없다.

  • 큐·발견 집합 1: queue []
  • 큐·발견 집합 2: distances A0 B1 C1 D2
  • 탐색 종료

BFS는 거리 층을 넓힌다

출발점에서 이웃을 발견한다. 더 발견할 정점이 없다. 각 단계의 값과 전환 이유를 비교합니다.

자료구조·알고리즘 · 상태와 관계

BFS는 거리 층을 넓힌다

A에서 한 간선인 B,C를 먼저 처리한 뒤 두 간선인 D를 발견합니다.

BFS는 거리 층을 넓힌다무방향 그래프 A-B,A-C,B-D,C-D에서 A거리0,B와C거리1,D거리2이며 이웃순서에 따라 D의부모는B다.ABCD거리 0거리 1거리 2부모 A → B → D

선은 무방향 간선입니다. 세 거리 층과 경로 A→B→D는 간선 개수를 기준으로 합니다. 다른 동률 최단 경로 A→C→D도 가능합니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 A 방문부터 D 방문까지 진행합니다.

관계도의 보충 설명

전체 도표의 표기·관계·조건
구분전체 도표의 내용
설명·조건A에서 한 간선 떨어진 B,C를 먼저 발견한 뒤 두 간선 떨어진 D를 발견합니다.
관계 설명무방향 그래프 A-B,A-C,B-D,C-D에서 A의 거리는 0, B와 C의 거리는 1, D의 거리는 2이며 이웃 순서에 따라 D의 부모는 B다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
A 방문

출발점에서 이웃을 발견한다.

  • 큐·발견 집합 1: dist A=0; B=C=1
  • 큐·발견 집합 2: queue [B,C]
  • B,C 등록
B 방문

D를 처음 발견한다.

  • 큐·발견 집합 1: dist D=2; parent D=B
  • 큐·발견 집합 2: queue [C,D]
  • D 등록
C 방문

D는 이미 발견되어 다시 넣지 않는다.

  • 큐·발견 집합 1: queue [D]
  • 큐·발견 집합 2: dist D=2
  • C 제거; D 상태 유지
D 방문

더 발견할 정점이 없다.

  • 큐·발견 집합 1: queue []
  • 큐·발견 집합 2: distances A0 B1 C1 D2
  • 탐색 종료

손으로 상태 추적하기

꺼낸 정점새로 발견처리 후 큐거리
AB,C[B,C]B=1,C=1
BD[C,D]D=2
C없음[D]D는 이미 발견
D없음[]끝

D의 부모는 B, B의 부모는 A입니다. 두 간선의 A→B→D를 얻습니다.

직접 실행하기

아래 코드는 이 본문만으로 실행할 수 있습니다. lesson.py로 저장하고 첫 장에서 확인한 Python 명령으로 실행하세요.

from collections import deque

def bfs_distances(graph, start):
    if start not in graph:
        raise ValueError("start must be a vertex")
    distance, parent = {start: 0}, {start: None}
    queue = deque([start])
    while queue:
        vertex = queue.popleft()
        for neighbor in graph.get(vertex, []):
            if neighbor not in distance:
                distance[neighbor] = distance[vertex] + 1
                parent[neighbor] = vertex
                queue.append(neighbor)
    return distance, parent


def restore_path(parent, target):
    if target not in parent:
        return []
    path = []
    while target is not None:
        path.append(target)
        target = parent[target]
    return path[::-1]


graph = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A", "D"], "D": ["B", "C"], "E": []}
distance, parent = bfs_distances(graph, "A")
print(distance)
print(restore_path(parent, "D"))
print(restore_path(parent, "E"))

실행 결과:

{'A': 0, 'B': 1, 'C': 1, 'D': 2}
['A', 'B', 'D']
[]

왜 맞고 언제 끝나는가

큐에는 거리가 감소하지 않는 순서로 정점들이 들어갑니다. 거리 d의 정점에서 새 이웃을 발견하면 그 이웃의 거리는 d+1입니다. 그 이웃에 더 짧은 경로가 있었다면 더 앞선 거리 층에서 이미 발견했어야 하므로 첫 발견 거리가 최소입니다. 정점마다 큐에 한 번만 들어가 유한한 입력에서 끝납니다. parent의 거리는 매번 하나 줄어 복원도 종료합니다.

비용과 적용 범위

인접 목록 시간 O(V+E), 거리·부모·큐 공간 O(V)입니다. deque 양 끝 연산은 O(1)입니다. 경로 복원은 경로 길이에 비례합니다. 벽을 정점으로 넣지 않는 격자 미로도 같은 방식으로 표현할 수 있습니다.

스스로 풀어 보기

  1. 위 그래프에 A—D를 추가하면 D의 거리와 부모는 무엇인가요?
  2. 방문 표시를 큐에서 꺼낼 때로 미루면 이웃 B·C가 D를 각각 넣을 수 있나요?

힌트

  1. A를 처리하면서 바로 D를 발견합니다.
  2. 아직 꺼내지 않은 정점도 이미 큐에 있을 수 있습니다.

해설

  1. 거리 1, 부모 A이고 경로는 A→D입니다.
  2. 가능합니다. 첫 발견 때 기록하면 중복 후보와 부모 덮어쓰기를 피합니다. 꺼낼 때 표시하는 방식도 별도 설계할 수 있지만 이 코드의 증명과 비용을 그대로 붙이면 안 됩니다.

흔한 오해

가중치 100인 한 간선과 가중치 1인 두 간선이 있으면 BFS는 간선 수가 적은 쪽을 택할 수 있습니다. 비용 합이 최소인 문제에는 Dijkstra가 필요합니다.

복습과 다음 연결

Warshall은 모든 정점 쌍의 도달 여부를 표로 만듭니다. 큐 API는 deque 공식 문서를 참고하세요.