1. 최단 경로 알고리즘이란?
최단 경로 알고리즘(shortest path algorithm)
- 두 노드 사이의 구성 edge weight의 합이 최소가 되는 경로를 찾는 알고리즘
- 그래프의 특성에 따라 사용되는 최단 거리 알고리즘이 다르다.
- 방향성의 유무
- 가중치의 유무
- 음수 가중치의 유무
- cycle의 유무
- BFS 알고리즘은 unweighted graph에서만 쓸 수 있다.
- Weighted graph에 Dijkstra 알고리즘을 쓸 수 있다.
- Negative weighted graph에 Bellman-Ford 알고리즘을 쓸 수 있다.
2. BFS 최단 경로 알고리즘
BFS predecessor
Backtraking
- 현재 노드를 경로에 추가한다.
- 현재 노드의 predecessor로 이동한다.
- predecessor가 있다면 1단계로 이동한다.
BFS 최단 경로 알고리즘
- 모든 노드를 방문하지 않은 노드로 표시한다. (O(n))
- 시작 노드를 방문 표시 후, 큐에 넣는다.
- 큐 가장 앞 노드를 꺼낸다.
- 꺼낸 노드에 인접한 노드(A)에 접근한다.
- A가 처음 방문한 노드면 방문한 노드 표시를 해준다.
- A의 predecessor 변수를 큐에서 꺼낸 노드로 설정한다.
- A를 큐에 넣는다.
- 꺼낸 노드에 인접한 노드에 전부 접근하기 전까지 4단계로 돌아간다.
- 큐에 아무 노드가 없을 때까지 3단계로 돌아간다.
- Backtraking한다.
from collections import deque
class StationNode:
"""지하철 역을 나타내는 노드"""
def __init__(self, station_name):
self.station_name = station_name
self.adjacent_stations = []
self.visited = False
def add_connection(self, station):
"""파라미터로 받은 역과 엣지를 만들어주는 메소드"""
self.adjacent_stations.append(station)
station.adjacent_stations.append(self)
def create_station_graph(input_file):
stations = {}
with open(input_file) as stations_raw_file:
for line in stations_raw_file:
previous_station = None
raw_data = line.strip().split("-")
for name in raw_data:
station_name = name.strip()
if station_name not in stations:
current_station = StationNode(station_name)
stations[station_name] = current_station
else:
current_station = stations[station_name]
if previous_station is not None:
current_station.add_connection(previous_station)
previous_station = current_station
return stations
def bfs(graph, start_node):
"""최단 경로용 bfs 함수"""
queue = deque()
for station_node in graph.values():
station_node.visited = False
station_node.predecessor = None
start_node.visited = True
queue.append(start_node)
while queue:
current_station = queue.popleft()
for neighbor in current_station.adjacent_stations:
if not neighbor.visited:
neighbor.visited = True
neighbor.predecessor = current_station
queue.append(neighbor)
def back_track(destination_node):
"""최단 경로를 찾기 위한 back tracking 함수"""
res_str = ""
temp = destination_node
while temp is not None:
res_str = f"{temp.station_name} {res_str}"
temp = temp.predecessor
return res_str
stations = create_station_graph("./new_stations.txt")
bfs(stations, stations["을지로3가"])
print(back_track(stations["강동구청"]))
을지로3가 을지로4가 동대문역사문화공원 신당 상왕십리 왕십리 마장 답십리 장한평 군자 아차산 광나루 천호 강동구청
BFS 최단 경로 알고리즘의 시간 복잡도
- Worst-case time complexity: O(V+E)
- Best-case time complexity: O(V+E)
- Average time complexity: O(V+E)
- Worst-case space complexity: O(V)
3. Dijkstra 알고리즘
Dijkstra 알고리즘 변수들
- Distance: 최단 경로의 현재 추정치
- Predecessor: distance 값을 기준으로 가장 weight가 작은 predecessor를 저장하는 변수
- Complete: 가장 작은 distance를 찾았음을 표시하기 위한 변수
Edge relaxtion
- Distance와 비교하여 source node까지의 최단 경로의 현재 추정치를 노드에 업데이트하여 저장하는 것
Dijkstra 알고리즘
- Source node(s)에서 거리가 가장 작은 정점을 반복적으로 선택하고 인접한 정점의 거리를 업데이트하여 작동하는 최단 경로 알고리즘
- 모든 노드의 distance를 infinity, predecessor를 None으로, complete를 False로 초기화한다. (O(n))
- 방문하지 않은 노드 중 s와의 거리가 가장 짧은 노드(v)를 찾는다.
- v를 방문 표시한다.
- v의 아직 방문하지 않은 인접 노드 중에 s에서 v까지 경로가 현재 거리보다 짧은 경우 s에서 s의 distance를 업데이트하고, 방문하지 않은 인접 노드들의 predecessor를 v로 설정한다.
- 방문하지 않은 정점이 있다면 2단계로 돌아간다.
- 모든 정점에 대한 distance 딕셔너리과 predecessor 딕셔너리를 리턴한다.
import heapq
def dijkstra(graph, start):
dist = {vertex: float('inf') for vertex in graph}
dist[start] = 0
predecessor = {vertex: None for vertex in graph}
pq = [(0, start)]
while pq:
current_dist, current_vertex = heapq.heappop(pq)
if current_dist > dist[current_vertex]:
continue
for neighbor, weight in graph[current_vertex].items():
new_dist = dist[current_vertex] + weight
if new_dist < dist[neighbor]:
dist[neighbor] = new_dist
predecessor[neighbor] = current_vertex
heapq.heappush(pq, (new_dist, neighbor))
return dist, predecessor
my_graph = {
'A': {'B': 2, 'C': 3},
'B': {'A': 2, 'C': 1},
'C': {'A': 3, 'B': 1, 'D': 4, 'E': 5},
'D': {'C': 4, 'E': 1},
'E': {'C': 5, 'D': 1}
}
distance, predecessor = dijkstra(my_graph, 'B')
print(f"distances: {distance}")
print(f"predecessors: {predecessor}")
distances: {'A': 2, 'B': 0, 'C': 1, 'D': 5, 'E': 6}
predecessors: {'A': 'B', 'B': None, 'C': 'B', 'D': 'C', 'E': 'C'}
Feedback
- 최단 경로 알고리즘의 시간, 공간 복잡도를 분석해보자.
- BFS 최단 경로 알고리즘에서 최단 경로가 여러 개일 경우를 분석해보자.
- 다양한 언어들로 BFS, Dijkstra 등의 알고리즘으로 최단 거리 알고리즘을 구현해보자.
- Dijkstra 알고리즘을 수학적으로 분석해보자.
- Bellman-Ford 알고리즘에 대해 알아보자.
참고 자료