|

다익스트라 알고리즘 입문: 가중치가 있는 최단거리는 왜 BFS가 안 될까

다익스트라 알고리즘 입문 대표 이미지
가중치가 붙는 순간 최단거리 문제는 BFS보다 우선순위 기반 탐색이 필요해진다

다익스트라 알고리즘은 코딩테스트에서 자주 나오지만, 많은 입문자가 BFS를 배운 뒤에 “최단거리면 그냥 BFS 아닌가?”에서 한 번 막힙니다. 실제로 어떤 문제는 BFS로 충분하고, 어떤 문제는 BFS로는 틀립니다. 이 경계를 제대로 이해해야 다익스트라가 갑자기 외워야 할 알고리즘이 아니라 자연스러운 확장처럼 보입니다.

이번 글에서는 다익스트라를 공식을 먼저 외우는 방식이 아니라, 가중치가 붙는 순간 왜 BFS 사고방식이 깨지는지부터 설명하겠습니다. 그리고 그 자리를 우선순위 큐가 어떻게 메우는지도 단계적으로 보겠습니다.

다익스트라 알고리즘 입문 요약 카드
핵심 판단 기준을 먼저 잡는 요약 카드

BFS는 왜 최단거리 문제에 강할까

BFS는 간선의 비용이 모두 같을 때 강합니다. 한 칸 이동, 한 번 변환, 한 단계 확장처럼 매번 비용이 동일하다면 먼저 방문한 경로가 곧 가장 짧은 경로가 됩니다. 그래서 queue에 넣는 순서가 곧 거리 순서가 됩니다.

즉 BFS는 “먼저 도착한 것이 가장 짧다”는 가정이 자연스럽게 성립하는 문제에서 잘 맞습니다. 격자 최단거리나 레벨 탐색 문제에서 BFS가 강한 이유가 여기에 있습니다.

이 감각은 BFS 문제 풀이 패턴 글과 직접 연결됩니다.


가중치가 붙는 순간 BFS는 왜 흔들릴까

다익스트라와 BFS 비교 카드
둘의 경계를 비교 카드로 먼저 정리한 이미지

입문자 관점에서 제일 중요한 차이는, BFS는 간선 수가 곧 비용이라는 가정 위에서만 안전하다는 점입니다. 반면 다익스트라는 간선 수보다 누적 비용을 직접 비교해야 하므로, “누가 먼저 queue에 들어왔는가”보다 “누가 지금 더 싼가”가 더 중요합니다.

문제는 이동 비용이 모두 같지 않을 때입니다. 예를 들어 어떤 간선은 비용 1이고, 어떤 간선은 비용 5라면 먼저 방문했다고 해서 더 짧은 경로라고 보장할 수 없습니다. queue는 입력된 순서를 보장할 뿐, 현재까지의 최소 비용 순서를 보장하지 않기 때문입니다.

즉 BFS는 “방문 순서 = 최단거리 순서”라는 전제가 필요합니다. 가중치가 생기면 그 전제가 무너지므로, 더 이상 단순 queue만으로는 현재 가장 유리한 후보를 먼저 꺼낼 수 없습니다.

입문자가 여기서 가장 많이 헷갈리는 부분은 “그래도 먼저 간 길이 더 빨리 도착한 것 아닌가?”입니다. 하지만 가중치가 다르면 간선 개수가 적다고 해서 총 비용이 작은 것이 아닙니다. 즉 BFS는 간선 수를 잘 세지만, 비용 합은 잘 세지 못합니다.


다익스트라는 무엇을 대신 보장할까

다익스트라는 queue 순서 대신, 현재까지 알려진 거리(dist)가 가장 작은 정점부터 처리한다는 원칙을 세웁니다. 그래서 단순 FIFO가 아니라 우선순위 큐가 필요합니다.

즉 다익스트라의 핵심은 그래프를 특별하게 순회하는 것이 아니라, “지금 가장 가까운 후보부터 확정한다”는 규칙을 끝까지 지키는 데 있습니다.

priority_queue 참고처럼 우선순위를 기준으로 가장 중요한 원소를 먼저 꺼내는 구조가 여기서 직접 연결됩니다.

import heapq

