-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
08.29: shortest path, dijkstra(택배 배송)
- Loading branch information
1 parent
ea4b9f8
commit 2c671ad
Showing
1 changed file
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// 택배 배송 : 최단 경로, 다익스트라 | ||
const input = require("fs") | ||
.readFileSync(0, "utf-8") | ||
.toString() | ||
.trim() | ||
.split("\n") | ||
.map((v) => v.split(" ").map((v) => Number(v))); | ||
const [n, m] = input.shift(); | ||
const edges = input; | ||
const shortest = new Array(n + 1).fill(Infinity); | ||
// connected 2차원 배열 메모리 초과 (50000 * 50000) | ||
const connected = new Array(n + 1).fill(null).map(() => new Object()); | ||
const convertKeyTypeToNumber = ([k, v]) => [Number(k), v]; | ||
|
||
shortest[1] = 0; | ||
|
||
for (const [start, end, cost] of edges) { | ||
const prev = connected[start][end] === undefined ? Infinity : connected[start][end]; | ||
const min = Math.min(prev, cost); | ||
connected[start][end] = min; | ||
connected[end][start] = min; | ||
} | ||
|
||
class MinHeap { | ||
constructor() { | ||
this.arr = [null]; | ||
this.size = 0; | ||
} | ||
|
||
push(arr) { | ||
this.arr.push(arr); | ||
this.size++; | ||
|
||
let curr = this.size; | ||
let parent = Math.floor(this.size / 2); | ||
|
||
while (curr !== 1) { | ||
if (this.arr[curr][2] < this.arr[parent][2]) { | ||
[this.arr[curr], this.arr[parent]] = [this.arr[parent], this.arr[curr]]; | ||
} | ||
curr = parent; | ||
parent = Math.floor(curr / 2); | ||
} | ||
} | ||
|
||
pop() { | ||
if (this.size === 0) { | ||
return; | ||
} | ||
if (this.size === 1) { | ||
this.arr.pop(); | ||
this.size--; | ||
return; | ||
} | ||
const popped = this.arr.pop(); | ||
let curr = 1; | ||
let left = curr * 2; | ||
let right = left + 1; | ||
|
||
this.arr[1] = popped; | ||
while (true) { | ||
if (this.arr[left] === undefined) { | ||
break; | ||
} else if (this.arr[right] === undefined) { | ||
// left와 비교 | ||
if (this.arr[curr][2] > this.arr[left][2]) { | ||
[this.arr[curr], this.arr[left]] = [this.arr[left], this.arr[curr]]; | ||
} | ||
curr = left; | ||
left = curr * 2; | ||
right = left + 1; | ||
} else { | ||
// left, right와 비교 | ||
const isLeftSmall = this.arr[right][2] > this.arr[left][2]; | ||
if (isLeftSmall) { | ||
if (this.arr[curr][2] > this.arr[left][2]) { | ||
[this.arr[curr], this.arr[left]] = [this.arr[left], this.arr[curr]]; | ||
} | ||
} else { | ||
if (this.arr[curr][2] > this.arr[right][2]) { | ||
[this.arr[curr], this.arr[right]] = [this.arr[right], this.arr[curr]]; | ||
} | ||
} | ||
curr = isLeftSmall ? left : right; | ||
left = curr * 2; | ||
right = left + 1; | ||
} | ||
} | ||
this.size--; | ||
} | ||
|
||
top() { | ||
return this.size <= 0 ? null : this.arr[1]; | ||
} | ||
} | ||
|
||
const minHeap = new MinHeap(); | ||
|
||
for (const [end, cost] of Object.entries(connected[1]).map(convertKeyTypeToNumber)) { | ||
if (connected[1][end] !== undefined) { | ||
minHeap.push([1, end, cost]); | ||
} | ||
} | ||
|
||
while (minHeap.size) { | ||
const [start, end, cost] = minHeap.top(); | ||
|
||
minHeap.pop(); | ||
console.log(start, end, cost, shortest[end], cost); | ||
if (shortest[end] > cost) { | ||
shortest[end] = cost; | ||
for (const [nextEnd, nextCost] of Object.entries(connected[end]).map(convertKeyTypeToNumber)) { | ||
const prev = shortest[end]; | ||
// 연결된 것 중에 이미 최소값이 정해진 것은 제외 | ||
if (nextCost !== Infinity && shortest[nextEnd] === Infinity) { | ||
minHeap.push([start, nextEnd, nextCost + prev]); | ||
} | ||
} | ||
} | ||
|
||
console.log(minHeap.arr, shortest); | ||
} | ||
|
||
console.log(shortest[n]); | ||
|
||
// 5 5 | ||
// 1 2 1 | ||
// 1 3 2 | ||
// 2 4 1 | ||
// 4 5 1 | ||
// 3 5 0 | ||
|
||
// 1 -> 2 -> 4 -> 5: 3 | ||
// 1 -> 3 -> 5: 2 | ||
|
||
// 오답: 현재 선택한 간선의 값 비교 | ||
// [ null, [ 2, 4, 1 ], [ 1, 3, 2 ] ] [ Infinity, 0, 1, Infinity, Infinity, Infinity ] | ||
// [ null, [ 4, 5, 1 ], [ 1, 3, 2 ] ] [ Infinity, 0, 1, Infinity, 2, Infinity ] | ||
// [ null, [ 5, 3, 0 ], [ 1, 3, 2 ] ] [ Infinity, 0, 1, Infinity, 2, 3 ] | ||
// [ null, [ 1, 3, 2 ] ] [ Infinity, 0, 1, 3, 2, 3 ] | ||
// [ null ] [ Infinity, 0, 1, 2, 2, 3 ] | ||
// - 1 -> 2 -> 4 -> 5가 먼저 저장되고, 1 -> 3 -> 5에서 1 -> 3이 새로 업데이트될 때, 1 -> 3 -> 5가 업데이트되지 않는 문제 | ||
// - 1 -> 3: 3에서 2로 업데이트 함에따라 이 경로를 사용하는 다른 경로도 최신화되어야 하는데 그렇지 않음 | ||
// - 업데이트를 해준다면 상관없지만, 아래 방법을 사용하면 업데이트 과정이 필요없이 문제를 해결할 수 있음 | ||
|
||
// 정답: 현재까지의 최소값 + 현재 선택한 간선의 값 비교 | ||
// [ null, [ 1, 3, 2 ], [ 2, 4, 2 ] ] [ Infinity, 0, 1, Infinity, Infinity, Infinity ] | ||
// [ null, [ 2, 4, 2 ], [ 3, 5, 2 ] ] [ Infinity, 0, 1, 2, Infinity, Infinity ] | ||
// [ null, [ 3, 5, 2 ], [ 4, 5, 3 ] ] [ Infinity, 0, 1, 2, 2, Infinity ] | ||
// [ null, [ 4, 5, 3 ] ] [ Infinity, 0, 1, 2, 2, 2 ] | ||
// [ null ] [ Infinity, 0, 1, 2, 2, 2 ] | ||
// - start를 사용하지 않기 때문에 모두 1로 생각해도 됨 | ||
// - 모든 경우의 수를 다 고려하기 떄문에 [4, 5, 3]이 [3, 5, 2]에 의해 적용되지 않게 됨 | ||
// - 정점이 선택되면, 해당 정점까지의 최단 경로가 확정됨 | ||
// - 즉, 큐에서 꺼낸 정점은 더 이상 갱신될 필요가 없는 최단 경로를 가진 정점 | ||
|
||
// 우선순위 큐에서 비교해야 하는 값은 현재 선택한 간선의 값이 아니라 "현재까지의 최소값 + 현재 선택한 간선의 값" | ||
// - 즉, "누적된 경로 비용"을 관리해야 함 | ||
// - why? 정확한 누적 비용을 추적하여 최단 경로를 결정하기 위해서 | ||
// - 누적 비용을 사용하지 않으면 전체 경로의 비용이 무시되고, 부분 경로만을 기준으로 최단 경로를 판단 |