문제
N×M크기의 배열로 표현되는 미로가 있다.
1 | 0 | 1 | 1 | 1 | 1 |
1 | 0 | 1 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 0 | 1 | 1 |
미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.
위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.
입력
첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.
출력
첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.
문제 링크
https://www.acmicpc.net/problem/2178
풀이
그래프 이론에서의 너비 우선 탐색, BFS를 사용하면 해결되는 문제다.
1. 가장 먼저 미로와 거리를 나타내는 배열을 만들어 준다.▼
2. 그리고 미로의 (0, 0)부터 탐색을 시작해준다.
탐색을 할 때는 상, 하, 좌, 우 를 검사하는데 맵 밖이거나 갈 수 없는 곳이라면 스킵한다.
하지만 만약 갈 수 있는 곳이라면 다음에 갈 곳이므로, 다음에 갈 곳을 모아놓은 큐에 넣어준다.
(큐에 넣은 값들은 y, x 순서이다.)
현재 좌표와 동일한 좌표의 `distance`값에 직전 좌표의 `distance + 1`을 넣어주는데, 현재는 직전 값이 없으므로 그냥 `1`을 넣어준다. ▼
3. 다음에는 큐에 가장 먼저 들어갔던 좌표를 pop 해주고, 그 좌표로 이동한다.
2번이랑 똑같은 과정을 반복해주면 된다.
이 때 바로 뒤의 값은 이미 방문했던 값이므로 큐에 넣어주지 않는다.
아래에 첨부된 코드랑은 약간 다르지만, 여기서 설명하는 걸로는 `distance`값이 `0`이냐, `0`이 아니냐로 방문 여부를 판단할 것이다.
(0, 0)은 `distance` 값이 `1`이므로 이미 방문한 곳이라 더 이상 방문해주지 않는다. ▼
4. 위 과정을 끝에 도달할 때 까지 방문해주면 된다.
문제에서 항상 도착위치로 이동할 수 있는 경우만 입력으로 준다고 했으므로, 중간에 막히는 경우는 생각하지 않아도 된다.
위의 과정대로 전부 진행해주면, 아래와 같은 결과가 나오고 오른쪽 끝 값을 출력해주면 된다.▼
이 문제에서 BFS를 사용하는 이유를 아주 간단하게 얘기하자면,
DFS의 경우엔 결과가 최단 거리라는 보장이 없지만, BFS는 결과가 최단 거리라는 보장이 있기 때문이다.
Swift 코드
let firstLine = readLine()!.split(separator: " ").map({Int($0)!})
let n = firstLine[0]
let m = firstLine[1]
var maze = [[Int]]()
for _ in 0..<n {
maze.append(readLine()!.map({Int(String($0))!}))
}
var visited: [[Int]] = [[Int]](repeating: [Int](repeating: -1, count: m), count: n)
var queue: [[Int]] = [[0,0]]
let dx: [Int] = [0,0,-1,1]
let dy: [Int] = [-1,1,0,0]
var predecessor: [[[Int]]] = [[[Int]]](repeating: [[Int]](repeating: [-1, -1], count: m), count: n)
predecessor[0][0] = [0,0]
var distance: [[Int]] = [[Int]](repeating: [Int](repeating: 0, count: m), count: n)
distance[0][0] = 1
while queue.count != 0 {
let now = queue.remove(at: 0)
if visited[now[0]][now[1]] == -1 {
visited[now[0]][now[1]] = 1
for i in 0..<dx.count {
let nowdx = now[0] - dx[i]
let nowdy = now[1] - dy[i]
if nowdx < 0 || nowdx > n-1 || nowdy < 0 || nowdy > m-1 {
continue
} else {
if maze[nowdx][nowdy] == 1 && visited[nowdx][nowdy] == -1 {
predecessor[nowdx][nowdy] = now
distance[nowdx][nowdy] = distance[now[0]][now[1]] + 1
queue.append([nowdx, nowdy])
}
}
}
}
}
print(distance[n-1][m-1])
위의 설명을 보고 오면 ‘visited는 뭐지…’ 싶을 수도 있다.
`visited`는 방문 여부를 나타낸 2차원 배열이므로 `distance`로도 충분히 체크할 수 있는 부분이라 그냥 `distance`로 체크한다고 설명을 해놨다.
그래서 `visited`는 그저 `distance`에서 떨어져 나온 방문 체크용 배열이라고 생각하면 된다.
'Algorithm > BOJ PS' 카테고리의 다른 글
백준 2346번 풍선 터뜨리기 - SWIFT (0) | 2024.05.13 |
---|---|
백준 2193번 이친수 - SWIFT (0) | 2024.05.13 |
백준 1991번 트리 순회 - SWIFT, C++ (0) | 2024.04.28 |
백준 1992번 쿼드트리 - SWIFT (0) | 2024.04.28 |
백준 1987번 알파벳 - SWIFT (0) | 2024.04.19 |