본문 바로가기
코딩테스트/DFS\BFS

[python] 백준 1012번: 유기농 배추

by brown_board 2023. 1. 29.
728x90

 

    #bfs 실행
    for i in range(n): #바깥리스트
        for j in range(m): #내부리스트
            if graph[i][j] == 1:
                bfs(i,j)
                cnt += 1 # cnt = bfs(i,j)
    print(cnt)

https://www.acmicpc.net/problem/1012

 

1012번: 유기농 배추

차세대 영농인 한나는 강원도 고랭지에서 유기농 배추를 재배하기로 하였다. 농약을 쓰지 않고 배추를 재배하려면 배추를 해충으로부터 보호하는 것이 중요하기 때문에, 한나는 해충 방지에 

www.acmicpc.net

 

위의 문제는 전형적인 bfs/dfs 문제이다. 그러나 최근에 이와 같은 전형을 풀어보지 않아서 개념을 까먹은 상태였다. 그래서 관련 개념을 한번 짚고 문제를 풀려고 한다.

DFS는 깊이우선탐색이라는 의미이다. 먼저 찾기로한 원소가 있다면 그 원소와 연결된 노드를 먼저 탐색 후 나중 노드를 탐색한다. 그래서 보통 재귀함수를 사용한다. 이는 스택을 떠올리면 쉽게 이해가 된다. 재귀함수에 먼저 들어간 원소가 탈출할 때 까지 다른 원소가 들어간 구문이 실행되지 않기 때문이다.

BFS는 너비우선 탐색이다. 이는 스택과 반대되는 큐 자료구조이다. 선입선출 구조로 먼저 들어오면 먼저 나간다는 개념만 알고 있으면 된다.

알고리즘을 풀때 보통 BFS가 더 빠른 편이다. 이에 대한 차이는 알고리즘으로 비교하면 더욱 쉽게 이해가 간다. 그래서 본 문제를 DFS/ BFS 두 개의 방식으로 풀어서 이해해보자.

- DFS

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
39
40
41
42
43
44
import sys
sys.setrecursionlimit(10000
#sys.stdin = open("input7.txt",'r')
input = sys.stdin.readline
 
#그래프의 한블럭을 찾아야 하므로 dfs설정
def dfs(start_x,start_y):
        
    #상하좌우 움직이기
    for i in range(4):
        move_x = start_x + dx[i]
        move_y = start_y + dy[i]
        #맵 안에서 움직이기
        if (0 <= move_x < y) and (0 <= move_y < x):
            if graphs[move_x][move_y] == 1:
                graphs[move_x][move_y] = 0
                dfs(move_x,move_y)
 
#상하좌우 움직일 좌표
dx = [-1,1,0,0]
dy = [0,0,-1,1]
 
#테스트
= int(input())
for _ in range(t):
    # x = 가로, y = 세로, k=배추갯수
    x,y,k = map(int,input().split())
    # 빈 그래프
    graphs = [[0 for _ in range(x)] for _ in range(y)]
    cnt = 0
 
    # 배추있는 곳은 1로 정의하기
    for i in range(k):
        xk, yk = map(int,input().split())
        # x,y자리
        graphs[yk][xk] = 1
    
    #dfs 실행
    for i in range(y): #행 (바깥 리스트)
        for j in range(x): #열 (내부 리스트)
            if graphs[i][j] == 1:
                dfs(i,j)
                cnt += 1
    print(cnt)
cs

- 참고 사항

import sys
sys.setrecursionlimit(10000)

이는 재귀함수를 사용할 때 파이썬의 재귀 깊이 제한을 늘려주는 코드이다. 만약에 파이썬 코딩 문제를 재귀로 풀게 될때 실행시켜주어야 하는 코드이다.

- 해석

def dfs(start_x,start_y):
        
    #상하좌우 움직이기
    for i in range(4):
        move_x = start_x + dx[i]
        move_y = start_y + dy[i]
        #맵 안에서 움직이기
        if (0 <= move_x < y) and (0 <= move_y < x):
            if graphs[move_x][move_y] == 1:
                graphs[move_x][move_y] = 0
                dfs(move_x,move_y)

1) 일단 dfs로 스택구조인 것을 머릿속으로 생각하고 풀어야 한다. 배추가 있는 자리부터 시작한다면 이 자리에 근접한 자리는 전부 가볼 것이다.
2) 그러므로 움직인 자리가 1이라면 0으로 바꿔주고 재귀함수를 실행시켜야  다시 그 자리에 도착했을 때 실행안되고 그 원소는 실행이 끝난다.
3) 이와 같은 방식으로 스택구조가 된다. 그렇다면 깊이 우선 탐색이 될것이다.
이해가 안된다면 스택와 dfs의 실행방식을 다시 보는 것이 좋을 것 같다.

    #dfs 실행
    for i in range(y): #행 (바깥 리스트)
        for j in range(x): #열 (내부 리스트)
            if graphs[i][j] == 1:
                dfs(i,j)
                cnt += 1
    print(cnt)

