개념

가중치가 있는 그래프에서 한 정점에서 다른 모든 정점까지의 최단 경로를 구하는 알고리즘

그리디 방식으로 가장 가까운 정점을 선택하며 확장


시간복잡도

구현 방식시간복잡도
인접 행렬 + 배열O(V²)
인접 리스트 + 배열O(V²)
인접 리스트 + 우선순위 큐O((V + E) log V)
공간복잡도O(V + E)

특징

장점

  • 최단 경로 보장: 음수 가중치가 없으면 최적해
  • 빠른 속도: 우선순위 큐 사용 시 O((V+E) log V)
  • 단일 출발점: 한 번 실행으로 모든 정점까지 거리

단점

  • 음수 가중치 불가: 음수 간선이 있으면 사용 불가
  • 단일 출발점만: 모든 쌍 최단 경로는 불가 (플로이드-워셜 필요)

적용 조건

  • 음수 가중치 없음: 모든 간선의 가중치 ≥ 0
  • 방향/무방향 모두 가능

구현 방법

우선순위 큐 구현 (추천)

import heapq
 
def dijkstra(graph, start):
    """다익스트라 알고리즘 - 우선순위 큐"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
 
    # 우선순위 큐: (거리, 정점)
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        # 이미 처리된 정점
        if current_dist > distances[current]:
            continue
 
        # 인접 정점 탐색
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
 
            # 더 짧은 경로 발견
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
 
    return distances
 
# 테스트
# 그래프: graph[정점] = [(인접 정점, 가중치), ...]
graph = [
    [(1, 2), (2, 5)],           # 0번 정점
    [(0, 2), (2, 3), (3, 4)],   # 1번 정점
    [(0, 5), (1, 3), (3, 1)],   # 2번 정점
    [(1, 4), (2, 1)]            # 3번 정점
]
 
print(dijkstra(graph, 0))  # [0, 2, 5, 6]
 
# 시간복잡도: O((V + E) log V)
# 공간복잡도: O(V + E)

경로 추적 구현

import heapq
 
def dijkstra_with_path(graph, start, end):
    """경로까지 추적하는 다익스트라"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
    previous = [-1] * n  # 이전 정점 기록
 
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        if current_dist > distances[current]:
            continue
 
        # 목적지 도착
        if current == end:
            break
 
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
 
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                previous[neighbor] = current
                heapq.heappush(pq, (distance, neighbor))
 
    # 경로 복원
    path = []
    current = end
    while current != -1:
        path.append(current)
        current = previous[current]
    path.reverse()
 
    return distances[end], path
 
# 테스트
graph = [
    [(1, 2), (2, 5)],
    [(0, 2), (2, 3), (3, 4)],
    [(0, 5), (1, 3), (3, 1)],
    [(1, 4), (2, 1)]
]
 
distance, path = dijkstra_with_path(graph, 0, 3)
print(f"최단 거리: {distance}")  # 6
print(f"경로: {path}")           # [0, 1, 2, 3]

배열 기반 구현 (밀집 그래프)

