너가 어떤 도시(A)에서 다른 도시들로 여행을 가고 있다고 상상해봐.
그리고 너는 가장 빠른 길을 찾고 싶어.
이때 "어떤 도시에 가는 데 걸리는 최단 시간"을 메모지에 적고 다닌다고 생각해보자.
예를 들어, B에서 D로 가는 시간이 10분이고,
현재 메모지에 D까지의 시간은 7분이라고 적혀 있다면?
- 계산된 값: 3(B까지 걸린 시간) + 10 = 13
- 메모지에 있는 값: 7
→ 13 > 7이므로 갱신하지 않음 (더 느린 길이기 때문)
| 함수명 | 설명 |
|---|---|
dijkstra(start) | start 정점에서 시작하는 다익스트라 함수 |
add_edge(u, v, w) | 정점 u에서 정점 v로 가는 가중치 w의 간선 추가 (필요한 경우) |
initialize() | 거리 배열과 방문 배열 초기화 |
| 변수명 | 의미 |
|---|---|
graph | 인접 리스트 형태의 그래프 (예: graph[u] = [(v, w), (x, y)]) |
distance | 각 노드까지의 최단 거리 저장 배열 (초기값 무한대) |
visited | 해당 노드를 이미 방문했는지 여부 확인용 (불리언 배열) |
pq 또는 heap | 우선순위 큐 (heapq 사용 시) |
current 또는 cur_node | 현재 탐색 중인 노드 |
cur_dist | 현재 노드까지의 거리 |
next_node | 인접 노드 |
weight 또는 edge_cost | 현재 간선을 통한 이동 비용 |
import sys, heapq
INF = float("inf")
class answer:
def __init__(self):
self.n = int(sys.stdin.readline())
self.m = int(sys.stdin.readline())
self.pq = []
def build_graph(self):
self.graph = [[] for _ in range(self.n + 1)]
self.distance = [INF for _ in range(self.n + 1)]
def add_edge(self):
for _ in range(self.m):
u, v, cost = map(int, sys.stdin.readline().split())
self.graph[u].append([cost, v])
def init_pq(self):
self.start, self.end = map(int, sys.stdin.readline().split())
self.distance[self.start] = 0
heapq.heappush(self.pq, [0, self.start])
def dijkstra(self):
while len(self.pq) > 0:
pop_dt = heapq.heappop(self.pq)
current = pop_dt[1]
cur_dist = pop_dt[0]
if self.distance[current] < cur_dist:
continue
for i in self.graph[current]:
edge_cost = i[0]
next_node = i[1]
new_dist = cur_dist + edge_cost
if self.distance[next_node] > new_dist:
self.distance[next_node] = new_dist
heapq.heappush(self.pq, [new_dist, next_node])
print(self.distance[self.end])
ans = answer()
ans.build_graph()
ans.add_edge()
ans.init_pq()
ans.dijkstra()
seen vs visited의 차이점seenvisitedvisitedqueue = deque([start])
visited = set([start]) # 큐에 넣는 순간 visited로 처리
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
seenheap = [(0, start)]
seen = set()
while heap:
cost, node = heappop(heap)
if node in seen: # 이미 최소 비용으로 처리한 적 있음
continue
seen.add(node)
for neighbor, weight in graph[node]:
heappush(heap, (cost + weight, neighbor))
다익스트라는 "최단 거리"를 보장하는 알고리즘이기 때문에, 같은 노드가 여러 번 우선순위 큐에 들어갈 수 있어. 이 중 가장 짧은 비용으로 도달한 경우만 유효하기 때문에, 큐에서 꺼낼 때 그 노드가 이전에 처리되었는지를 seen으로 확인하는 거야.
만약 큐에 넣을 때 visited 처리를 해버리면, 더 짧은 경로가 나중에 발견되어도 무시돼버릴 수 있어서 최단 거리 보장이 깨지게 돼. 따라서 다익스트라는 꺼낼 때 처리하는 게 핵심이야.
visited를 큐에 넣을 때 처리하는 경우일부 A* 알고리즘이나 실용적인 최적화에서는 visited를 큐에 넣을 때 처리하기도 해. 대신 이때는 다음 조건을 추가해야 해:
즉, 이 방식은 추가적인 비용 비교나 조건이 붙어야 안전하게 동작할 수 있어. 일반적인 다익스트라에서는 seen을 꺼낸 다음에 처리하는 것이 기본이자 안전한 방식이야.
| 상황 | 변수 이름 | 언제 체크? | 목적 |
|---|---|---|---|
| BFS/DFS | visited | 큐에 넣기 전에 | 중복 방문 방지 |
| 다익스트라 | seen | 큐에서 꺼낸 후 | 최소 비용만 확정 처리 |
set()이 방문 여부를 체크하는 원리set은 중복을 허용하지 않는 자료구조야.seen.add('A')를 하면 A가 들어가.if 'A' in seen:을 하면 → True가 나와.seen.add('A')를 해도, A는 중복으로 추가되지 않아.즉, 한 번 추가한 값은 다시 들어가지 않음 → 이걸 이용해서 "이미 방문한 적 있니?" 를 체크할 수 있는 거야.
seen = set()
...
_, node = heappop(heap)
if node in seen:
continue # 이미 처리한 적 있으면 무시
seen.add(node) # 이제 이 노드는 처리한 것으로 기록
heap에서 꺼낸 노드가 seen에 있는지 확인continue)seen.add(node)로 기록set을 쓰는 걸까?in 연산: 리스트는 O(n), set은 O(1) (평균)set이 최적이야.궁금했던 점
"큐에서 노드를 꺼냈는데 이미 visited 되어 있었고, 그런데 그 비용이 더 작으면 어떻게 돼?"
결론부터 말하면,
이런 상황은 '다익스트라 알고리즘이 올바르게 구현되었다면' 절대 발생하지 않는다.
다익스트라는 우선순위 큐(min-heap)를 이용해서 항상 가장 비용이 작은 경로부터 꺼내게 돼 있어. 이 말은 즉:
우선순위 큐는 내부적으로 힙 자료구조를 사용하기 때문에 항상 가장 작은 값을 먼저 꺼낼 수 있어. 다익스트라에서는 "(현재까지 누적된 비용, 노드)" 형태로 저장되므로, 비용이 작은 경로가 먼저 나오고, 그 경로가 해당 노드의 최소 비용 도달 경로로 확정돼.
노드 A → B로 가는 경로가 두 개 있다고 하자.
이 두 경로가 우선순위 큐에 들어갔을 때 순서:
먼저 (2, B)가 꺼내지고 이때 B는 처음 방문되었으므로 seen에 추가돼.
그 다음 (5, B)가 큐에서 나오면? 이미 B in seen 이니까 continue 하고 무시돼.
이미 방문했는데 더 짧은 경로로 또 왔다면 어떡하지?
| 상황 | 처리 방식 | 이유 |
|---|---|---|
| 큐에서 꺼낼 때 더 짧은 경로로 도달했다 | 발생할 수 없음 | 다익스트라는 항상 최단 경로부터 꺼내므로 |
| 이런 상황이 생긴다면 | 구현 오류 가능성 | 큐에서 꺼내기 전에 visited 처리했을 수도 있음 |
핵심 요약
항상 현재까지 가장 빠르게 도달할 수 있는 도시를 기준으로, 더 빠른 길이 있다면 갱신하고, 아니면 무시한다.