Skip to content

Latest commit

 

History

History
270 lines (186 loc) · 7.75 KB

1-29_TIL.md

File metadata and controls

270 lines (186 loc) · 7.75 KB

BFS 유형


1. 거리 측정

import sys
from collections import deque

input = sys.stdin.readline

dx = [0, 1, 0, -1]
dy = [1, 0, -1, 0]

n, m = map(int, input().split())

graph = [[0] * m for _ in range(n)]
visited = [[-1] * m for _ in range(n)]

def BFS(i, j):
    que = deque()
    que.append((i, j))
    visited[i][j] = 0

    while que:
        curr = que.popleft()

        for k in range(4):
            x = curr[0] + dx[k]
            y = curr[1] + dy[k]

            if x >= 0 and y >= 0 and x < n and y < m:
                if graph[x][y] != 0 and visited[x][y] < 0:
                    visited[x][y] = visited[curr[0]][curr[1]] + 1
                    que.append((x, y))

for i in range(n):
    numbers = list(input())
    for j in range(m):
        graph[i][j] = int(numbers[j])

BFS(0, 0)

print(visited[n-1][m-1] + 1)

위 문제는 BOJ 2178번 미로탐색 문제이다.

위와 같이 거리를 측정하는 문제들은 bool 형의 visited 배열을 두지 않고도 방문 여부를 알 수 있다. (대신 visited 배열의 초기값을 -1로 두어 visited ≥ 0 인 경우 방문으로 간주하는 것이다)

보통 시작점 - 도착점까지의 거리를 구하는 문제가 나오면 이런 알고리즘을 사용하는 경우가 많다.


2. 시작점이 여러 개 일 때

from collections import deque
import sys
input = sys.stdin.readline

m, n = map(int, input().split())
arr = []
que = deque()
res = 0
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
for _ in range(n):
    arr.append(list(map(int, input().split())))

for i in range(n):
    for j in range(m):
        if arr[i][j] == 1:
            que.append((j , i))

def bfs():
    while que:
        x, y = que.popleft()
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if 0 <= nx < m and 0 <= ny < n and arr[ny][nx] == 0:
                arr[ny][nx] = arr[y][x] + 1
                que.append((nx, ny))

bfs()
for i in arr:
    for j in i:
        if j == 0:
            print(-1)
            exit(0)
    res = max(res, max(i))

print(res - 1)

위 문제는 BOJ 7576 토마토 문제이다.

위 문제를 보면 알 수 있다시피, 이 문제는 시작점이 여러 개다. 익은 토마토가 1개일 수도 있고, 1개가 아닐 수도 있다.

단편적으로 생각해봤을 때 이중반복문을 돌려서 익은 토마토가 있을 때마다 BFS를 실행하는 방안을 생각해볼 수 있다.

하지만 그 방법으로 실행하면, **BFS의 시간복잡도가 O(NM)**이 되고, **토마토도 최대 O(NM)**개 존재할 수 있으니 시간복잡도는 **O($N^2M^2$)**이 되기 때문에 시간 초과가 난다.

해결방안

해결방안은 생각보다 단순하다. 시작점을 모두 큐에 넣고 BFS를 돌리면 끝난다.

코드에서 보면 알 수 있다시피 n개의 시작점 중 먼저 도착했을 경우에 arr 값을 바꾸기 때문에 더 늦게 도착한 시작점은 값을 바꿀 수가 ❌

추가적으로 시작점이 여러 개인 BFS 문제를 풀 때 알아둬야 할 특징이 한 개 더 있다.

  • 위 알고리즘에서는 BFS를 돌 때 que에 쌓이는 순서는 반드시 거리순으로 쌓인다!!!

시작점이 두 종류일 때

import sys
from collections import deque

arr = []
n, m = map(int, input().split())
for i in range(n):
    arr.append(list(input().rstrip()))

visited_fire = [[-1 for _ in range(1001)] for _ in range(1001)]
visited_person = [[-1 for _ in range(1001)] for _ in range(1001)]
que_fire = deque()
que_person = deque()

dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

for i in range(n):
    for j in range(m):
        if arr[i][j] == 'F':
            que_fire.append((i, j))
            visited_fire[i][j] = 0
        elif arr[i][j] == 'J':
            que_person.append((i, j))
            visited_person[i][j] = 0

