[알고리즘] BOJ 2146 다리 만들기 #Python

김상현·2022년 10월 4일
0

알고리즘

목록 보기
203/301
post-thumbnail

[BOJ] 2146 다리 만들기 바로가기

📍 문제

여러 섬으로 이루어진 나라가 있다. 이 나라의 대통령은 섬을 잇는 다리를 만들겠다는 공약으로 인기몰이를 해 당선될 수 있었다. 하지만 막상 대통령에 취임하자, 다리를 놓는다는 것이 아깝다는 생각을 하게 되었다. 그래서 그는, 생색내는 식으로 한 섬과 다른 섬을 잇는 다리 하나만을 만들기로 하였고, 그 또한 다리를 가장 짧게 하여 돈을 아끼려 하였다.

이 나라는 N×N크기의 이차원 평면상에 존재한다. 이 나라는 여러 섬으로 이루어져 있으며, 섬이란 동서남북으로 육지가 붙어있는 덩어리를 말한다. 다음은 세 개의 섬으로 이루어진 나라의 지도이다.

위의 그림에서 색이 있는 부분이 육지이고, 색이 없는 부분이 바다이다. 이 바다에 가장 짧은 다리를 놓아 두 대륙을 연결하고자 한다. 가장 짧은 다리란, 다리가 격자에서 차지하는 칸의 수가 가장 작은 다리를 말한다. 다음 그림에서 두 대륙을 연결하는 다리를 볼 수 있다.

물론 위의 방법 외에도 다리를 놓는 방법이 여러 가지 있으나, 위의 경우가 놓는 다리의 길이가 3으로 가장 짧다(물론 길이가 3인 다른 다리를 놓을 수 있는 방법도 몇 가지 있다).

지도가 주어질 때, 가장 짧은 다리 하나를 놓아 두 대륙을 연결하는 방법을 찾으시오.


📍 입력

첫 줄에는 지도의 크기 N(100이하의 자연수)가 주어진다. 그 다음 N줄에는 N개의 숫자가 빈칸을 사이에 두고 주어지며, 0은 바다, 1은 육지를 나타낸다. 항상 두 개 이상의 섬이 있는 데이터만 입력으로 주어진다.


📍 출력

첫째 줄에 가장 짧은 다리의 길이를 출력한다.


📍 풀이

💡 고찰

  • 문제를 해결하다가 메모리 초과 문제가 발생하였다.
  • 두 개의 섬을 연결하는 최단 경로를 구할 때, 모든 섬의 육지 정보를 queue 에 저장한 후 BFS 알고리즘을 작동하면 queue 에 쌓이는 데이터가 너무 많아서 발생한 문제인 것 같았다.
  • 메모리 초과 문제를 해결하기 위해서 사용한 방법은 모든 섬의 육지 정보를 한번에 처리하는 것이 아닌 각 섬으로 분할하여 처리한 후 최단 경로 값을 반환하는 방식을 이용하였다.
  • 문제에 대한 알고리즘적 접근은 쉽게 했지만, 순수 코딩실력이 많이 부족하다는 것을 느꼈다. 분발해야겠다.

📌 문제 풀이

  • 이 문제는 크게 2개의 순서를 거쳐 해결할 수 있다.
    • ① 주어진 지도(lst)에서 바다(0)로 분할된 각 섬(1)의 육지에 번호를 할당한다.
    • ② 각 섬의 육지에서 시작하여 다른 섬의 육지에 도달하는 최단 경로를 구한다.

✏️ 섬 분할하기

count = 0
for y in range(N):
    for x in range(N):
        if lst[y][x] == 1:
            count += 1
            stack = [(x, y)]
            while stack:
                x, y = stack.pop()
                graph[y][x] = count
                lst[y][x] = 0
                for dx, dy in move:
                    nx, ny = x + dx, y + dy
                    if nx < 0 or nx >= N or ny < 0 or ny >= N or lst[ny][nx] != 1: continue
                    stack.append((nx,ny))
  • DFS 알고리즘을 통해 현재 각 섬의 육지에 번호를 할당할 수 있다.
  • 바다(0)를 경계로 만들어진 육지(1)에 해당하는 좌표에 섬의 번호(count)로 갱신한다.

✏️ 각 섬의 육지좌표 추출

for y in range(N):
        for x in range(N):
            if graph[y][x] != 0:
                startPoints[graph[y][x]].append((x,y))
  • 섬 분할이 완료 된 후 각 섬을 연결하는 최단경로를 찾기 위한 자료를 추출한다.
  • 각 섬의 번호를 key 값으로하는 Dictionary 를 생성하여 각 섬의 육지 좌표를 value 값으로 저장한다.

