읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- JavaScript 최소 힙 구현: 우선순위 큐의 push·pop과 비교 함수힙은 가장 작은 후보나 가장 큰 후보를 먼저 꺼내도록 순서를 유지합니다.
- JavaScript 그래프 구현: 인접 리스트·인접 행렬 비교와 BFS그래프는 점인 정점과 점 사이의 연결인 간선으로 관계를 나타냅니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 트리·그래프와 최단거리 · 알고리즘 개념
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
다익스트라는 거리 후보를 어떻게 완화하고 확정하나요?
다익스트라는 아직 처리하지 않은 거리 후보 중 가장 작은 것을 골라 이웃의 거리를 줄입니다. 모든 간선의 가중치가 0 이상이면 꺼낸 최신 거리보다 더 짧은 경로가 나중에 나타나지 않습니다.

0→1의 비용이 7이어도 0→2가 2, 2→1이 1이면 1까지의 거리는 3으로 줄어듭니다. 먼저 발견한 7은 확정값이 아닙니다.
적용 조건·범위: 정점 번호는 0부터 시작하며 각 간선은 [도착 정점, 가중치]입니다. 그래프는 방향 그래프이고 가중치는 비음수 정수, 거리 합은 Number의 안전한 정수 범위 안으로 제한합니다. 아래 예제에는 실행에 필요한 최소 힙 함수도 포함되어 있습니다.
본문 근거: Competitive Programmer’s Handbook — Antti Laaksonen · Princeton Algorithms — Shortest Paths 강의 자료

이 글에서 할 일: 거리 완화와 확정을 구분하고 오래된 힙 후보를 건너뜁니다. 선수 지식: 그래프의 인접 리스트·최소 힙.
더 짧은 경로를 발견하면 거리를 갱신합니다
dist[v]는 지금까지 발견한 v까지의 가장 짧은 거리입니다. 시작점은 0, 나머지는 Infinity에서 시작합니다. u에서 v로 가는 가중치 w를 보았을 때 dist[u]+w < dist[v]이면 더 짧은 경로를 찾은 것입니다. 이 갱신을 완화라고 합니다.

| 꺼낸 후보 | 거리 변화 | 판단 |
|---|---|---|
| (0,0) | dist[1]=7, dist[2]=2 | 시작점 처리 |
| (2,2) | dist[1]=3, dist[3]=8 | 2를 거쳐 개선 |
| (3,1) | dist[3]=4 | 1을 거쳐 개선 |
| (4,3) | 변화 없음 | 최신 후보 처리 |
| (7,1), (8,3) | 변화 없음 | 저장된 거리와 달라 건너뜀 |
표의 후보는 (거리,정점)입니다. 발견 시점에 방문 완료로 표시하면 7을 3으로 줄이는 경로를 놓칠 수 있습니다. 최소 힙에서 꺼낸 후보가 현재 dist와 같을 때 그 정점을 처리합니다.
최소 힙으로 실행하는 전체 예제
정점 번호는 0부터 시작하며 각 간선은 [도착 정점, 가중치]입니다. 그래프는 방향 그래프이고 가중치는 비음수 정수, 거리 합은 Number의 안전한 정수 범위 안으로 제한합니다. 아래 예제에는 실행에 필요한 최소 힙 함수도 포함되어 있습니다.