while que_fire:
    x, y = que_fire.popleft()
    for k in range(4):
        nx = x + dx[k]
        ny = y + dy[k]
        if nx < 0 or nx >= n or ny < 0 or ny >= m:
            continue
        if visited_fire[nx][ny] >= 0 or arr[nx][ny] == '#':
            continue

        visited_fire[nx][ny] = visited_fire[x][y] + 1
        que_fire.append((nx, ny))

while que_person:
    x, y = que_person.popleft()
    for k in range(4):
        nx = x + dx[k]
        ny = y + dy[k]
        if nx < 0 or nx >= n or ny < 0 or ny >= m:
            print(visited_person[x][y] + 1)
            sys.exit()

        if visited_person[nx][ny] >= 0 or arr[nx][ny] == '#':
            continue
        if visited_fire[nx][ny] != -1 and visited_fire[nx][ny] <= visited_person[x][y] + 1:
            continue

        visited_person[nx][ny] = visited_person[x][y] + 1
        que_person.append((nx, ny))

print("IMPOSSIBLE")

위 문제는 BOJ 4179번 불! 문제이다.

위 문제는 지훈이는 이동가능 한 칸을 불에 타기 전에 이동해야한다.

이 조건을 구현하는 것이 생각보다 까다로운데, 생각을 해보면 continue 조건으로 넘어가는 if문을 한 가지 더 추가하면 된다.

  1. 배열의 범위를 넘어가거나
  2. 이미 방문했거나, 장애물이 있거나
  3. 지훈이가 도착하기 전에 이미 불에 타버렸거나

1, 2번까지는 원래 우리가 구현하던 방법이지만, 3번을 구현하기 위해서는 생각이 필요하다.

해결방법

불에 대한 bfs와 지훈이에 대한 bfs를 따로 돌리면 된다.

불에 대한 bfs를 먼저 돌려, 3번 조건을 구현하기 위해 (지훈이가 해당 점에 도착하기 전 위치에서의 거리 + 1) ≥ (불이 도착한 거리)를 비교하여 continue로 넘겨주면 된다.

이렇게 bfs를 2개 돌리면 된다.


4. 1차원에서의 BFS

from collections import deque

visited = [0] * (10 ** 5 + 1)

def bfs(visited):
    que = deque()
    que.append(N)

    while que:
        tmp = que.popleft()
        if tmp == K:
            return visited[tmp]
        for i in (tmp - 1, tmp + 1, tmp * 2):
            if 0 <= i <= (10 ** 5) and not visited[i]:
                visited[i] = visited[tmp] + 1
                que.append(i)

N, K = map(int, input().split())

print(bfs(visited))

위 문제는 BOJ 1697번 숨바꼭질 문제다.

마지막 유형은 이게 BFS가 맞나 싶기도 할 것이다.

언뜻 봤을 때는 이분 탐색으로도 해결이 가능해보이고, 그리디, DP 등의 방법으로도 해결이 가능해보인다.

하지만 이 문제는 BFS를 사용해서 풀어야한다.

해결방법 위 코드처럼 이동가능한 범위를 dx, dy 배열처럼 놓고 반복문을 돌리면 된다.

이 문제가 BFS 문제라는 것을 알아차리기만 하면 사실 풀이 방법은 매우 쉽지만, 이 문제에서 정확하게 짚고 넘어가야 할 부분은 BFS의 범위이다.

위 문제를 기준으로 점의 범위가 0 ≤ N ≤ 100,000 이니까 100,000으로 설정하면 되지 않을까? 라고 때려 맞추기 쉽다.

하지만 문제에서 수빈이가 100,000을 넘어가면 안된다는 조건이 없기 때문에 이 부분은 함부로 단정지으면 안된다.

  • 상식적으로 음수의 범위로 간다면 절대 최단 시간이 될 수가 없다.
  • 일단 한 번 100,000을 벗어나게 되면 주구장창 -1만 반복해야 함

위 두개의 조건으로 인해 최대 범위는 200,000이 된다는 것을 알 수 있다.

그래서 0 ≤ BFS ≤ 200,000 의 범위에서 BFS를 돌리면 답을 찾을 수 있다. (근데 사실 100,000을 넘어가는 것 자체가 손해임)

why? *2 하고 -1을 n번 하는 것보다 -1하고 *2을 n번 하는게 훨씬 이득임