def dijkstra_array(graph, start):
    """배열 기반 다익스트라 (V² 복잡도)"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
    visited = [False] * n
 
    for _ in range(n):
        # 미방문 정점 중 최소 거리 정점 찾기
        min_dist = float('inf')
        min_vertex = -1
 
        for v in range(n):
            if not visited[v] and distances[v] < min_dist:
                min_dist = distances[v]
                min_vertex = v
 
        if min_vertex == -1:
            break
 
        visited[min_vertex] = True
 
        # 인접 정점 갱신
        for neighbor in range(n):
            if graph[min_vertex][neighbor] != 0:
                weight = graph[min_vertex][neighbor]
                distance = distances[min_vertex] + weight
 
                if distance < distances[neighbor]:
                    distances[neighbor] = distance
 
    return distances
 
# 테스트 (인접 행렬)
# 0은 간선 없음을 의미
graph = [
    [0, 2, 5, 0],
    [2, 0, 3, 4],
    [5, 3, 0, 1],
    [0, 4, 1, 0]
]
 
print(dijkstra_array(graph, 0))  # [0, 2, 5, 6]
 
# 시간복잡도: O(V²)
# 밀집 그래프(간선 많음)에 효율적

동작 과정

그래프:
    0 --2-- 1
    |       |
    5       4
    |       |
    2 --3-- 3

시작: 0번 정점

1단계: distances = [0, ∞, ∞, ∞]
       0 선택, 인접: 1(거리 2), 2(거리 5)
       distances = [0, 2, 5, ∞]

2단계: distances = [0, 2, 5, ∞]
       1 선택, 인접: 3(거리 2+4=6)
       distances = [0, 2, 5, 6]

3단계: distances = [0, 2, 5, 6]
       2 선택, 인접: 3(거리 5+1=6, 갱신 안 함)
       distances = [0, 2, 5, 6]

4단계: distances = [0, 2, 5, 6]
       3 선택, 모든 정점 방문 완료

최종: [0, 2, 5, 6]

템플릿

import heapq
 
def dijkstra_template(graph, start):
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
 
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        # 이미 처리된 경우 스킵
        if current_dist > distances[current]:
            continue
 
        # 인접 정점 확인
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
 
            # 최단 거리 갱신
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
 
    return distances

최적화 기법

1. 방문 체크 (선택적)

def dijkstra_visited(graph, start):
    """방문 배열 사용"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
    visited = [False] * n
 
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        if visited[current]:
            continue
 
        visited[current] = True
 
        for neighbor, weight in graph[current]:
            if not visited[neighbor]:
                distance = current_dist + weight
 
                if distance < distances[neighbor]:
                    distances[neighbor] = distance
                    heapq.heappush(pq, (distance, neighbor))
 
    return distances

2. 조기 종료 (목적지만 찾기)