문제에서 요구하는 대로 연결된 1의 한 구역을 1씩 카운트해야 되므로 모든 그래프를 탐색하되 1인 경우에만 탐색하면 된다. 여기서 dfs가 실행이 될때 인접한 1이 모든 0이 되었으므로 dfs함수를 탈출할 때 1씩 더해지게 된다.

 

- BFS

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
39
40
41
42
43
44
45
46
47
import sys
from collections import deque
sys.stdin = open("input7.txt",'r')
input = sys.stdin.readline
 
#상하좌우 이동
dx = [-1,1,0,0]
dy = [0,0,-1,1]
 
def bfs(x,y):
    #deque 라이브러리를 사용하여 큐 자료구조 사용
    q = deque([(x,y)])
    #큐가 빌 때까지 실행
    while q:
        x,y = q.popleft()
        
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
 
            #맵안에서만 이동하기 and 움직인 위치가 1이라면 인접한 노드이므로 
            if (0 <= nx < n) and (0 <= ny < m) and graph[nx][ny] == 1:
                q.append((nx,ny))
                graph[nx][ny] =2
    return 1
 
= int(input())
for _ in range(t): # for _ in range(int(input()))
    #가로 m, 세로 n 배추갯수 k
    m,n,k = map(int,input().split())
    # 그래프그리기
    graph = [[0 for _ in range(m)] for _ in range(n)]
    
    #배추가 있는 자리에 1넣기
    for i in range(k):
        xk,yk = map(int,input().split())
        graph[yk][xk] = 1
    
    cnt = 0
    
    #bfs 실행
    for i in range(n): #바깥리스트
        for j in range(m): #내부리스트
            if graph[i][j] == 1:
                bfs(i,j)
                cnt += 1 # cnt = bfs(i,j)
    print(cnt)
cs

- 해석

def bfs(x,y):
    #deque 라이브러리를 사용하여 큐 자료구조 사용
    q = deque([(x,y)])
    #큐가 빌 때까지 실행
    while q:
        x,y = q.popleft()
        
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            #맵안에서만 이동하기 and 움직인 위치가 1이라면 인접한 노드이므로 
            if (0 <= nx < n) and (0 <= ny < m) and graph[nx][ny] == 1:
                q.append((nx,ny))
                graph[nx][ny] =2
    return 1

 

1) 큐 자료구조를 사용할 때 deque 라이브러리를 사용하는 것이 더 빠르기 때문에 이를 사용하는 것이 좋다.
2) 나의 경우에는 deque를 실행할때 바로 원소값을 넣어주는 편이다. 그래서 q= deque()로 정의해주어도 되지만 바로 사용한다.
3) 큐 자료구조이므로 먼저 들어가자마자 나와야한다. 그러므로 while문 실행시킬 때 바로 안에 있는 원소를 빼주고 그 원소의 상하좌우 원소중에 1인 값을 큐에 넣는다.
4) 그 후에 이 값을 2로 바꾼다. (1이 아닌 어떠한 값도 가능함)
5) 이렇게 되면 while문을 탈출했을 때 한 구역에 해당하는 값이 2가된다. (1이 1개밖에 없는 구역은 그대로 1이 됨)
6) 탈출 시에 return 1을 하여 바로 cnt에 더할 수 있게 만듦

#bfs 실행
for i in range(n): #바깥리스트
    for j in range(m): #내부리스트
        if graph[i][j] == 1:
            bfs(i,j)
            cnt += 1 # cnt = bfs(i,j)
print(cnt)

dfs와 마찬가지로 bfs함수를 탈출할 때마다 한 개의 구역을 실행시키는 것이므로 그 후에 cnt += 1을 해도 된다. 그렇지만 return문을 작성하여 1을 반환하게 만든다면 바로 cnt에 대입이 가능하다.

 

- 이번 문제를 풀면서 배운 내용

1) bfs/dfs에 대한 개념을 확실하게 잡기에 매우 좋은 문제였다
2) 테스트 케이스를 받을 때 t = int(input()으로 받지말고 바로 range(int(input())으로 받는 게 더 깔끔해 보였다.
3) 맵 안에서만 움직이는 경우를 따로 if문을 만들고 그 안에 또 if문을 작성하였는데 그럴 필요없이 and로 같이 묶는 게 더 편리했다.
4) 함수 외부의 변수를 함수 내부에서 사용할 때 자꾸 global 키워드로 작성하는 버릇이 있다. 외부변수의 값을 변경시킬 필요없을 때는 바로 함수에 갔다써도 된다.
5) 두 가지 방법으로 풀었는데 bfs가 170ms, dfs가 230ms 으로 bfs 방식이 조금 더 빨랐다. 아무래도 재귀를 사용하였기 때문인 것 같다.

728x90

'코딩테스트 > DFS\BFS' 카테고리의 다른 글

<PART 3> Q16.백준 14502번: 연구소  (0) 2022.11.16
<PART 3> Q15.백준 18352번: 특정 거리의 도시 찾기  (0) 2022.11.16
<PART 2> DFS/BFS 예제  (0) 2022.11.05
<PART 2> DFS/BFS  (0) 2022.11.05
<PART2> 스택/큐/재귀함수  (0) 2022.11.04

댓글