📚 CS/알고리즘, 자료구조

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

수댕ʕت̫͡ʔ 2024. 12. 1. 22:55

다익스트라 알고리즘에 대해 알아보자. 백준 문제 1753번 최단경로 문제를 풀다가 다익스트라 알고리즘을 공부해야할 필요성을 느꼈다. 최단경로 문제를 보면서 다익스트라를 정리해보았다,

 

1. 다익스트라(Dijkstra)

다익스트라 알고리즘은 하나의 시작 노드에서 다른 노드까지의 최단 경로를 찾는 방법이다. 매 단계에서 가장 가중치가 낮은 노드를 선택하며 진행된다. 그리고 간선의 가중치가 음수인 경우네는 적용할 수 없다는 것이 주요 특징이다.

 

1-1. 동작 과정

  1. 먼저 시작 노드의 거리를 0으로 초기화한다. 그리고 다른 모든 노드의 거리들은 무한대로 둔다.
  2. 이제부터 탐색을 시작한다. 방문하지 않는 노드 중에서 최단 거리가 가장 짧은 노드를 선택한다.
  3. 선택한 노드를 거쳐 다른 인접한 노드로 가는 비용을 계산하고, 기존의 최단거리보다 작으면 갱신한다.

1-2. 시간복잡도

기존 시간복잡도 : O(노드의 수 ^ 2)

우선순위 큐 이용 : O(간선의 수 log 간선의 수)

 

2. 문제 풀이

이 문제를 예시로 다익스트라 알고리즘에 대해서 이해를 해보자.

https://www.acmicpc.net/problem/1753

 

2-1. 문제

문제

방향그래프가 주어지면 주어진 시작점에서 다른 모든 정점으로의 최단 경로를 구하는 프로그램을 작성하시오. 단, 모든 간선의 가중치는 10 이하의 자연수이다.

입력

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가 주어진다. 셋째 줄부터 E개의 줄에 걸쳐 각 간선을 나타내는 세 개의 정수 (u, v, w)가 순서대로 주어진다. 이는 u에서 v로 가는 가중치 w인 간선이 존재한다는 뜻이다. u와 v는 서로 다르며 w는 10 이하의 자연수이다. 서로 다른 두 정점 사이에 여러 개의 간선이 존재할 수도 있음에 유의한다.

출력

첫째 줄부터 V개의 줄에 걸쳐, i번째 줄에 i번 정점으로의 최단 경로의 경로값을 출력한다. 시작점 자신은 0으로 출력하고, 경로가 존재하지 않는 경우에는 INF를 출력하면 된다.

 

2-2. 코드

import heapq

V, E = map(int, input().split())

start = int(input())

graph = {}

for i in range(V):
    graph[i+1] = []

for i in range(E):
    u, v, w = map(int, input().split())
    graph[u].append((v, w))

def solution(graph, start):

    distances = {node: float('inf') for node in graph}

    distances[start] = 0

    queue = [(0, start)]

    while queue:
        current_distance, current_destination = heapq.heappop(queue)

        if distances[current_destination] < current_distance:
            continue

        for new_de, new_distance in graph[current_destination]:
            distance = current_distance + new_distance
            if distance < distances[new_de]:
                distances[new_de] = distance
                heapq.heappush(queue, (distance, new_de))

    return distances


answer = solution(graph, start)

for node in range(1, V + 1):
    if answer[node] == float('inf'):
        print("INF")  
    else:
        print(answer[node])

 

2-3. 해설

우선순위 큐를 이용한 코드이다. 여기서 중요한 것은

def solution(graph, start):

    distances = {node: float('inf') for node in graph}

    distances[start] = 0

    queue = [(0, start)]

    while queue:
        current_distance, current_destination = heapq.heappop(queue)

        if distances[current_destination] < current_distance:
            continue

        for new_de, new_distance in graph[current_destination]:
            distance = current_distance + new_distance
            if distance < distances[new_de]:
                distances[new_de] = distance
                heapq.heappush(queue, (distance, new_de))

    return distances

 

 

solution함수이다. 

 

먼저 모든 거리를 무한대로 초기화한다.

 

distances = {node: float('inf') for node in graph}

 

여기서 그래프의 노드들의 모두 무한대로 초기화한것인데, 딕셔너리를 이용해 { 1: float('inf'). 2: float('inf') ... } 이런식으로 초기화된다.

 

그리고 현재 최소의 길이는 0으로 초기화해주고 우선순위큐에 첫번째 노드를 담는다.

distances[start] = 0
queue = [(0, start)]

 

그리고 이제 큐가 비어있을 때까지 갱신을 반복한다.

먼저 현재의 거리와 현재 출발지를 우선순위큐에서 꺼낸다. 참고로 heappop()을 통해서 거리가 가장 작은 노드를 pop해주는 것이다. 만약에 그 노드의 기존 거리보다 현재 거리가 더 크다면 현재 경로는 무시한다.

그래프를 순회하면서, 연결된 노드의 가중치를 계산하고 새로운 거리가 기존 거리보다 작다면 거리 정보를 갱신하고 큐에 추가한다.

while queue:
        current_distance, current_destination = heapq.heappop(queue)

        if distances[current_destination] < current_distance:
            continue

        for new_de, new_distance in graph[current_destination]:
            distance = current_distance + new_distance
            if distance < distances[new_de]:
                distances[new_de] = distance
                heapq.heappush(queue, (distance, new_de))

 

즉, 그림으로 살펴보면 아래와 같다.