def dijkstra_early_exit(graph, start, end):
    """목적지 도착 시 종료"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
 
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        # 목적지 도착
        if current == end:
            return distances[end]
 
        if current_dist > distances[current]:
            continue
 
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
 
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
 
    return float('inf')  # 도달 불가

핵심 요약

개념설명
원리그리디 - 가장 가까운 정점 선택
시간복잡도O((V+E) log V) - 우선순위 큐
공간복잡도O(V + E)
적용 조건음수 가중치 없음
활용단일 출발점 최단 경로

다른 알고리즘과 비교

알고리즘시간복잡도음수 가중치용도
다익스트라O((V+E) log V)불가단일 출발점, 양수 가중치
벨만-포드O(VE)가능단일 출발점, 음수 가중치
플로이드-워셜O(V³)가능모든 쌍 최단 경로
BFSO(V+E)불가가중치 없는 그래프

주의사항

1. 음수 가중치

# 음수 가중치가 있으면 작동 안 함
graph = [
    [(1, -5), (2, 3)],  # 음수 가중치!
    [(2, 2)],
    []
]
# 벨만-포드 알고리즘 사용 필요

2. 우선순위 큐 중복

# 같은 정점이 여러 번 큐에 들어갈 수 있음
# 거리 체크로 무시
if current_dist > distances[current]:
    continue  # 이미 처리됨

3. 초기화

# 시작 정점은 0으로 초기화
distances = [float('inf')] * n
distances[start] = 0  # 중요!

자주 나오는 문제 유형

1. 기본 최단 거리

문제: A에서 B까지 최단 거리

import heapq
 
def shortest_path(n, edges, start, end):
    """최단 거리 구하기"""
    graph = [[] for _ in range(n)]
 
    for u, v, w in edges:
        graph[u].append((v, w))
        # 무방향이면 양방향 추가
        # graph[v].append((u, w))
 
    distances = dijkstra(graph, start)
    return distances[end]
 
# 테스트
edges = [(0, 1, 2), (0, 2, 5), (1, 2, 3), (1, 3, 4), (2, 3, 1)]
print(shortest_path(4, edges, 0, 3))  # 6

2. 경유지를 거치는 최단 경로

문제: A → B → C 최단 경로

def shortest_path_via(graph, start, via, end):
    """경유지를 거치는 최단 경로"""
    # start → via
    dist1 = dijkstra(graph, start)
 
    # via → end
    dist2 = dijkstra(graph, via)
 
    total = dist1[via] + dist2[end]
 
    return total if total != float('inf') else -1
 
# 0 → 1 → 3 경로
graph = [
    [(1, 2), (2, 5)],
    [(2, 3), (3, 4)],
    [(3, 1)],
    []
]
 
print(shortest_path_via(graph, 0, 1, 3))  # 6

3. K번 이내 경유 최단 경로

문제: 최대 K개 정점을 거쳐 도달하는 최단 경로

import heapq
 
def shortest_path_k_stops(n, edges, start, end, k):
    """K번 이내 경유"""
    graph = [[] for _ in range(n)]
 
    for u, v, w in edges:
        graph[u].append((v, w))
 
    # (거리, 정점, 경유 횟수)
    pq = [(0, start, 0)]
    # distances[정점][경유 횟수]
    distances = [[float('inf')] * (k + 2) for _ in range(n)]
    distances[start][0] = 0
 
    while pq:
        current_dist, current, stops = heapq.heappop(pq)
 
        if current == end:
            return current_dist
 
        if stops > k:
            continue
 
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
            new_stops = stops + 1
 
            if distance < distances[neighbor][new_stops]:
                distances[neighbor][new_stops] = distance
                heapq.heappush(pq, (distance, neighbor, new_stops))
 
    return -1
 
# 테스트
edges = [(0, 1, 100), (0, 2, 500), (1, 2, 100), (2, 3, 100)]
print(shortest_path_k_stops(4, edges, 0, 3, 1))  # 600

4. 양방향 최단 경로

문제: A → B 최단 거리 + B → A 최단 거리

def shortest_path_bidirectional(graph, reverse_graph, start, end):
    """양방향 최단 경로"""
    # 정방향
    dist_forward = dijkstra(graph, start)
 
    # 역방향
    dist_backward = dijkstra(reverse_graph, end)
 
    return dist_forward[end] + dist_backward[start]
 
# 방향 그래프
graph = [
    [(1, 2), (2, 5)],
    [(3, 4)],
    [(3, 1)],
    []
]
 
# 역방향 그래프 생성
reverse_graph = [[] for _ in range(4)]
for u in range(len(graph)):
    for v, w in graph[u]:
        reverse_graph[v].append((u, w))
 
print(shortest_path_bidirectional(graph, reverse_graph, 0, 3))

5. 최소 비용 경로 개수

문제: 최단 경로의 개수 세기

import heapq
 
def count_shortest_paths(graph, start, end):
    """최단 경로 개수"""
    n = len(graph)
    distances = [float('inf')] * n
    distances[start] = 0
    count = [0] * n
    count[start] = 1
 
    pq = [(0, start)]
 
    while pq:
        current_dist, current = heapq.heappop(pq)
 
        if current_dist > distances[current]:
            continue
 
        for neighbor, weight in graph[current]:
            distance = current_dist + weight
 
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                count[neighbor] = count[current]
                heapq.heappush(pq, (distance, neighbor))
            elif distance == distances[neighbor]:
                count[neighbor] += count[current]
 
    return distances[end], count[end]
 
# 테스트
graph = [
    [(1, 1), (2, 1)],
    [(3, 1)],
    [(3, 1)],
    []
]
 
distance, cnt = count_shortest_paths(graph, 0, 3)
print(f"최단 거리: {distance}, 경로 개수: {cnt}")  # 2, 2

추천 연습 문제

기초

중급

고급


언제 사용할까?

사용 가능

  • 양수 가중치: 모든 간선 가중치 ≥ 0
  • 단일 출발점: 한 정점에서 다른 모든 정점
  • 빠른 속도: O((V+E) log V)
  • GPS, 네비게이션: 실제 활용 예시

사용 불가

  • 음수 가중치: 벨만-포드 사용
  • 모든 쌍 최단 경로: 플로이드-워셜 사용
  • 가중치 없음: BFS가 더 빠름

결론: 음수 가중치가 없는 최단 경로 문제의 표준 알고리즘!