JavaScript 예제: 시작점 0에서 각 정점까지의 최단거리를 구합니다. 앞의 후보 처리 표와 아래 코드·실행 결과를 함께 보며, 더 짧은 경로로 거리를 갱신하고 오래된 후보를 건너뛰는 과정을 살펴보세요. 직접 실행하지 않아도 설명을 따라갈 수 있습니다.
function dijkstra(graph, start) {
// 정점은 0부터, 간선은 [도착 정점, 비음수 정수 가중치]
// 거리 합은 Number의 안전한 정수 범위 안이라고 가정합니다.
const heap = [];
function push(item) {
heap.push(item);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[p][0] <= heap[i][0]) break;
[heap[p], heap[i]] = [heap[i], heap[p]];
i = p;
}
}
function pop() {
const top = heap[0];
const last = heap.pop();
if (heap.length === 0) return top;
heap[0] = last;
let i = 0;
while (2 * i + 1 < heap.length) {
let child = 2 * i + 1;
if (child + 1 < heap.length && heap[child + 1][0] < heap[child][0]) child++;
if (heap[i][0] <= heap[child][0]) break;
[heap[i], heap[child]] = [heap[child], heap[i]];
i = child;
}
return top;
}
const dist = Array(graph.length).fill(Infinity);
dist[start] = 0;
push([0, start]);
while (heap.length > 0) {
const [cost, vertex] = pop();
if (cost !== dist[vertex]) continue;
for (const [next, weight] of graph[vertex]) {
const candidate = cost + weight;
if (candidate < dist[next]) {
dist[next] = candidate;
push([candidate, next]);
}
}
}
return dist;
}
const graph = [[[1, 7], [2, 2]], [[3, 1]], [[1, 1], [3, 6]], [], []];
console.log(dijkstra(graph, 0).join(','));
실행 결과
0,3,2,4,Infinity
정점 4는 시작점에서 도달할 수 없어 Infinity로 남습니다. cost !== dist[vertex]는 더 좋은 경로가 생긴 뒤 힙에 남아 있는 옛 후보를 건너뜁니다. 같은 거리에는 새 후보를 넣지 않으므로 0 가중치 순환에서도 같은 상태를 끝없이 추가하지 않습니다.
내 컴퓨터에서 직접 실행하기 — 다익스트라 예제 (선택)
코드를 직접 바꿔보고 싶은 분을 위한 안내입니다. 이 과정은 방문자 자신의 컴퓨터에서 진행하며, 글을 읽기 위해 반드시 실행할 필요는 없습니다.
준비물: Node.js와 코드 편집기인 Visual Studio Code(VS Code). Node.js는 JavaScript 파일을 컴퓨터에서 실행하는 프로그램입니다. 아직 설치하지 않았다면 두 프로그램의 공식 사이트에서 설치한 뒤 진행하세요.
- 컴퓨터에 연습용 폴더를 하나 만들고, VS Code에서 파일 → 폴더 열기로 그 폴더를 엽니다.
- 그 폴더 안에
dijkstra.js파일을 만듭니다. 위의 JavaScript 예제 전체를 복사해 붙여넣고 저장하세요. 마지막console.log줄도 포함하며, 파일 이름 끝이.js.txt가 되지 않도록 확인합니다. - VS Code의 터미널 → 새 터미널을 선택합니다. 아래쪽에 열린 터미널은 명령어를 입력하는 창입니다. 다음 명령으로 Node.js 설치 여부를 확인합니다.
node --version
v로 시작하는 버전 번호가 나오면 실행할 수 있습니다. 명령을 찾을 수 없다는 메시지가 나오면 Node.js를 설치하고 VS Code를 완전히 닫았다가 다시 여세요.
터미널의 현재 위치가 방금 파일을 저장한 폴더인지 확인한 뒤, 다음 명령을 입력하고 Enter를 누릅니다. 이 명령은 JavaScript 파일 안이 아니라 터미널에 입력합니다.
node dijkstra.js
터미널에 나온 값을 위의 실행 결과와 비교하세요. Cannot find module이 나오면 파일 이름과 저장 여부, 터미널에서 연 폴더가 같은지 확인합니다.
비음수 조건이 거리 확정을 보장합니다
꺼낸 최신 거리 d보다 더 짧은 경로가 나중에 생긴다고 가정해 보겠습니다. 그 경로의 아직 처리하지 않은 첫 정점에는 앞선 완화로 d보다 작은 후보가 있어야 합니다. 최소 힙은 그 후보를 먼저 꺼냈어야 하므로 모순입니다. 이후 간선을 더 지나도 거리가 줄지 않는다는 비음수 조건이 이 논증의 핵심입니다.