def dijkstra(start, graph, n):
    INF = 10**15
    dist = [INF] * n
    dist[start] = 0
    pq = [(0, start)]

    while pq:
        cur_dist, cur = heapq.heappop(pq)
        if cur_dist > dist[cur]:
            continue

        for nxt, weight in graph[cur]:
            nd = cur_dist + weight
            if nd < dist[nxt]:
                dist[nxt] = nd
                heapq.heappush(pq, (nd, nxt))

    return dist

위 코드에서 if cur_dist > dist[cur]: continue 조건도 중요합니다. 우선순위 큐에는 같은 정점이 여러 번 들어갈 수 있기 때문에, 이미 더 짧은 경로가 확정된 오래된 후보는 건너뛰어야 합니다. 이 부분을 모르면 코드가 왜 중복 후보를 허용하는지 이해가 잘 안 됩니다.

또 하나 자주 헷갈리는 점은 방문 배열 처리입니다. BFS처럼 처음 꺼냈다고 바로 “완전 방문”으로 생각하기보다, 현재 후보 거리가 최신인지 dist 배열과 비교하는 습관이 더 중요합니다.

이 코드에서 중요한 줄은 두 군데입니다. 하나는 heap에서 가장 작은 거리 후보를 꺼내는 부분이고, 다른 하나는 더 짧은 경로를 찾았을 때만 dist를 갱신하는 부분입니다. 즉 다익스트라는 방문 여부 자체보다 “현재까지의 최단거리 추정값”을 관리하는 알고리즘에 더 가깝습니다.


우선순위 큐가 왜 꼭 필요한가

만약 단순 queue를 쓰면 먼저 들어온 후보가 먼저 나옵니다. 하지만 다익스트라는 지금까지의 거리 값이 더 작은 후보를 먼저 처리해야 합니다. 이 요구를 가장 자연스럽게 만족하는 자료구조가 우선순위 큐입니다.

즉 다익스트라는 그래프 알고리즘이면서 동시에 우선순위 큐 문제이기도 합니다. 이 점에서 힙 글과도 강하게 이어집니다.


문제에서 어떤 신호가 보이면 다익스트라일까

  • 최단거리인데 간선 비용이 모두 같지 않다
  • 한 시작점에서 여러 정점까지 최소 비용을 구해야 한다
  • 그래프나 격자에서 이동 비용이 칸마다 다르다
  • 현재 가장 유리한 후보를 계속 먼저 확정해야 한다

이 네 가지 중 두세 가지가 같이 보이면 다익스트라 가능성이 큽니다. 반대로 비용이 모두 같다면 굳이 다익스트라까지 갈 필요 없이 BFS가 더 단순하고 적절한 경우가 많습니다.


자주 하는 실수

  1. 가중치가 있는데도 BFS처럼 방문 배열만 보고 끝낸다
  2. 우선순위 큐를 쓰면서도 더 짧은 경로 갱신 조건을 잘못 둔다
  3. 음수 가중치 문제에 다익스트라를 억지로 적용한다
  4. visited를 너무 일찍 확정해서 더 좋은 경로를 놓친다

특히 음수 가중치 문제는 다익스트라의 전제와 맞지 않습니다. 이 경우는 다른 알고리즘 축을 봐야 합니다. 즉 다익스트라는 강력하지만, 아무 최단거리 문제에나 만능은 아닙니다.


다익스트라 알고리즘 입문 동작 그림
동작 과정을 그림으로 다시 풀어 정리한 도식
다익스트라 단계 추적 그림
작은 예시를 단계별로 따라가는 추적 도식

이 추적 그림을 한 번 눈으로 따라가 보면, 다익스트라는 정점을 한 번씩만 방문하는 단순 탐색이 아니라 “현재까지의 최단거리 가설”을 계속 더 나은 값으로 수정해 가는 과정이라는 점이 더 분명해집니다.

마무리

다익스트라를 이해하는 핵심은 공식을 외우는 것이 아니라, 왜 BFS가 더 이상 거리 순서를 보장하지 못하는지 먼저 보는 데 있습니다.

가중치가 붙으면 queue가 아니라 현재 거리 기준 우선순위가 필요하다는 점만 잡혀도, 다익스트라는 훨씬 덜 낯설어집니다.

함께보면 좋은 글