다익스트라(Dijkstra) 탐색 알고리즘: 이론부터 Python 구현까지 완벽 가이드

Posted by

다익스트라 알고리즘(Dijkstra’s Algorithm)가중치가 있는 그래프에서 최단 경로를 찾는 가장 널리 사용되는 알고리즘 중 하나이다.

이 알고리즘은 네트워크 라우팅, 지도 서비스, GPS 경로 탐색 등 다양한 분야에서 핵심적인 역할을 한다. 이번 포스트에서는 다익스트라 알고리즘의 개념과 원리부터 실제로 어떻게 구현하는지까지 자세히 알아보자.


다익스트라 알고리즘(Dijkstra’s Algorithm)이란?

다익스트라 알고리즘은 비음수 가중치(양수로만 가중치가 구성되어 있는 경우)가 있는 그래프에서 한 노드에서 다른 모든 노드로 가는 최단 경로를 찾는 알고리즘이다. 시작 노드에서 다른 노드로 가는 경로들 중에서 비용(가중치)이 가장 적은 경로를 선택하며, 이 과정에서 우선순위 큐를 활용해 효율적인 탐색을 수행한다.

알고리즘의 주요 특징

  • 단일 출발점 최단 경로: 출발점에서 모든 노드로 가는 최단 경로를 계산한다.
  • 비음수 가중치: 그래프의 간선(Edge)의 가중치는 0 이상이어야 한다. 음수 가중치가 있을 경우, 다익스트라 알고리즘은 올바른 경로를 계산하지 못한다.
  • 탐욕적 알고리즘: 현재 시점에서 가장 최적의 선택을 하여 결과적으로 최적의 해를 찾는다.

다익스트라 알고리즘의 동작 원리

다익스트라 알고리즘은 다음과 같은 순서로 동작한다.

  1. 초기화: 출발 노드의 거리는 0으로 설정하고, 다른 모든 노드의 거리는 무한대(∞, inf)로 설정한다. 그리고 출발 노드를 방문 처리하고, 현재 거리를 기준으로 탐색을 시작한다.
  2. 가장 가까운 노드 선택: 아직 방문하지 않은 노드들 중에서 가장 짧은 거리를 가진 노드를 선택한다. 초기에는 출발 노드가 선택된다.
  3. 인접한 노드 업데이트: 선택된 노드의 인접 노드로 가는 경로를 확인하고, 그 경로의 거리가 현재 알고 있는 거리보다 짧으면 거리를 업데이트한다.
  4. 반복: 모든 노드를 방문할 때까지 이 과정을 반복한다.

이 과정은 우선순위 큐(Priority Queue)를 사용하여 가장 가까운 노드를 효율적으로 선택하는 방식으로 진행된다.


예시: 간단한 그래프에서 다익스트라 알고리즘 적용

다음 그래프를 예시로 들어 다익스트라 알고리즘이 어떻게 동작하는지 살펴보자.

A --1--> B --3--> C
|        |        |
4        2        1
|        |        |
D --5--> E --1--> F

여기서 각 간선에 적힌 숫자는 가중치(비용)를 의미한다. A에서 F까지의 최단 경로를 구해보면, 다익스트라 알고리즘은 다음과 같은 순서로 동작한다.

  1. 시작 노드 A 선택: A에서 출발하고, A의 인접 노드인 B(1)와 D(4)의 거리를 기록한다.
  2. B 노드 선택: 현재 가장 짧은 경로인 B(1)를 선택하고, B의 인접 노드인 C(3)와 E(2)의 거리를 업데이트한다.
  3. E 노드 선택: 다음으로 가장 짧은 경로인 E(2)를 선택하고, E의 인접 노드인 F(1)를 업데이트한다.
  4. F 노드 선택: F까지 도달했으므로, 최단 경로는 A → B → E → F 이며, 이 경로의 총 가중치는 1 + 2 + 1 = 4이다.

다익스트라 알고리즘의 Python 구현

이제 Python으로 다익스트라 알고리즘을 구현하는 방법을 살펴보자. 이를 위해 우선순위 큐를 사용할 것이며, Python의 heapq 모듈을 이용해 효율적인 큐 동작을 구현할 수 있다.

import heapq

# 그래프를 인접 리스트로 표현
graph = {
    'A': {'B': 1, 'D': 4},
    'B': {'A': 1, 'C': 3, 'E': 2},
    'C': {'B': 3, 'F': 1},
    'D': {'A': 4, 'E': 5},
    'E': {'B': 2, 'D': 5, 'F': 1},
    'F': {'C': 1, 'E': 1}
}

