최단 경로 알고리즘
개선된 다익스트라 알고리즘
앞서서 기본적인 다익스트라 알고리즘에 대해서 살펴보았다. 이번에는 조금 더 개선된 다익스트라 알고리즘에 대하여 알아보도록 할 것 이다.
힙(Heap) 자료구조
개선된 다익스트라 알고리즘은 힙 자료구조를 사용한다.
힙 자료구조란 우선순위 큐(Priority Queue)를 구현하기 위해 사용하는 자료구조 중 하나로, 우선순위 큐는 우선순위가 가장 높은 데이터를 가장 먼저 삭제한다.
힙 자료구조는 코드상으로 heapq 를 사용하며, heapq는 원소로 튜플 입력 받으면 첫번째 원소를 기준으로 우선 순위 큐를 구성한다. 우선순위 값 표현에는 일반적으로 정수형 자료형의 변수 사용된다.
최소 힙 또는 최대 힙을 이용할 수 있는데, 파이썬 라이브러리는 기본적으로 최소 힙 구조를 이용하여 우선순위 큐를 구현한다.
최소 힙을 최대 힙처럼 사용하기 위해서 우선순위에 해당하는 값에 음수 부호 붙여서 넣었다가, 꺼낸 후 다시 원래의 값으로 돌리는 방식이 사용 가능하다.
개선된 다익스트라 알고리즘 원리
최단 거리를 저장하기 위한 1차원 리스트는 그대로 이용하고, 현재 가장 가까운노드를 저장하기 위한 목적으로만 우선순위 큐를 추가로 이용한다. 즉 (거리 , 노드)의 정보를 가지는 객체를 우선순위 큐에 넣는데, 이 떄 (거리, 노드) 정보는 튜플 자료형을 이용한다.
개선된 다익스트라 알고리즘 소스코드는 다음과 같다.
import heapq
import sys
input = sys.stdin.readline
INF = int(1e9) # 무한을 의미하는 값으로 10억을 설정
# 노드의 개수, 간선의 개수를 입력받기
n, m = map(int, input().split())
# 시작 노드 번호를 입력받기
start = int(input())
# 각 노드에 연결되어 있는 노드에 대한 정보를 담는 리스트를 만들기
graph = [[] for i in range(n + 1)]
# 최단 거리 테이블을 모두 무한으로 초기화
distance = [INF] * (n + 1)
# 모든 간선 정보를 입력받기
for _ in range(m):
a, b, c = map(int, input().split())
# a번 노드에서 b번 노드로 가는 비용이 c라는 의미
graph[a].append((b, c))
def dijkstra(start):
q = []
# 시작 노드로 가기 위한 최단 경로는 0으로 설정하여, 큐에 삽입
heapq.heappush(q, (0, start))
distance[start] = 0
while q: # 큐가 비어있지 않다면
# 가장 최단 거리가 짧은 노드에 대한 정보 꺼내기
dist, now = heapq.heappop(q)
# 현재 노드가 이미 처리된 적이 있는 노드라면 무시
if distance[now] < dist:
continue
# 현재 노드와 연결된 다른 인접한 노드들을 확인
for i in graph[now]:
cost = dist + i[1]
# 현재 노드를 거쳐서, 다른 노드로 이동하는 거리가 더 짧은 경우
if cost < distance[i[0]]:
distance[i[0]] = cost
heapq.heappush(q, (cost, i[0]))
# 다익스트라 알고리즘을 수행
dijkstra(start)
# 모든 노드로 가기 위한 최단 거리를 출력
for i in range(1, n + 1):
# 도달할 수 없는 경우, 무한(INFINITY)이라고 출력
if distance[i] == INF:
print("INFINITY")
# 도달할 수 있는 경우 거리를 출력
else:
print(distance[i])
개선된 다익스트라 알고리즘의 시간복잡도는 O(ElogV)로, V는 노드의 개수, E는 간선의 개수를 의미한다.
플로이드 워셜 알고리즘
플로이드 워셜 알고리즘이란?
모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야 하는 경우에는 플로이드 워셜 알고리즘을 사용할 수 있다. 다익스트라 알고리즘과 마찬가지로 단계마다 ‘거쳐 가는 노드’를 기준으로 알고리즘 수행하지만, 매번 방문하지 않은 노드 중 최단 거리를 갖는 노드를 찾을 필요가 없다. 2차원 리스트에 ‘최단 거리’ 정보를 저장하는 것 또한 차이점이다.
또한 플로이드 워셜 알고리즘은 다이나믹 프로그래밍의 일종이다.
점화식은 다음과 같다.
A에서 B로 가는 최소 비용과 A에서 K를 거쳐 B로 가는 비용을 비교하여 더 작은 값으로 갱신하겠다는 것이며, 바로‘바로 이동하는 거리’ 비용 > ‘특정한 노드 거쳐서 이동하는 거리’비용 중에서 더 짧은 것으로 갱신한다는 것과 같은 의미이다.
플로이드 워셜 알고리즘의 총 시간 복잡도는 O(N^3)이며, N번의 단계에서 매번 O(N^2)의 시간을 소요한다.
플로이드 워셜 알고리즘의 소스코드는 다음과 같다.
INF = int(1e9) # 무한을 의미하는 값으로 10억을 설정
# 노드의 개수 및 간선의 개수를 입력받기
n = int(input())
m = int(input())
# 2차원 리스트(그래프 표현)를 만들고, 모든 값을 무한으로 초기화
graph = [[INF] * (n + 1) for _ in range(n + 1)]
# 자기 자신에서 자기 자신으로 가는 비용은 0으로 초기화
for a in range(1, n + 1):
for b in range(1, n + 1):
if a == b:
graph[a][b] = 0
# 각 간선에 대한 정보를 입력 받아, 그 값으로 초기화
for _ in range(m):
# A에서 B로 가는 비용은 C라고 설정
a, b, c = map(int, input().split())
graph[a][b] = c
# 점화식에 따라 플로이드 워셜 알고리즘을 수행
for k in range(1, n + 1):
for a in range(1, n + 1):
for b in range(1, n + 1):
graph[a][b] = min(graph[a][b], graph[a][k] + graph[k][b])
# 수행된 결과를 출력
for a in range(1, n + 1):
for b in range(1, n + 1):
# 도달할 수 없는 경우, 무한(INFINITY)이라고 출력
if graph[a][b] == 1e9:
print("INFINITY", end=" ")
# 도달할 수 있는 경우 거리를 출력
else:
print(graph[a][b], end=" ")
print()
'코딩 테스트' 카테고리의 다른 글
최단 경로 알고리즘이란 무엇일까? (0) | 2022.03.29 |
---|
댓글