게임을 하다보면, 내 캐릭터 위치를 이동시키기 위해 어딘가 위치를 찍게 된다.
그럼, 내 캐릭터는 어떤 알고리즘으로 그 위치를 정확하게 찾아가는 걸까?
1. 플로이드 알고리즘 (Dynamic Programming, )
2. 다익스트라 알고리즘 (Greedy Approach, )
3. A* 알고리즘 (Heuristic Alrogithm, )
4. JPS 알고리즘 (Jump Point Search, ?)
BFS, 벨만포드 등도 있지만 그냥 대표적인 것들만 적어두었다.
JPS 알고리즘이 성능이 더 좋은건 맞지만 동서남북 4방향이 아닌 8방향의 대각 방향을 보기 때문에 코딩테스트 문제에서는 거의 사용되지 않는다.
따라서 A*알고리즘에 대해 알아보자 (물론 A*도 8방향을 볼 수는 있다.)
A* 알고리즘은 다음 탐색 노드를 선정하는 방식이 다른 최단거리 탐색 알고리즘들과는 약간 다르다.
Open-List와 Closed-List
위 3개를 종료노드까지 반복하면 된다.
각 줄에 주석을 달아두었으니 이해해보자.
import sys, math
input = sys.stdin.readline
# 격자 크기, 빵 사러 갈 사람의 수
N, M = map(int, input().split())
# 격자 받아오기
area = [list(map(int, input().split())) for _ in range(N)]
# 편의점 위치 받아오기
store = [list(map(int, input().split())) for _ in range(M)]
# A* 알고리즘에 필요한 변수
F, G, H = 0, 0, 0
# A* 알고리즘을 진행하며 지나가지 못하는 부분을 만들어 줄 벽
wall = []
# 빵사러 갈 사람들의 최단거리 수
path_length = [0]*M
# basecamp 좌표 저장을 위한 리스트
basecamp = []
# basecamp 좌표 저장
for i in range(N):
for j in range(N):
if area[i][j] == 1:
basecamp.append([i, j])
# store 위치 재 조정(-1씩 해줘야 한다.)
for idx in range(len(store)):
sx, sy = store[idx]
store[idx] = [sx-1, sy-1]
# 점과 점 사이를 알기 위한 식 사용(F, G, H중 H에 해당)
def heuristic(x1, y1, x2, y2):
return int(math.sqrt(((x2-x1)**2) + ((y2-y1)**2))*10)
# A* 알고리즘 실행
def astar(f, g, h, x1, y1, x2, y2):
# 남 동 서 북
dx = [1, 0, 0, -1]
dy = [0, 1, -1, 0]
buffer = []
# 한 칸 이동할 때마다 10씩 줌
g += 10
# 남 동 서 북 순으로 4번 반복해서 각 위치에 대한 가중치 구하기
for x, y in zip(dx, dy):
nx1, ny1 = (x1+x), (y1+y)
# 만약 이동하려는 곳이 닫힌 리스트(체크한 리스트)에 있는 곳이라면 넘어감
if (nx1, ny1) in closed_list:
continue
# H 가중치 구하기
h = heuristic(nx1, ny1, x2, y2)
# 가중치 합산
f = g + h
# 합산된 가중치 buffer에 잠시 저장 (4점 비교를 위해)
buffer.append([f, (nx1, ny1)])
# 가장 작은 값을 new x, new y변수에 저장
nx, ny = min(buffer)[1]
# 가장 작은 값을 open_list에 저장
open_list.append((nx, ny))
return nx, ny
# 가고싶은 편의점에서 가장 가까운 베이스캠프 탐색 (반대로 탐색함)
## astar 알고리즘을 사용하려면 정확한 출발지점과 도착지점이 있어야 함
### 출발 지점은 편의점
for user, (sx, sy) in enumerate(store):
# 벽을 생성하기 위한 임시 변수
wall_buffer = []
# 도착 지점은 베이스캠프
for bx, by in basecamp:
# 만약 출발지가 벽이라면(이미 지나갔었다면) 넘어감
# 또는 도착지가 벽이라면(이미 지나갔었다면) 넘어감
if (sx, sy) in wall or (bx, by) in wall:
continue
# 이동 가능한 좌표 저장을 위한 변수
open_list = []
# 이동이 완료된 좌표 저장을 위한 변수
closed_list = []
# 출발지점은 계속 변경 될 예정이기 때문에 새로 변수 선언
nsx, nsy = sx, sy
# 출발지점을 open_list에 넣고
open_list.append((nsx, nsy))
# 바로 빼서 closed_list에 넣음
closed_list.append(open_list.pop())
# astar 알고리즘 반복 실행
while True:
# 출발지점을 제일 가중치가 낮은(가까운) 곳으로 변경
nsx, nsy = astar(F, G, H, nsx, nsy, bx, by)
# 이동했던 좌표를 closed_list에 넘김
closed_list.append(open_list.pop())
# 만약 이동하려는 좌표가 도착지라면(목적지에 도착했다면) 반복문 종료
if (nsx, nsy) == (bx, by):
break
# 이동했던 경로를 벽을 생성하기 위한 임시 변수에 저장
wall_buffer.append(closed_list)
# 최종적으로 어떤 경로를 선택할 건지 저장하기 위한 변수
check_path = []
# 여러 경로들 중, 최적의 경로 1개만 선택하기 위한 반복문
for wb in wall_buffer:
# 만약 경로가 비어있다면(처음이라면)
if check_path == []:
check_path = wb
else: # 경로가 비어있지 않다면
if len(check_path) == len(wb): # 최단거리 같을 때
if check_path[-1][0] == wb[-1][0]: # 행이 같을 때
if check_path[-1][1] > wb[-1][1]: # 비교하려는 열 값이 더 작다면
check_path = wb # 바꾸기
else: # 행이 다를 때
if check_path[-1][0] > wb[-1][0]: # 비교하려는 행 값이 더 작다면
check_path = wb # 바꾸기
elif len(check_path) > len(wb): # 비교하려는 최단거리가 더 작을 때
check_path = wb # 바꾸기
wall.append(check_path[0]) # 편의점(출발지점) 벽으로 세움
wall.append(check_path[-1]) # 베이스캠프(도착지점) 벽으로 세움
path_length[user] = len(check_path) # user 순서대로 몇칸 움직여야하는지 값 넘겨주기
# 시뮬레이션 (최단거리를 유저 순으로 진행했을 때)
count, time = 1, 1
while sum(path_length) != len(path_length):
for i in range(count):
if path_length[i] > 1:
path_length[i] -= 1
if count < M:
count += 1
time += 1
# 최종 걸린 시간 출력
print(time)