# 다익스트라 알고리즘 함수 (시작점과 도착점 지정)
def dijkstra(graph, start, end):

    distances = {node: float('inf') for node in graph}  # 시작 노드 이외의 거리는 무한대로 설정
    distances[start] = 0  # 시작 노드의 거리는 0
    priority_queue = [(0, start)]  # (거리, 노드) 형태로 큐 초기화
    previous_nodes = {node: None for node in graph}  # 경로 추적을 위한 이전 노드 기록

    while priority_queue:
        current_distance, current_node = heapq.heappop(priority_queue)

        # 현재 노드가 도착 노드라면 종료
        if current_node == end:
            break

        # 이미 처리된 노드라면 무시
        if current_distance > distances[current_node]:
            continue

        # 현재 노드의 인접 노드들에 대해 거리 계산
        for neighbor, weight in graph[current_node].items():
            distance = current_distance + weight

            # 더 짧은 경로를 찾았다면 거리 업데이트
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                previous_nodes[neighbor] = current_node  # 이전 노드를 기록
                heapq.heappush(priority_queue, (distance, neighbor))

    # 도착 노드까지의 최단 경로 추적
    path = []
    current = end
    while current is not None:
        path.append(current)
        current = previous_nodes[current]
    path = path[::-1]  # 경로를 역순으로 만들어서 올바른 순서로 출력

    return distances[end], path

# 다익스트라 알고리즘 실행 (시작점 'A', 도착점 'F')
start_node = 'A'
end_node = 'F'
distance, path = dijkstra(graph, start_node, end_node)

# 결과 출력
print(f"Shortest distance from {start_node} to {end_node}: {distance}")
print(f"Path: {' -> '.join(path)}")

코드 설명

  • graph: 그래프를 인접 리스트로 표현한 것이다. 각 노드가 인접 노드들과의 가중치를 딕셔너리 형태로 저장한다.
  • dijkstra 함수
    • startend: 시작점과 도착점을 함수의 인자로 받아, 출발점에서 도착점까지의 최단 경로를 구한다.
    • distances: 각 노드까지의 최단 거리를 저장하는 딕셔너리이다. 시작점의 거리는 0으로 설정되고, 나머지는 무한대(∞, inf)로 설정된다.
    • previous_nodes: 경로 추적을 위해 각 노드의 이전 노드를 기록하는 딕셔너리이다. 이를 통해 최종 경로를 추적할 수 있다.
    • priority_queue: 현재까지의 거리를 기준으로 가장 짧은 거리를 가진 노드를 탐색하는 데 사용되는 우선순위 큐이다.
  • while 루프: 큐에서 노드를 꺼내 인접 노드들을 탐색하며, 더 짧은 경로를 발견하면 경로와 거리를 업데이트한다. 도착 노드에 도착하면 반복을 종료한다.
  • 경로 추적 (path): previous_nodes 딕셔너리를 활용해 도착점에서부터 시작점까지의 경로를 추적한 후, 역순으로 정렬해 최종 경로를 구한다.
  • 결과: 최단 거리와 경로를 출력한다. 예를 들어, ‘A’에서 ‘F’까지의 경로는 A -> B -> E -> F이며, 그 경로의 총 가중치(거리)는 4이다.

다익스트라 알고리즘의 한계

다익스트라 알고리즘은 비음수 가중치에 대해서만 올바르게 동작한다. 만약 그래프에 음수 가중치가 포함되어 있다면, 벨만-포드(Bellman-Ford) 알고리즘을 사용하는 것이 더 적합하다. 또한, 매우 큰 그래프의 경우 메모리 사용량이 많아질 수 있으므로 적절한 자료 구조를 선택하는 것이 중요하다.


다익스트라 알고리즘의 실생활 활용 사례

다익스트라 알고리즘은 다음과 같은 실제 문제에서 사용된다.

  • 네트워크 라우팅: 네트워크 내에서 가장 빠른 경로를 찾는 데 사용된다.
  • GPS 네비게이션: A에서 B까지의 최단 경로를 계산하는 지도 서비스에 활용된다.
  • 게임 AI: 게임 캐릭터가 목적지로 이동하는 경로를 계산하는 데 사용된다.

결론

다익스트라 알고리즘은 그래프에서 최단 경로를 찾는 매우 강력한 도구이다. 우선순위 큐를 사용하여 효율적으로 동작하며, 다양한 실생활 문제에 적용할 수 있다.

Leave a Reply

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다