다익스트라 (Dijkstra)

[ 최단거리 찾기 ]

  • 거리 비용이 같은 맵에서 최단거리 찾기
    dijkstra just tile
    ex) 타일 게임에서 최단 경로 찾기
  • 노드마다 다른 거리 비용을 가지는 맵에서 최단거리 찾기
    dijkstra different cost tile
    ex) STAGE마다 클리어에 필요한 시간 비용이 다른 STAGE 맵에서 최단 시간
  • 간선마다 다른 거리 비용을 가지는 그래프에서 최단거리 찾기
    dijkstra graph
  • Dijkstra 알고리즘으로 해결할 수 없는 경우
    음수의 거리 비용이 존재할 경우에는 다익스트라(Dijkstra) 알고리즘을 사용할 수 없습니다. 자세한 이유는 뒤에 [ 증명 ]에서 설명하겠습니다.

[ 다익스트라(Dijkstra) 알고리즘 ]

다익스트라(Dijkstra) 알고리즘은 지정한 한 노드에서 접근 가능한 모든 노드까지의 최단 거리를 구할 수 있는 알고리즘입니다. 방법은 다음과 같습니다.

  • 초기화
    먼저 각 노드들의 출발점으로부터의 거리를 기록할 테이블을 만듭니다. (모든 값을 INF(무한대라고 볼 수 있는 굉장히 큰 수)로 초기화합니다.)
    출발지의 기록 거리를 업데이트하고 출발지를 현재 노드로 선택합니다.
    dijkstra initailize
  • 현재 노드와 인접한 노드들의 기록 거리를 업데이트합니다.
    dijkstra visit 1
    ※초록색 노드는 방문된 노드입니다.
    ※노란색 노드는 대기열에 있는 노드입니다.
  • 현재 노드는 CHECK 표시를 하여 다시 방문하는 일이 없도록 합니다.
  • [현재 노드의 기록 거리 + 인접 노드까지의 거리] < [인접 노드의 기록 거리] 경우에만 업데이트합니다.
  • 업데이트에 성공한 노드들을 대기열에 넣습니다.
  • 대기열에서 기록 거리가 가장 작은 노드를 꺼내 현재 위치로 정합니다. (우선순위 큐를 이용할 수 있습니다.)
    [37, 98]에서 37로 가장 작은 거리를 가지는 [1][0] 노드를 꺼내서 방문합니다.
    dijkstra visit 2
    [40, 50, 98]에서 40으로 가장 작은 거리를 가지는 [2][0] 노드를 꺼내서 방문합니다.
    dijkstra visit 3
  • 1,2를 대기열이 빌 때까지 반복합니다.
    dijkstra visit circle

[ 증명 ]

  1. 경로가 있는 모든 노드를 방문하는가?
    인접한 노드의 업데이트가 성공하면 해당 노드를 대기열에 넣습니다. 각 노드의 기록 거리 초깃값은 INF이기 때문에 길이 막혀있지 않는 한 모든 노드를 한 번씩은 방문합니다.
  2. 방문된 노드의 기록 거리가 최단거리인가?
  3. 임의의 노드 A를 방문할 때 대기열에서 기록 거리가 가장 짧은 노드를 꺼낸 것이 A입니다. 즉 큐에 있는 다른 노드를 거쳐서 A에 도착할 경우 분명히 더 크거나 같은 거리 비용이 소요됩니다.
    ※ 거리 비용이 음수인 값이 있을 경우에는 성립하지 않기 때문에 Dijkstra 알고리즘을 사용할 수 없습니다.
  4. A 방문 이후에 업데이트되는 기록 거리는 A의 기록 거리보다 크기 때문에 A의 기록 거리가 수정될 일은 없습니다.

[ 코드 ]

from queue import PriorityQueue

INF = 9999 # 입력 범위를 고려하여 충분히 큰 값으로 설정합니다.  
X = 0
Y = 1

def tuple_sum(tuple_a, tuple_b):
  tuple_sum = tuple(sum(elem) for elem in zip(tuple_a, tuple_b))
  return tuple_sum
  
def set_distance(distance_map, load_map, checked_map, current_point, next_point):
  m = len(load_map)
  n = len(load_map[0])
  if(next_point[Y] < 0 or next_point[X] < 0 or next_point[Y] >= m or next_point[X] >= n):
      return False
  if(load_map[next_point[Y]][next_point[X]] == 0):
      return False
  if(checked_map[next_point[Y]][next_point[X]]):
      return False
  update_distance = distance_map[current_point[Y]][current_point[X]] + load_map[next_point[Y]][next_point[X]]
  if(distance_map[next_point[Y]][next_point[X]] > update_distance):
      distance_map[next_point[Y]][next_point[X]] = update_distance
      return True
  else:
      return False
  
def solution(load_map):
  m = len(load_map)
  n = len(load_map[0])
  
  distance_map = [[INF for j in range(n)] for i in range(m)]
  distance_map[0][0] = 0
  checked_map = [[False for j in range(n)] for i in range(m)]
  MOVES = [(1,0), (0,1), (-1,0), (0,-1)]
  start_point = (0, 0)
  
  next_queue = PriorityQueue()
  next_queue.put([distance_map[0][0], start_point])
  while(not next_queue.empty()):
      current_point = next_queue.get(0)[1]

      checked_map[current_point[Y]][current_point[X]] = True
      for move in MOVES:
          next_point = tuple_sum(current_point, move)
          if(set_distance(distance_map, load_map, checked_map, current_point, next_point)):
              next_queue.put([distance_map[next_point[Y]][next_point[X]], next_point])
  return distance_map

if(__name__ == "__main__"):
  load_map = [[ 1,50, 2, 1,31],
              [93, 0, 0, 0,12],
              [ 1,30,19, 7,87],
              [20, 5,39,15,86],
              [ 0, 0, 0,27,45]]
  result = solution(load_map)
  for row in result:
      for node in row:
          print("%4d" %node, end=" ")
      print("")

[ 유형별 차이 살펴보기 ]

  • 거리 비용이 같은 맵에서 최단거리 찾기
    거리 비용이 모두 같은 맵에서는 굳이 우선순위 큐를 사용할 필요가 없습니다. 이동한 깊이가 곧 거리가 되기 때문에 너비 우선 탐색(BFS)으로만 탐색해도 먼저 도착한 경로가 최단 경로가 됩니다.
  • 노드마다 다른 거리 비용을 가지는 맵에서 최단거리 찾기
    이 경우에는 우선순위 큐가 필요하지만 한번 업데이트된 노드의 기록 거리를 덮어쓸 일은 없습니다. 왜냐면 어느 방향에서 들어오던 특정 노드에 진입하는데 필요한 거리 비용은 같기 때문에 먼저 접근하는 노드가 더 작은 기록 거리를 가지고 있습니다. 따라서 최초의 업데이트가 최단 거리를 기록하게 됩니다.
  • 간선마다 다른 거리 비용을 가지는 그래프에서 최단거리 찾기
    하지만 간선마다 거리 비용이 다르다면 이야기가 다릅니다.
    dijkstra overwrite
    그림처럼 B의 기록 거리가 C의 기록 거리보다 작더라도 [C의 기록 거리 + C-A의 거리 비용] < [B의 기록 거리 + B-A 거리 비용]일 경우 C를 방문할 때 이미 업데이트되어 있는 A의 기록 거리를 덮어쓰게 됩니다.

Published under  on .

Last updated on .

googy

I'm Googy. If you like to see other work, visit My GitHub!