직접 풀어보자! BFS와 DFS 5문제
안녕, 나 코딩냥이다냥! 오늘 다섯 문제는 뼈대가 전부 같다냥. 1번을 제대로 짜두면 나머지는 거기에 한두 줄씩 더하는 것뿐이라냥. 그러니 1번에 시간을 넉넉히 쓰자냥.
탐색 문제를 풀 때 순서
- 무엇이 점이고 무엇이 연결인지 정한다 — 칸이냥, 정점이냥?
- 어디서 시작하는지 정한다 — 한 곳이냥, 여러 곳이냥?
- 무엇을 기록하는지 정한다 — 방문 여부냥, 거리냥?
3번이 문제마다 달라지는 부분이라냥. 나머지 둘은 거의 안 바뀐다냥.
상하좌우로 이어진 땅 덩어리가 몇 개인지 센다냥.
격자를 전부 훑으면서, 아직 안 가본 땅을 만나면 거기서 탐색을 시작한다냥. 그 한 번의 탐색이 섬 하나를 통째로 방문 표시한다냥.
시작한 횟수가 곧 섬의 개수라냥.
for r in range(n):
for c in range(m):
if grid[r][c] == '#' and not seen[r][c]:
cnt += 1
# 여기서 BFS 나 DFS 로 이 섬 전체를 표시한다냥대각선은 이어진 것으로 보지 않는다냥. 방향표를 상하좌우 네 개만 쓰자냥.
8장에서 여덟 방향을 썼다고 그대로 가져오면 틀린다냥.
방문 표시는 큐에 넣을 때 하자냥. 꺼낼 때 하면 같은 칸이 큐에 여러 번 쌓인다냥.
가장 큰 땅 덩어리가 몇 칸인지 출력한다냥.
1번과 뼈대가 똑같다냥. 세는 대상만 바뀐다냥.
탐색을 시작한 횟수가 아니라, 한 번의 탐색에서 꺼낸 칸 수를 세면 된다냥. 그중 제일 큰 값이 답이라냥.
재귀 DFS 로 풀 거면 이 줄이 꼭 필요하다냥.
import sys
sys.setrecursionlimit(10 ** 6)숨겨진 테스트에 100 x 100이 전부 땅인 격자가 있다냥. 빈틈이 없어서
재귀가 10000단계까지 파고든다냥. 이 줄이 없으면 정답 코드도
RecursionError 로 터진다냥.
BFS 로 풀면 이 걱정이 아예 없다냥.
공개된 예시에 1 1 짜리 물 한 칸이 들어 있다냥.
최댓값을 구할 때 시작값을 잘못 잡으면 여기서 틀린다냥.
왼쪽 위에서 오른쪽 아래까지 지나는 칸 수를 최소로 한다냥.
반드시 BFS 로 풀어야 한다냥.
DFS 로 먼저 도착한 경로를 답으로 쓰면 틀린다냥. DFS 는 한 방향으로 끝까지 파고들어서 빙 돌아가는 길로 먼저 닿을 수 있다냥.
숨겨진 테스트에 그걸 잡는 큰 미로가 들어 있다냥.
dist = [[-1] * m for _ in range(n)]
dist[0][0] = 1
q = deque([(0, 0)])
while q:
r, c = q.popleft()
for dr, dc in DIRS:
nr, nc = r + dr, c + dc
if not (0 <= nr < n and 0 <= nc < m):
continue # 격자 밖이라냥
if grid[nr][nc] == '#':
continue # 벽이라냥
if dist[nr][nc] != -1:
continue # 이미 갔다냥
dist[nr][nc] = dist[r][c] + 1
q.append((nr, nc))-1 이 곧 "아직 안 감"이라 seen 을 따로 둘 필요가 없다냥.
답은 이동 횟수가 아니라 지나는 칸의 개수라냥. 시작과 도착을 모두
포함한다냥. 그래서 시작 칸을 1 로 놓고 시작하는 거라냥.
1 1 짜리 격자의 답이 1 인지 확인해보면 감이 잡힌다냥.
시작이나 도착이 벽일 수도 있다냥. 탐색을 시작하기 전에 먼저
확인하자냥. 도착이 벽이면 당연히 -1 이라냥.
격자가 아닌 그래프에서 BFS 방문 순서를 출력한다냥.
알고리즘은 하나도 안 바뀐다냥. 이웃을 주는 곳이 방향표에서 인접 리스트로 바뀔 뿐이라냥.
for nxt in adj[cur]: # DIRS 자리에 adj[cur] 가 온다냥
if not seen[nxt]:
seen[nxt] = True
q.append(nxt)인접 리스트를 정렬해야 한다냥. 번호가 작은 정점부터 방문하라고 했기 때문이라냥.
정렬하지 않으면 입력에 적힌 순서대로 방문해서 답이 달라진다냥. 숨겨진 테스트가 이걸 잡는다냥.
for a in adj:
a.sort()시작 정점에서 이어지지 않은 정점은 아예 빼고 출력한다냥.
공개된 예시에 그런 경우가 들어 있다냥. 정점 번호를 1번부터 n번까지
전부 출력하면 틀린다냥.
모든 빈 칸이 채워지는 최소 단계를 구한다냥.
시작점을 하나씩 탐색할 필요가 없다냥.
시작점을 전부 큐에 넣고 시작하면 물결이 동시에 퍼진다냥. 그러면 한 번의 BFS 로 끝난다냥. 이게 오늘의 마지막 요령이라냥.
for r in range(n):
for c in range(m):
if grid[r][c] == '*':
dist[r][c] = 0
q.append((r, c))- 빈 칸이 처음부터 없으면 →
0 - 끝까지 채우지 못한 빈 칸이 있으면 →
-1 - 그 외에는 → 가장 마지막에 채워진 칸의 단계 수
빈 칸 개수를 미리 세어두고, 채울 때마다 하나씩 줄이면 편하다냥. 끝나고도
남아 있으면 -1 이라냥.
답은 단계 수라냥. 시작점은 0단계라서 시작점만 있고 빈 칸이 없으면
답이 0 이라냥.
공개된 예시에 1 1 짜리 시작점 하나가 들어 있으니 그걸로 확인하자냥.
다섯 개 다 풀었냥? 리스트에서 시작해서 탐색까지 왔다냥. 여기서 배운 BFS 뼈대는 앞으로 만날 대부분의 탐색 문제에서 거의 그대로 쓰인다냥.
다음은 완전탐색이라냥. 똑똑한 방법이 안 떠오를 때 쓰는, 가장 든든한 무기라냥. 정말 수고했다냥~
