Skip to content

Commit 1543fea

Browse files
committed
💥 [breaking] refactor: refactor dijkstra to make it more robust
1 parent e8882e1 commit 1543fea

File tree

4 files changed

+171
-67
lines changed

4 files changed

+171
-67
lines changed

Diff for: .vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"gitmoji",
77
"guanghechen",
88
"Isap",
9+
"kase",
910
"leetcode",
1011
"lowbit",
1112
"manacher",

Diff for: packages/dijkstra/README.md

+88-34
Original file line numberDiff line numberDiff line change
@@ -82,53 +82,107 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
8282

8383
## Usage
8484

85-
A solution for leetcode "Number of Ways to Arrive at Destination"
86-
(https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/):
87-
88-
```typescript
89-
import type { DijkstraEdge } from '@algorithm.ts/dijkstra'
90-
import dijkstra from '@algorithm.ts/dijkstra'
91-
92-
export function countPaths(N: number, roads: number[][]): number {
93-
const MOD = 1e9 + 7
94-
const G: Array<Array<DijkstraEdge<number>>> = new Array(N)
95-
for (let i = 0; i < N; ++i) G[i] = []
96-
for (const road of roads) {
97-
G[road[0]].push({ to: road[1], cost: road[2] })
98-
G[road[1]].push({ to: road[0], cost: road[2] })
99-
}
85+
* Simple
86+
87+
```typescript
88+
import dijkstra from '@algorithm.ts/dijkstra'
89+
90+
const dist: number[] = dijkstra({
91+
N: 4,
92+
source: 0,
93+
edges: [
94+
{ to: 1, cost: 2 },
95+
{ to: 2, cost: 2 },
96+
{ to: 3, cost: 2 },
97+
{ to: 3, cost: 1 },
98+
],
99+
G: [[0], [1, 2], [3], []],
100+
})
101+
// => [0, 2, 4, 4], Which means:
102+
//
103+
// 0 --> 0: cost is 0
104+
// 0 --> 1: cost is 2
105+
// 0 --> 2: cost is 4
106+
// 0 --> 3: cost is 4
107+
```
108+
109+
* Pass custom `dist` array.
110+
111+
```typescript
112+
import dijkstra from '@algorithm.ts/dijkstra'
113+
114+
const dist: number[] = []
115+
dijkstra(
116+
{
117+
N: 4,
118+
source: 0,
119+
edges: [
120+
{ to: 1, cost: 2 },
121+
{ to: 2, cost: 2 },
122+
{ to: 3, cost: 2 },
123+
{ to: 3, cost: 1 },
124+
],
125+
G: [[0], [1, 2], [3], []],
126+
},
127+
undefined,
128+
dist
129+
)
130+
131+
dist // => [0, 2, 4, 4]
132+
```
133+
134+
### Example
100135

101-
const source = 0
102-
const target = N - 1
103-
const dist: number[] = dijkstra<number>(N, target, G, 0, 1e12)
136+
* A solution for leetcode "Number of Ways to Arrive at Destination"
137+
(https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/):
104138

105-
const dp: number[] = new Array(N).fill(-1)
106-
return dfs(source)
139+
```typescript
140+
import type { DijkstraEdge } from '@algorithm.ts/dijkstra'
141+
import dijkstra from '@algorithm.ts/dijkstra'
142+
143+
export function countPaths(N: number, roads: number[][]): number {
144+
const edges: DijkstraEdge[] = []
145+
const G: number[][] = new Array(N)
146+
for (let i = 0; i < N; ++i) G[i] = []
147+
for (const [from, to, cost] of roads) {
148+
G[from].push(edges.length)
149+
edges.push({ to, cost })
150+
151+
G[to].push(edges.length)
152+
edges.push({ to: from, cost })
153+
}
107154

108-
function dfs(o: number): number {
109-
if (o === target) return 1
155+
const source = 0
156+
const target = N - 1
157+
const dist: number[] = dijkstra({ N, source: target, edges, G }, 1e12)
110158

111-
let answer = dp[o]
112-
if (answer !== -1) return answer
159+
const dp: number[] = new Array(N).fill(-1)
160+
return dfs(source)
113161

114-
answer = 0
115-
const d = dist[o]
116-
for (const e of G[o]) {
117-
if (dist[e.to] + e.cost === d) {
118-
const t = dfs(e.to)
119-
answer = modAdd(answer, t)
162+
function dfs(o: number): number {
163+
if (o === target) return 1
164+
165+
let answer = dp[o]
166+
if (answer !== -1) return answer
167+
168+
answer = 0
169+
const d = dist[o]
170+
for (const idx of G[o]) {
171+
const e: DijkstraEdge = edges[idx]
172+
if (dist[e.to] + e.cost === d) {
173+
const t = dfs(e.to)
174+
answer = modAdd(answer, t)
175+
}
120176
}
177+
return dp[o] = answer
121178
}
122-
dp[o] = answer
123-
return answer
124179
}
125180

126181
function modAdd(x: number, y: number): number {
127182
const z: number = x + y
128183
return z < MOD ? z : z - MOD
129184
}
130-
}
131-
```
185+
```
132186

133187

134188
## Related

Diff for: packages/dijkstra/__test__/dijkstra.spec.ts

+37-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1-
import type { DijkstraEdge } from '../src'
1+
import type { IEdge } from '../src'
22
import dijkstra from '../src'
33

4-
describe('dijkstra', function () {
4+
describe('basic', function () {
5+
test('simple', function () {
6+
expect(
7+
dijkstra({
8+
N: 4,
9+
source: 0,
10+
edges: [
11+
{ to: 1, cost: 2 },
12+
{ to: 2, cost: 2 },
13+
{ to: 3, cost: 2 },
14+
{ to: 3, cost: 1 },
15+
],
16+
G: [[0], [1, 2], [3], []],
17+
}),
18+
).toEqual([0, 2, 4, 4])
19+
})
20+
})
21+
22+
describe('leetcode', function () {
523
// https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/
624
test('leetcode/number-of-ways-to-arrive-at-destination', function () {
725
const data: any[] = [
@@ -29,24 +47,32 @@ describe('dijkstra', function () {
2947
},
3048
]
3149

50+
const customDist: number[] = []
3251
for (const kase of data) {
33-
expect(countPaths(kase.input[0], kase.input[1])).toEqual(kase.answer)
52+
const [N, roads] = kase.input
53+
54+
expect(countPaths(N, roads)).toEqual(kase.answer)
55+
expect(countPaths(N, roads, customDist)).toEqual(kase.answer)
3456
}
3557
})
3658
})
3759

3860
const MOD = 1e9 + 7
39-
function countPaths(N: number, roads: number[][]): number {
40-
const G: Array<Array<DijkstraEdge<number>>> = new Array(N)
61+
function countPaths(N: number, roads: number[][], customDist?: number[]): number {
62+
const edges: IEdge[] = []
63+
const G: number[][] = new Array(N)
4164
for (let i = 0; i < N; ++i) G[i] = []
42-
for (const road of roads) {
43-
G[road[0]].push({ to: road[1], cost: road[2] })
44-
G[road[1]].push({ to: road[0], cost: road[2] })
65+
for (const [from, to, cost] of roads) {
66+
G[from].push(edges.length)
67+
edges.push({ to, cost })
68+
69+
G[to].push(edges.length)
70+
edges.push({ to: from, cost })
4571
}
4672

4773
const source = 0
4874
const target = N - 1
49-
const dist: number[] = dijkstra<number>(N, target, G, 0, 1e12)
75+
const dist: number[] = dijkstra({ N, source: target, edges, G }, 1e12, customDist)
5076

5177
const dp: number[] = new Array(N).fill(-1)
5278
return dfs(source)
@@ -59,7 +85,8 @@ function countPaths(N: number, roads: number[][]): number {
5985

6086
answer = 0
6187
const d = dist[o]
62-
for (const e of G[o]) {
88+
for (const idx of G[o]) {
89+
const e: IEdge = edges[idx]
6390
if (dist[e.to] + e.cost === d) {
6491
const t = dfs(e.to)
6592
answer = modAdd(answer, t)

Diff for: packages/dijkstra/src/index.ts

+45-23
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,73 @@
11
import { createPriorityQueue } from '@algorithm.ts/priority-queue'
22

3-
export interface DijkstraEdge<T extends number | bigint> {
3+
export interface IEdge {
44
/**
55
* The other end of the edge.
66
*/
77
to: number
88
/**
99
* The cost of walking along this side.
1010
*/
11-
cost: T
11+
cost: number
1212
}
1313

14+
export interface IGraph {
15+
/**
16+
* The number of nodes in the graph. (0-index)
17+
*/
18+
N: number
19+
/**
20+
* The source node. (0-index)
21+
*/
22+
source: number
23+
/**
24+
* Graph edges.
25+
*/
26+
edges: ReadonlyArray<IEdge>
27+
/**
28+
* Adjacency list. G[i] represent the index list of the edges start from node i.
29+
*/
30+
G: ReadonlyArray<ReadonlyArray<number>>
31+
}
32+
33+
const ZERO = 0
34+
const DEFAULT_INF = Math.floor(Number.MAX_SAFE_INTEGER / 2)
35+
const Q = createPriorityQueue<{ pos: number; cost: number }>((x, y) => {
36+
if (x.cost === y.cost) return 0
37+
return x.cost < y.cost ? 1 : -1
38+
})
39+
1440
/**
1541
* The dijkstra algorithm, optimized with priority queue.
1642
*
17-
* @param N the number of nodes in the graph
18-
* @param source the source node
19-
* @param G edges of the graph. G[i] represent the edge list start from node i.
20-
* @param ZERO 0 for number, 0n for bigint.
21-
* @param INF a big number, such as Number.MAX_SAFE_INTEGER
43+
* @param INF A big number, representing the unreachable cost.
44+
* @param customDist
2245
* @returns
2346
*
2447
* @see https://me.guanghechen.com/post/algorithm/graph/shortest-path/dijkstra
2548
*/
26-
export function dijkstra<T extends number | bigint>(
27-
N: number,
28-
source: number,
29-
G: ReadonlyArray<ReadonlyArray<DijkstraEdge<T>>>,
30-
ZERO: T,
31-
INF: T,
32-
): T[] {
33-
const dist: T[] = new Array(N).fill(INF)
34-
const Q = createPriorityQueue<{ pos: number; cost: T }>((x, y) => y.cost - x.cost)
49+
export function dijkstra(
50+
graph: IGraph,
51+
INF: number = DEFAULT_INF,
52+
customDist?: number[],
53+
): number[] {
54+
const { N, source, edges, G } = graph
55+
const dist: number[] = customDist ?? []
56+
if (dist.length < N) dist.length = N
3557

36-
// eslint-disable-next-line no-param-reassign
58+
dist.fill(INF, 0, N)
3759
dist[source] = ZERO
3860
Q.enqueue({ pos: source, cost: ZERO })
3961

4062
while (Q.size() > 0) {
4163
const { pos, cost } = Q.dequeue()!
4264
if (dist[pos] < cost) continue
43-
for (const e of G[pos]) {
44-
const candidate: T = (dist[pos] as any) + e.cost
45-
if (dist[e.to] > candidate) {
46-
// eslint-disable-next-line no-param-reassign
47-
dist[e.to] = candidate
48-
Q.enqueue({ pos: e.to, cost: dist[e.to] })
65+
for (const idx of G[pos]) {
66+
const edge: IEdge = edges[idx]
67+
const candidate: number = dist[pos] + edge.cost
68+
if (dist[edge.to] > candidate) {
69+
dist[edge.to] = candidate
70+
Q.enqueue({ pos: edge.to, cost: dist[edge.to] })
4971
}
5072
}
5173
}

0 commit comments

Comments
 (0)