✏️ 각 섬을 연결하는 최단 경로 찾기

for key, values in startPoints.items(): # key : 섬 이름, values : 섬의 육지 좌표
        
    visited = [[-1] * N for _ in range(N)]

    for x, y in values: visited[y][x] = 0 # 현재 섬의 육지 좌표 0으로 초기화

    queue = deque(values) # 섬의 육지 좌표
    check = False
    while queue:
        x, y = queue.popleft()
        for dx, dy in move:
            nx, ny = x + dx, y + dy
            # graph 범위를 넘어가는 경우
            if nx < 0 or nx >= N or ny < 0 or ny >= N: continue 

            # 현재 섬에서 다른 섬에 연결된 경우
            if graph[ny][nx] > 0 and graph[ny][nx] != key:
                answer = min(answer, visited[y][x])
                # 현재 섬에 대한 조사 종료
                check = True
                break 

            # 현재 섬에서 처음 접근한 바다(0)를 만난 경우
            if graph[ny][nx] == 0 and visited[ny][nx] == -1:
                visited[ny][nx] = visited[y][x]+1
                queue.append((nx, ny))
                
            # 현재 섬에 대한 조사 종료
            if check: break
  • BFS 알고리즘을 이용하여 현재 섬에서 다른 섬으로 연결되는 최단 경로를 구할 수 있다.
  • 모든 섬의 연결되는 최단 경로를 구해야 한다.
  • 현재 섬에서 다른 섬으로 연결되는 최단 경로를 구했다면 이후에 발생하는 현재 섬에서 다른 섬으로의 연결은 모두 현재 연결 값보다 작을 수 없으므로 BFS 를 종료한다.
  • 1번 섬에서 다른 섬으로 연결되는 경로
  • 2번 섬에서 다른 섬으로 연결되는 경로
  • 3번 섬에서 다른 섬으로 연결되는 경로

✍ 코드

from sys import stdin
from collections import deque, defaultdict

move = [(1,0),(-1,0),(0,1),(0,-1)] # 상하좌우

def solution(N,lst):
    answer = 10001 # 최단 경로
    graph = [[0] * N for _ in range(N)] # 섬 분할 graph
    startPoints = defaultdict(list) # 각 섬의 육지 좌표를 저장하는 딕셔너리

    # 섬 분할하기, dfs 활용
    count = 0
    for y in range(N):
        for x in range(N):
            if lst[y][x] == 1:
                count += 1
                stack = [(x, y)]
                while stack:
                    x, y = stack.pop()
                    graph[y][x] = count
                    lst[y][x] = 0
                    for dx, dy in move:
                        nx, ny = x + dx, y + dy
                        if nx < 0 or nx >= N or ny < 0 or ny >= N or lst[ny][nx] != 1: continue
                        stack.append((nx,ny))

    # 각 섬의 육지 좌표 추출      
    for y in range(N):
        for x in range(N):
            if graph[y][x] != 0:
                startPoints[graph[y][x]].append((x,y))

    # 최단 경로를 찾는 BFS
    for key, values in startPoints.items(): # key : 섬 이름, values : 섬의 육지 좌표
        
        visited = [[-1] * N for _ in range(N)]

        for x, y in values: visited[y][x] = 0 # 현재 섬의 육지 좌표 0으로 초기화

        queue = deque(values) # 섬의 육지 좌표
        check = False
        while queue:
            x, y = queue.popleft()
            for dx, dy in move:
                nx, ny = x + dx, y + dy
                # graph 범위를 넘어가는 경우
                if nx < 0 or nx >= N or ny < 0 or ny >= N: continue 

                # 현재 섬에서 다른 섬에 연결된 경우
                if graph[ny][nx] > 0 and graph[ny][nx] != key:
                    answer = min(answer, visited[y][x])
                    # 현재 섬에 대한 조사 종료
                    check = True
                    break 

                # 현재 섬에서 처음 접근한 바다(0)를 만난 경우
                if graph[ny][nx] == 0 and visited[ny][nx] == -1:
                    visited[ny][nx] = visited[y][x]+1
                    queue.append((nx, ny))
                
                # 현재 섬에 대한 조사 종료
                if check: break
    return answer

N = int(stdin.readline())
lst = [list(map(int,stdin.readline().split())) for _ in range(N)]

print(solution(N,lst))
profile
목적 있는 글쓰기

0개의 댓글