오늘의 학습 키워드
이번엔 dfs 정리한 것을 바탕으로 bfs를 정리!!!
그래프 탐색이란?
- 하나의 정점으로부터 시작하여 차례대로 모든 정점들을 한 번씩 방문하는 것
- Ex) 특정 도시에서 다른 도시로 갈 수 있는지 없는지, 전자 회로에서 특정 단자와 단자가 서로 연결되어 있는지
그러면 BFS란?
루트 노드(혹은 다른 임의의 노드)에서 시작해서 인접한 노드를 먼저 탐색하는 방법
1번 노드에서 시작하여 방문하고, 인접한 모든 노드를 큐에 넣습니다.
큐 상태: [2, 3]
큐에서 2번 노드를 꺼내 방문하고, 인접한 모든 노드를 큐에 넣습니다.
큐 상태: [3, 6, 4]
큐에서 3번 노드를 꺼내 방문하고, 인접한 모든 노드를 큐에 넣습니다.
큐 상태: [6, 4, 7, 5]
큐에서 6번 노드를 꺼내 방문합니다. 6번은 더 이상 방문할 인접 노드가 없습니다.
큐 상태: [4, 7, 5]
큐에서 4번 노드를 꺼내 방문합니다. 4번도 더 이상 방문할 인접 노드가 없습니다.
큐 상태: [7, 5]
큐에서 7번 노드를 꺼내 방문합니다. 7번도 더 이상 방문할 인접 노드가 없습니다.
큐 상태: [5]
마지막으로 5번 노드를 큐에서 꺼내 방문합니다. 5번도 더 이상 방문할 인접 노드가 없습니다.
큐 상태: []
큐가 비었으므로 BFS 탐색이 끝났습니다.
BFS 탐색 순서는 다음과 같습니다: 1 -> 2 -> 3 -> 6 -> 4 -> 7 -> 5
from collections import deque
def bfs(graph, start):
# 방문한 노드를 기록하기 위한 리스트
visited = [False] * len(graph)
queue = deque([start])
visited[start] = True
while queue:
v = queue.popleft() # 큐에서 첫 번째 노드를 꺼냄
print(v, end=' ')
# 현재 노드와 연결된 노드를 모두 확인 (번호가 낮은 순서부터 처리)
for i in sorted(graph[v]):
if not visited[i]:
queue.append(i) # 방문하지 않은 노드는 큐에 추가
visited[i] = True
# 그래프 정보 (DFS 코드와 동일)
graph = [
[], # 0번 노드는 사용하지 않음
[2, 3, 7], # 1번 노드는 2, 3, 7번 노드와 연결
[1, 4, 6], # 2번 노드는 1, 4, 6번 노드와 연결
[1, 5, 7], # 3번 노드는 1, 5, 7번 노드와 연결
[2, 6], # 4번 노드는 2, 6번 노드와 연결
[3, 7], # 5번 노드는 3, 7번 노드와 연결
[2, 4], # 6번 노드는 2, 4번 노드와 연결
[1, 3] # 7번 노드는 1, 3번 노드와 연결
]
print("방문 순서:")
bfs(graph, 1)
출력 :
방문순서
1 2 3 6 4 7 5
BFS 함수
graph
와 start
를 인자로 받아 시작 노드에서부터 너비 우선 탐색을 진행visited
리스트는 각 노드가 방문되었는지를 기록하기 위한 것으로, 노드의 수만큼 False로 초기화합니다.deque
를 이용해 큐를 구현하였고, 시작 노드를 큐에 추가하고 visited
리스트에 방문 표시를 합니다.탐색 과정
while queue:
반복문은 큐가 빌 때까지 실행됩니다.v = queue.popleft()
는 큐에서 첫 번째 노드를 꺼내어 현재 방문 중인 노드를 기록합니다.for i in sorted(graph[v]):
는 현재 노드 v와 연결된 모든 노드를 번호가 낮은 순서대로 확인하는 루프입니다.https://www.acmicpc.net/problem/24444
위 입출력 예시에 따르면 결론적으로 5개의 정점, 5개의 간선인데
오름차순 방문: BFS 탐색 시 인접 정점들을 오름차순으로 방문.
따라서 BFS 탐색 순서는 1 -> 2 -> 4 -> 3이며, 5번 노드는 방문할 수 없기 때문에 방문 순서는 0이다.
1. 기본 설정 및 입력 처리
import sys
from collections import deque
sys.setrecursionlimit(10 ** 6)
input = sys.stdin.readline
n, m, r = map(int, input().split()) # 정점의 수, 간선의 수, 시작 정점
graph = [[] for _ in range(n + 1)] # 인접 리스트로 그래프 표현
visited = [0] * (n + 1) # 각 정점의 방문 순서를 저장하는 리스트 (0이면 방문하지 않음)
import sys와 sys.setrecursionlimit(10 ** 6)
: 이 코드는 재귀 깊이를 설정하여 스택 오버플로를 방지하는데 사용되며 DFS에서 필요. 하지만 BFS에서는 사용하지 않아도 됨.input = sys.stdin.readline
: 입력을 빠르게 처리하기 위한 코드n, m, r = map(int, input().split())
: 입력에서 정점의 수(n), 간선의 수(m), 시작 정점(r)을 받음.graph = [[] for _ in range(n + 1)]
: 그래프를 인접 리스트로 표현. 정점 번호는 1부터 시작하므로 크기를 n+1로 설정하여 사용하지 않는 0번 인덱스를 포함visited = [0] * (n + 1)
: 각 정점의 방문 순서를 저장할 리스트. 모든 정점을 아직 방문하지 않았으므로 초기값은 0으로 설정.2. 너비 우선 탐색 (BFS) 함수 정의
order = 1 # 방문 순서 카운터
def bfs(graph, start, visited):
global order
queue = deque([start])
visited[start] = order # 시작 정점 방문 표시 및 순서 기록
while queue:
v = queue.popleft() # 큐에서 첫 번째 노드를 꺼냄
for i in graph[v]: # 현재 노드와 연결된 모든 인접 노드를 확인
if visited[i] == 0: # 아직 방문하지 않은 노드라면
queue.append(i) # 큐에 추가
order += 1
visited[i] = order # 방문 순서 기록
order = 1
: 방문 순서를 나타내는 카운터 변수. 시작 정점을 방문할 때 1로 설정.bfs(graph, start, visited)
: 그래프, 시작 정점, 방문 리스트를 인자로 받아 BFS를 수행.queue = deque([start])
: deque를 사용하여 큐를 생성하고, 시작 정점을 큐에 넣음.visited[start] = c
: 시작 정점을 방문했음을 표시하고 방문 순서를 기록.while queue:
: 큐가 빌 때까지 반복.v = queue.popleft()
: 큐에서 첫 번째 노드를 꺼내 현재 방문 노드로 설정.for i in graph[v]:
: 현재 노드와 연결된 모든 인접 노드를 확인.if visited[i] == 0:
: 아직 방문하지 않은 노드라면 큐에 추가하고 방문 순서를 기록.3. 간선 정보 입력 및 그래프 구성
for i in range(m):
a, b = map(int, input().split()) # 간선 정보 입력
graph[a].append(b)
graph[b].append(a) # 무방향 그래프이므로 양쪽에 간선 추가
for i in range(m)
: m개의 간선 정보를 입력받음입력 예시에 따르면
- graph[1]에 4와 2를 추가 → graph[1] = [4, 2]
- graph[2]에 1, 3, 4를 추가 → graph[2] = [1, 3, 4]
- graph[3]에 2와 4를 추가 → graph[3] = [2, 4]
- graph[4]에 1, 2, 3을 추가 → graph[4] = [1, 2, 3]
4. 인접 리스트 정렬
for i in range(n + 1):
graph[i].sort() # 오름차순으로 인접 정점을 정렬
5. 너비 우선 탐색 시작 및 결과 출력
bfs(graph, r, visited)
for i in range(1, n + 1):
print(visited[i])
bfs(graph, r, visited)
: 시작 정점 r에서 DFS 수행print(visited[i])
: 정점 i의 방문 순서를 출력import sys
from collections import deque
sys.setrecursionlimit(10 ** 6)
input = sys.stdin.readline
n, m, r = map(int, input().split()) # 정점의 수, 간선의 수, 시작 정점
graph = [[] for _ in range(n+1)]
visited = [0] * (n+1) # 방문 순서 저장. 0이면 방문 X
order = 1 # 순서
def bfs(graph, start, visited):
global order
queue = deque([start])
visited[start] = order # 방문하면 순서 나타내기
while queue:
v = queue.popleft()
for i in graph[v]: # 인접 노드 중
if visited[i] == 0: # 방문하지 않은 노드 큐에 추가
queue.append(i)
order += 1 # 순서+1
visited[i] = order
# m개의 연결된 간선 정보 입력받기
for i in range(m):
a, b = (map(int, input().split()))
graph[a].append(b)
graph[b].append(a)
for i in range(n+1): # 인접노드 정보 오름차순 정렬
graph[i].sort()
# print(graph)
bfs(graph, r, visited)
for i in range(1, n+1):
print(visited[i])
🔥 bfs의 개념을 이해하면서 코드를 dfs와 비교하면서 진행하였다. 문제를 더 풀어봐야 이런 기본 개념 문제가 아닌 응용해서 적용할 수 있을 것 같다.
🔥 다음은 dfs와 bfs 중 문제를 푸는 것이 나올 것 같은데 풀기 전 dfs, bfs 정리한 것을 한 번씩 짚고 가자.
참고 문헌
[알고리즘] 너비 우선 탐색(BFS)이란