(백준) 1926.그림 / with.BFS
문제링크: https://www.acmicpc.net/problem/1926
코드
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
26
27
28
29
30
31
32
33
34
35
36
37
38
import sys
from collections import deque
n, m = map(int, sys.stdin.readline().rstrip().split())
draw = []
for _ in range(n):
a = list(map(int, sys.stdin.readline().rstrip().split()))
draw.append(a)
cnt = 0 # 그림 개수
res = [0] # 그림 넓이를 저장할 리스트
def bfs(x, y):
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
queue = deque()
queue.append([x, y]) # 0, 0
draw[x][y] = 0 # 방문한 노드는 0으로 초기화
b = 1 # 넓이를 계산하기 위한 변수
while queue: # queue 안에 원소가 있을 때 까지
c, d = queue.popleft() # c에 0, d에 0
for i in range (4): # i가 1일 때
nx = c + dx[i] # 0 + 0
ny = d + dy[i] # 0 + 0
if (0 <= nx < n) and (0 <= ny < m) and (draw[nx][ny] == 1):
b += 1
draw[nx][ny] = 0
queue.append([nx, ny])
res.append(b)
for x in range (n):
for y in range (m):
if draw[x][y] == 1: # draw 안에 1이 있을 때만
bfs(x, y) # bfs 함수를 호출하고
cnt += 1 # 그림의 개수를 +1씩 증가
print(cnt)
print(max(res))
풀이
- 입력값 n은 행의 개수, m은 열의 개수를 의미한다.
- map() 함수를 씌워서 int로 받고 공백을 기준으로 n과 m에 각각 매핑(적용)시켜준다.
- 그림을 2차원 배열로 입력 받는다.
- 그림 개수를 담을 cnt와 각각의 그림 넓이를 저장할 리스트 res를 선언한다.
- 출력값이
n*m
안에 있는 그림의 개수와 그 그림들 중 가장 넓은 넓이인걸 출력해야 되기 때문이다.
- 출력값이
- draw의 원소를 보기 위해 이중 반복문을 돌아서,
- 만약 draw의 값이 1이라면(그림이 그려져있다는 의미이니) 함수 bfs를 호출하고, 그림의 개수를 세기 위해 cnt를 1 증가한다.
- bfs 함수는 아래와 같이 동작한다.
- 좌우를 살펴보기 위한 dx 리스트와
- 상하를 살펴보기 위한 dy 리스트를 선언한다.
- while문을 돌리기 위해 queue에 현재 위치 x, y값을 넣는다.
- 이 때 0, 0은 이제 방문한 노드니까 0으로 초기화 한다.
- 그림 범위
(n*m)
내에서 1이 있으면 bfs 함수를 호출하기 때문이다.
- 그림 범위
- 각 그림의 넓이를 구하기 위해 변수 b를 1로 초기화 한다. 넓이는 queue 안에 원소가 존재하는한 1씩 증가하는 방식으로 구할 것이다. 이는 뒤에서 설명..
- queue 안에 원소가 있을 때까지 반복문을 돈다.
- c에는 현재 원소의 x 좌표값을, d에는 현재 원소의 y 좌표값을 넣는다.
- 현재 위치 기준 상 하 좌 우의 원소를 모두 돌아봐야 하기에 4번의 반복문을 돈다.
- nx로는 c 기준 상, 하를 이동하고
- ny로는 d 기준 좌, 우를 움직인다.
- 이 때 만약 nx가 n을 초과하지 않으면서 0을 포함한 양의 정수고, ny는 m을 초과하지 않으면서 0을 포함한 양의 정수이며, nx와 ny 좌표 값의 원소가 1이라면,
- 넓이 b를 1만큼 증가하고,
- 현재의 위치 (nx, ny)는 0으로 초기화하고
- 이 값을 queue에 넣는다.
왜?? (아마 여기가 이 문제의 핵심 부분이라 생각함.)
예를 들어 draw의 값이
[[111], [111]]
과 같다고 하자.(0, 1)
,(1, 0)
는 모두 유효하므로 큐에 넣고,draw[0][1]
과draw[1][0]
을0
으로 설정한다.- 이제 큐에
[(0, 1), (1, 0)]
이 들어가 있고, 이 값을 다시 큐에서 하나씩 꺼내면서 주변 (상하좌우)를 탐색한다. 큐는 FIFO(선입선출) 이기에(0, 1)
이 먼저 빠져 나온다. - 그렇담
(0, 1)
에선0, 2
와1, 1
이 유효하므로 큐에 넣고,draw[0][2]
와draw[1][1]
을0
으로 설정한다. 이런 방식으로 계속해서 큐에 주변의 좌표를 넣고, 그 주변을 탐색하면서 그림의 넓이를 구하는 것이다. 이가 바로 BFS!
- 그러다 queue 안이 다 비었다면, (주변에 1이 없어서 탐색을 끝냈다면) 넓이 b값을 res에 저장한다. 왜? 그림들 중에 가장 큰 넓이를 가진 값만 출력하기 위해서.
- 그러고 output에 맞게 cnt와
max(res)
를 print하면 끝.
질문
- Q1. 다른 분들 코드를 보니 DFS로 푸시는 분들이 계시던데, DFS로 구현하든 BFS로 구현하든 로직상 별차이가 없는 것 같다. 왜지?
- BFS는 너비우선탐색으로 queue를 이용해서 인접한 노드를 “넓게” 탐색한다. 즉 모든 노드를 한층씩 확장해가며 탐색한다. 반대로 DFS는 stack을 이용해서 “한 방향”으로 깊게 탐색하다가 더 이상 갈 곳이 없으면 돌아가서 다른 방향으로 탐색한다. 결국 이 문제에서 BFS와 DFS의 차이는 탐색 순서 차이 뿐이라 할 수 있다.
- Q2. BFS 코드를 보면 항상 queue의 원리를 사용하던데, 왜 굳이 deque 라이브러리를 불러오는 것인가?
- 시간복잡도 때문이다. 파이썬에서 일반적인 list를 큐처럼 사용하면,
pop(0)
을 이용해 큐의 앞쪽에서 값을 빼는데,pop(0)
은 O(n) 시간이 걸린다. 반면, deque는 앞과 뒤에서 원소를 추가하거나 제거하는데 O(1)의 시간이 걸린다. 그래서 BFS를 구현할 때 deque를 사용한 것.
- 시간복잡도 때문이다. 파이썬에서 일반적인 list를 큐처럼 사용하면,
cf. 질문은 인간이하고 답변은 지피티의 답변입니다. 틀렸다면 언제든 알려주세요.
내가그린기린그림~
무서운 BFS 뇨속을 미루고 미루다 만났따 ㅎ
This post is licensed under CC BY 4.0 by the author.