음수 간선은 이 조건을 깨뜨립니다. 일부 입력에서 우연히 맞더라도 이 코드의 정당성과 비용을 보장하지 않으므로 Bellman–Ford 등 입력에 맞는 방법을 선택합니다. 가중치가 모두 같은 경우에는 BFS도 검토할 수 있습니다.
평행 간선은 각각 완화하며 더 작은 후보가 남습니다. 무방향 그래프라면 간선을 양쪽 인접 리스트에 넣어야 합니다. 시작점이 유효한 정점인지, 숫자 범위를 넘지 않는지는 입력 단계에서 확인합니다.
정점 V개, 간선 E개일 때 이 중복 후보 방식의 이진 힙 구현은 O(V + E log(E+1)) 시간, 입력을 제외하고 O(V+E) 공간을 사용합니다. 평행 간선이 없는 일반적인 그래프에서는 흔히 O((V+E) log V)로 씁니다. 힙의 기존 항목을 줄이는 구현과 중복 후보를 넣는 구현의 공간 상한을 혼동하지 마세요.
직접 확인하고 문제에 적용하기
0→1 비용이 0이고 1→0도 0이면 왜 무한 반복하지 않을까요? 0→1 비용 7과 2의 평행 간선이 있으면 무엇이 남을까요?

답과 이유 확인
같은 거리에는 후보를 추가하지 않고 더 작을 때만 추가하기 때문입니다. 평행 간선의 최종 dist[1]은 2이며, 힙에 남은 거리 7 후보는 꺼낼 때 건너뜁니다.
참고 자료
자료 확인일: 2026년 10월 19일.
- Competitive Programmer’s Handbook — Antti Laaksonen — 13.2절의 비음수 최단 경로와 우선순위 큐.
- Princeton Algorithms — Shortest Paths 강의 자료 — 거리 완화와 비음수 다익스트라의 원리.
부모를 기록한 경로 복원과 최소 힙 후보 선택을 나눕니다
최단 거리의 숫자를 구한 뒤 실제 경로도 필요하다면, 완화에 성공할 때 parent[v]=u를 함께 기록합니다. 0→1 비용 7, 0→2 비용 2, 2→1 비용 1이면 처음에는 parent[1]=0이지만 더 짧은 경로를 발견한 뒤 parent[1]=2로 바뀝니다. 목적지 1에서 2, 다시 0을 거꾸로 따라가서 순서를 뒤집으면 [0,2,1]입니다. 도달하지 못한 목적지는 먼저 거리 Infinity인지 확인하고 경로 없음으로 처리해야 하며, 부모가 없다는 이유만으로 목적지 하나를 유효 경로처럼 반환하면 안 됩니다.

부모 기록은 어떤 간선으로 개선했는지를 남기는 기능이고, 최소 힙은 다음 거리 후보를 빠르게 고르는 자료구조입니다. 미방문 정점을 매번 선형 순회하여 최소 후보를 골라도 부모 기록과 경로 복원을 할 수 있습니다. 따라서 거리 표에 부모를 추가한 개선과 선형 탐색을 힙으로 바꾸는 개선은 별개입니다. 현재의 비음수 가중치·엄격히 더 짧을 때만 완화·오래된 힙 후보 건너뛰기 조건은 유지하며, 부모도 그 유효한 완화 안에서만 갱신합니다. 경로를 뒤집어 읽을 때에는 정점 순서뿐 아니라 실제 간선 비용 합이 저장된 최단 거리와 같은지도 확인하세요.
이어서 공부할 개념
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 층별 방문 순서와 최솟값 추출을 추적하고, 다익스트라의 비음수 조건과 오래된 후보를 설명하세요.
다음 추천 · 이 개념으로 풀 문제: 코딩테스트 고급: 최소 힙으로 다익스트라 최단 경로 구하기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 30개 · 필수 0개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.