Interview/Algorithm
다익스트라 알고리즘 Dijkstra's Algorithm
김 정출
2024. 10. 19. 00:14
다익스트라 알고리즘 Dijkstra's Algorithm
- 다익스트라 알고리즘(Dijkstra's Algorithm)은 가중치가 있는 그래프에서 한 노드에서 다른 모든 노드까지의 최단 경로를 찾는 알고리즘입니다.
- 이 알고리즘은 양의 가중치를 가진 그래프에서만 동작하며, 대표적으로 네트워크 경로 탐색이나 지도를 통한 길찾기 등에 많이 사용됩니다.
다익스트라 알고리즘의 동작 원리
- 초기화
- 시작 노드에서 각 노드로 가는 경로의 거리를 저장하는 배열을 만듭니다. 처음에는 시작 노드의 거리는 0으로 설정하고, 나머지 노드의 거리는 무한대로 설정합니다.
- 우선순위 큐(혹은 최소 힙)를 사용하여 탐색할 노드를 관리합니다.
- 최단 경로 계산
- 시작 노드를 우선순위 큐에 넣고, 그 노드를 기준으로 인접한 노드들의 거리를 계산합니다.
- 인접한 노드로 가는 경로의 거리가 현재 저장된 거리보다 짧으면, 그 노드의 거리를 업데이트하고 우선순위 큐에 넣습니다.
- 반복
- 우선순위 큐에서 가장 거리가 짧은 노드를 꺼내어, 그 노드의 인접한 노드들의 거리를 다시 계산하고 업데이트하는 과정을 반복합니다.
- 큐가 비거나 모든 노드의 최단 경로가 결정되면 종료합니다.
주요 개념
- 탐색 노드: 현재 최단 거리가 결정된 노드입니다.
- 우선순위 큐: 다음에 탐색할 노드를 선택하기 위해 사용하는 자료구조로, 최소 거리를 가진 노드를 빠르게 찾기 위해 사용됩니다.
- 가중치: 각 간선(노드 간 연결)에 부여된 값으로, 이동하는 데 드는 비용이나 거리 등을 나타냅니다.
의사 코드
1. 시작 노드를 설정하고, 해당 노드의 거리를 0으로 설정한다.
2. 나머지 노드들의 거리는 무한대로 초기화한다.
3. 우선순위 큐에 시작 노드를 넣는다.
4. 큐가 빌 때까지 다음을 반복한다:
1. 큐에서 가장 거리가 짧은 노드를 꺼낸다.
2. 그 노드의 인접 노드를 확인하고, 그 노드를 거쳐 가는 경로가 더 짧으면 거리를 업데이트한다.
3. 거리가 업데이트된 인접 노드를 큐에 추가한다.
시간 복잡도
다익스트라 알고리즘의 시간 복잡도는 그래프를 표현하는 방법과 우선순위 큐의 구현 방식에 따라 달라집니다. 일반적으로는 O(E log V)로, 여기서 E는 간선의 수, V는 노드의 수입니다.
- 우선순위 큐가 이진 힙(binary heap)으로 구현된 경우: O(E log V)
- 우선순위 큐가 피보나치 힙(Fibonacci heap)으로 구현된 경우: O(V log V + E)
이 알고리즘은 음수 가중치를 가진 그래프에는 사용할 수 없으며, 음수 가중치가 포함된 경우 벨만-포드 알고리즘을 사용해야 합니다.
Python 구현
아래는 다익스트라 알고리즘을 Python으로 구현한 코드입니다. 그래프는 인접 리스트(adjacency list)를 사용하여 표현하며, 각 노드는 다른 노드로 가는 간선과 그 가중치를 포함합니다.
import heapq
# 다익스트라 알고리즘 함수
def dijkstra(graph, start):
# 시작 노드부터 각 노드까지의 최단 거리를 저장할 딕셔너리 (기본 값은 무한대)
distances = {node: float('inf') for node in graph}
distances[start] = 0 # 시작 노드의 거리는 0으로 설정
# 우선순위 큐로 사용할 힙 생성 (거리, 노드) 튜플을 저장
priority_queue = [(0, start)]
while priority_queue:
# 가장 거리가 짧은 노드를 꺼낸다
current_distance, current_node = heapq.heappop(priority_queue)
# 이미 처리된 노드이면 무시
if current_distance > distances[current_node]:
continue
# 인접한 노드들에 대해 최단 거리 계산
for neighbor, weight in graph[current_node]:
distance = current_distance + weight
# 더 짧은 경로를 발견하면, 해당 경로로 업데이트
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))
return distances
# 그래프를 인접 리스트로 표현 (딕셔너리 형태)
graph = {
'A': [('B', 1), ('C', 4)],
'B': [('A', 1), ('C', 2), ('D', 5)],
'C': [('A', 4), ('B', 2), ('D', 1)],
'D': [('B', 5), ('C', 1)]
}
# 시작 노드를 'A'로 설정
start_node = 'A'
shortest_paths = dijkstra(graph, start_node)
# 결과 출력
for node, distance in shortest_paths.items():
print(f"최단 경로: {start_node} -> {node} = {distance}")
설명:
- 그래프 구조:
- 그래프는 딕셔너리로 표현되며, 각 노드가 키로, 그 노드와 연결된 이웃 노드와 가중치가 리스트 형태로 저장됩니다.
- 예시 그래프에서는 A, B, C, D라는 노드들이 있으며, 각 노드 사이에 가중치가 존재합니다.
- 우선순위 큐 (힙):
- heapq 모듈을 사용하여 우선순위 큐를 구현합니다. 큐에 (거리, 노드) 형태의 튜플을 넣고, 거리가 가장 작은 노드를 꺼내어 처리합니다.
- 최단 경로 계산:
- distances 딕셔너리는 시작 노드에서 각 노드까지의 최단 거리를 저장합니다. 초기에는 시작 노드를 제외한 모든 노드의 거리를 무한대로 설정하고, 알고리즘이 진행되면서 값을 갱신합니다.
- 결과:
- 각 노드까지의 최단 경로가 출력됩니다.
예시 실행 결과:
최단 경로: A -> A = 0
최단 경로: A -> B = 1
최단 경로: A -> C = 3
최단 경로: A -> D = 4
위 결과는 노드 A에서 다른 모든 노드까지의 최단 경로를 보여줍니다.
네트워크 라우팅 알고리즘
- 다익스트라 알고리즘은 네트워크 라우팅 알고리즘에서 매우 중요한 역할을 합니다. 실제 네트워크 환경에서는 여러 노드(라우터, 서버 등) 간의 최단 경로를 찾아 패킷을 효율적으로 전달해야 하는데, 다익스트라 알고리즘을 이용해 이러한 경로를 계산할 수 있습니다.
- 다익스트라 알고리즘은 특히 링크 상태 라우팅(Link-State Routing) 프로토콜에서 사용됩니다. 이 프로토콜의 대표적인 예는 **OSPF(Open Shortest Path First)**입니다. OSPF에서는 각 라우터가 인접한 라우터들과의 링크 상태(가중치, 비용)를 알고 있으며, 이를 통해 네트워크 전체의 최단 경로를 계산하여 패킷을 보냅니다.
라우팅 알고리즘 예시
예시 시나리오:
- 각 라우터가 네트워크의 노드로, 라우터 간의 연결은 링크로 표현됩니다.
- 링크는 데이터 전송의 지연 시간(가중치)을 나타냅니다.
- 라우터는 최단 경로 알고리즘을 사용하여 목적지까지의 최적 경로를 계산합니다.
네트워크 구성:
- 라우터 A, B, C, D, E가 있고, 이들 사이의 연결이 다음과 같습니다.
- A와 B 사이의 비용: 10
- A와 C 사이의 비용: 20
- B와 C 사이의 비용: 5
- B와 D 사이의 비용: 10
- C와 E 사이의 비용: 5
- D와 E 사이의 비용: 10
이 상황에서, 각 라우터는 네트워크 토폴로지를 알고 있어야 하며, 다익스트라 알고리즘을 사용하여 패킷을 목적지까지 전달하는 최적 경로를 계산할 수 있습니다.
다익스트라 알고리즘 적용 예시 (Python 코드):
import heapq
def dijkstra(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
priority_queue = [(0, start)]
while priority_queue:
current_distance, current_node = heapq.heappop(priority_queue)
if current_distance > distances[current_node]:
continue
for neighbor, weight in graph[current_node]:
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance, neighbor))
return distances
# 네트워크의 그래프 표현
network = {
'A': [('B', 10), ('C', 20)],
'B': [('A', 10), ('C', 5), ('D', 10)],
'C': [('A', 20), ('B', 5), ('E', 5)],
'D': [('B', 10), ('E', 10)],
'E': [('C', 5), ('D', 10)]
}
# 라우터 A에서 시작하는 최단 경로 계산
distances_from_A = dijkstra(network, 'A')
# 결과 출력
print("A 라우터에서 다른 라우터까지의 최단 경로:")
for router, distance in distances_from_A.items():
print(f"라우터 {router}까지의 경로 비용: {distance}")
실행 결과:
A 라우터에서 다른 라우터까지의 최단 경로:
라우터 A까지의 경로 비용: 0
라우터 B까지의 경로 비용: 10
라우터 C까지의 경로 비용: 15
라우터 D까지의 경로 비용: 20
라우터 E까지의 경로 비용: 20
네트워크 라우팅에서 다익스트라 알고리즘의 활용
- 링크 상태 수집: 각 라우터는 네트워크 내의 다른 라우터들과 연결된 링크의 상태 정보를 수집합니다. 이 링크 상태 정보에는 각 링크의 비용(전송 지연, 대역폭 등)이 포함됩니다.
- 최단 경로 계산: 다익스트라 알고리즘을 사용하여 각 라우터는 자신의 네트워크 상태를 바탕으로 다른 라우터까지의 최단 경로를 계산합니다. 각 라우터는 이 계산을 통해, 목적지까지 가는 가장 효율적인 경로를 알 수 있습니다.
- 라우팅 테이블 생성: 최단 경로 알고리즘을 통해 계산된 정보를 바탕으로 각 라우터는 자신의 라우팅 테이블을 업데이트합니다. 이 테이블에는 목적지와 그 목적지로 가기 위한 다음 홉(다음 라우터)이 포함됩니다.
- 패킷 전달: 라우팅 테이블을 참고하여, 라우터는 수신한 패킷을 최적의 경로로 전달합니다. 라우터는 패킷이 목표 노드에 도착할 때까지 중간 라우터들을 거쳐서 이동하게 됩니다.
OSPF(Open Shortest Path First)에서의 다익스트라 알고리즘
OSPF는 인터넷 프로토콜 네트워크에서 많이 사용되는 라우팅 프로토콜로, 라우터가 주기적으로 링크 상태 정보를 교환하여 네트워크의 토폴로지를 구축합니다. 이후 다익스트라 알고리즘을 사용하여 최단 경로를 계산하고, 최적의 경로를 라우팅 테이블에 반영합니다.
핵심 단계:
- 링크 상태 광고(LSA, Link State Advertisement): 각 라우터는 주기적으로 자신의 링크 상태를 네트워크에 광고합니다.
- SPF 트리 생성: 다익스트라 알고리즘을 사용하여, 각 라우터는 자신을 루트로 하는 최단 경로 트리(SPF, Shortest Path First)를 계산합니다.
- 라우팅 테이블 업데이트: 계산된 최단 경로를 바탕으로, 패킷을 보낼 다음 홉 정보를 라우팅 테이블에 저장합니다.
이처럼 다익스트라 알고리즘은 라우터가 네트워크 상에서 패킷을 효율적으로 전달할 수 있는 최적의 경로를 찾는 데 핵심적인 역할을 합니다.