Skip to content

Commit 2ebf9a8

Browse files
committed
✨ feat: refactor bellman-ford
1 parent 1059ed6 commit 2ebf9a8

8 files changed

+319
-210
lines changed

Diff for: MIGRATION.md

+37
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,40 @@
44

55
No breaking changes.
66

7+
8+
### @algorithm.ts/bellman-ford
9+
10+
1. Constructor: Only the `INF` props reserved, the `from`, `dist`, `inq`, `inqTimes` are not
11+
supported anymore.
12+
2. Get shortest path: no builtin `getShortestPathTo` anymore, call the `getShortestPath` from the
13+
`@algorithm.ts/graph` instead.
14+
3. `bellmanFord` returns a structured result instead of a boolean value.
15+
16+
```typescript
17+
export type IBellmanFordResult<C extends number | bigint> =
18+
| {
19+
// There is at least one negative cycle in the graph, so the shortest path is not existed.
20+
hasNegativeCycle: true
21+
}
22+
| {
23+
hasNegativeCycle: false
24+
/**
25+
* A big number, representing the unreachable cost.
26+
*/
27+
INF: C
28+
/**
29+
* Source point
30+
*/
31+
source: number
32+
/**
33+
* Record the shortest path parent source point to the specified point.
34+
* For example: bestFrom[x] represents the previous position of x in the shortest path
35+
* parent the source point to x.
36+
*/
37+
bestFrom: ReadonlyArray<number>
38+
/**
39+
* An array recording the shortest distance to the source point.
40+
*/
41+
dist: ReadonlyArray<C>
42+
}
43+
```

Diff for: packages/bellman-ford/README.md

+42-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<header>
22
<h1 align="center">
3-
<a href="https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/bellman-ford#readme">@algorithm.ts/bellman-ford</a>
3+
<a href="https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/bellman-ford#readme">@algorithm.ts/bellman-ford</a>
44
</h1>
55
<div align="center">
66
<a href="https://www.npmjs.com/package/@algorithm.ts/bellman-ford">
@@ -77,6 +77,7 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
7777
yarn add @algorithm.ts/bellman-ford
7878
```
7979

80+
8081
## Usage
8182

8283
* Simple
@@ -85,7 +86,7 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
8586
import type { IGraph } from '@algorithm.ts/bellman-ford'
8687
import bellmanFord from '@algorithm.ts/bellman-ford'
8788

88-
const graph: IGraph = {
89+
const graph: IBellmanFordGraph<number> = {
8990
N: 4,
9091
source: 0,
9192
edges: [
@@ -96,27 +97,30 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
9697
],
9798
G: [[0], [1, 2], [3], []],
9899
}
99-
const dist: number[] = []
100-
bellmanFord(graph, { dist }) // => true, which means there is no negative-cycle.
101-
dist
102-
// => [0, 2, 4, 4]
103-
//
104-
// Which means:
105-
// 0 --> 0: cost is 0
106-
// 0 --> 1: cost is 2
107-
// 0 --> 2: cost is 4
108-
// 0 --> 3: cost is 4
100+
101+
const result = bellmanFord(graph)
102+
/**
103+
* {
104+
* hasNegativeCycle: false, // there is no negative-cycle.
105+
* INF: 4503599627370494,
106+
* source: 0,
107+
* bestFrom: ,
108+
* dist: [0, 2, 4, 4]
109+
* }
110+
*
111+
* For dist:
112+
* 0 --> 0: cost is 0
113+
* 0 --> 1: cost is 2
114+
* 0 --> 2: cost is 4
115+
* 0 --> 3: cost is 4
116+
*/
109117
```
110118

111119
* Options
112120

113-
Name | Type | Required | Description
114-
:----------:|:-----------:|:---------:|:----------------
115-
`INF` | `number` | `false` | A big number, representing the unreachable cost.
116-
`from` | `number[]` | `false` | Record the shortest path parent source point to the specified point.
117-
`dist` | `number[]` | `false` | An array recording the shortest distance to the source point.
118-
`inq` | `boolean` | `false` | Used to check if an element is already in the queue.
119-
`inqTimes` | `number[]` | `false` | Record the number of times an element is enqueued, used to check whether there is a negative cycle.
121+
Name | Type | Required | Description
122+
:----------:|:---------------:|:---------:|:----------------
123+
`INF` | `number|bigint` | `false` | A big number, representing the unreachable cost.
120124

121125

122126
### Example
@@ -125,6 +129,7 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
125129

126130
```typescript
127131
import bellmanFord from '@algorithm.ts/bellman-ford'
132+
import { getShortestPath } from '@algorithm.ts/graph'
128133

129134
const A = 0
130135
const B = 1
@@ -146,29 +151,25 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
146151
G: [[0], [1, 2], [3, 4], [5]],
147152
}
148153

149-
const noNegativeCycle: boolean = bellmanFord(graph, undefined, context => {
150-
const a2aPath: number[] = context.getShortestPathTo(A)
151-
const a2bPath: number[] = context.getShortestPathTo(B)
152-
const a2cPath: number[] = context.getShortestPathTo(C)
153-
const a2dPath: number[] = context.getShortestPathTo(D)
154-
155-
a2aPath // => [0]
156-
a2bPath // => [0, 1]
157-
a2cPath // => [0, 1, 2]
158-
a2dPath // => [0, 1, 2, 3]
159-
})
154+
const result = _bellmanFord.bellmanFord(graph)
155+
assert(result.negativeCycle === false)
156+
157+
getShortestPath(result.bestFrom, Nodes.A, Nodes.A) // [Nodes.A]
158+
getShortestPath(result.bestFrom, Nodes.A, Nodes.B) // [Nodes.A, Nodes.B]
159+
getShortestPath(result.bestFrom, Nodes.A, Nodes.C) // [Nodes.A, Nodes.B, Nodes.C]
160+
getShortestPath(result.bestFrom, Nodes.A, Nodes.D) // [Nodes.A, Nodes.B, Nodes.C, Nodes.D])
160161
```
161162

162163
* A solution for leetcode "Number of Ways to Arrive at Destination"
163164
(https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/):
164165

165166
```typescript
166-
import type { IEdge, IGraph } from '@algorithm.ts/bellman-ford'
167-
import bellmanFord from '@algorithm.ts/bellman-ford'
167+
import type { IBellmanFordEdge, IBellmanFordGraph } from '@algorithm.ts/bellman-ford'
168+
import { bellmanFord } from '@algorithm.ts/bellman-ford'
168169

169170
const MOD = 1e9 + 7
170171
export function countPaths(N: number, roads: number[][]): number {
171-
const edges: IEdge[] = []
172+
const edges: Array<IBellmanFordEdge<number>> = []
172173
const G: number[][] = new Array(N)
173174
for (let i = 0; i < N; ++i) G[i] = []
174175
for (const [from, to, cost] of roads) {
@@ -181,10 +182,11 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
181182

182183
const source = 0
183184
const target = N - 1
184-
const graph: IGraph = { N, source: target, edges, G }
185-
const dist: number[] = customDist ?? []
186-
bellmanFord.bellmanFord(graph, { INF: 1e12, dist })
185+
const graph: IBellmanFordGraph<number> = { N, source: target, edges, G }
186+
const result = bellmanFord(graph, { INF: 1e12 })
187+
if (result.hasNegativeCycle) return -1
187188

189+
const { dist } = result
188190
const dp: number[] = new Array(N).fill(-1)
189191
return dfs(source)
190192

@@ -197,7 +199,7 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
197199
answer = 0
198200
const d = dist[o]
199201
for (const idx of G[o]) {
200-
const e: IEdge = edges[idx]
202+
const e: IBellmanFordEdge<number> = edges[idx]
201203
if (dist[e.to] + e.cost === d) {
202204
const t = dfs(e.to)
203205
answer = modAdd(answer, t)
@@ -218,9 +220,9 @@ The following definition is quoted from Wikipedia (https://en.wikipedia.org/wiki
218220

219221
* 《算法竞赛入门经典(第2版)》(刘汝佳): P363 Bellman-Ford 算法
220222
* [bellman-ford | Wikipedia][wikipedia-bellman-ford]
221-
* [@algorithm.ts/circular-queue][]
223+
* [@algorithm.ts/queue][]
222224

223225

224-
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/bellman-ford#readme
226+
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/bellman-ford#readme
225227
[wikipedia-bellman-ford]: https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
226-
[@algorithm.ts/circular-queue]: https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/circular-queue
228+
[@algorithm.ts/queue]: https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/queue

Diff for: packages/bellman-ford/__test__/bellman-ford.spec.ts

+95-55
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
1-
import { buildEdgeMap } from '@algorithm.ts/graph'
2-
import { testOjCodes } from 'jest.setup'
1+
import { TestOjDataProblemKey, testOjCodes } from '@@/fixtures/test-util/oj-data'
2+
import { buildEdgeMap, getShortestPath } from '@algorithm.ts/graph'
3+
import assert from 'assert'
34
import type { IBellmanFordGraph } from '../src'
4-
import { BellmanFord, bellmanFord } from '../src'
5+
import { BellmanFord, bellmanFord, bellmanFordBigint } from '../src'
56

67
describe('basic', function () {
7-
test('no negative cycle', function () {
8-
const graph: IBellmanFordGraph = {
9-
N: 4,
10-
source: 0,
11-
edges: [
12-
{ to: 1, cost: 2 },
13-
{ to: 2, cost: 2 },
14-
{ to: 3, cost: 2 },
15-
{ to: 3, cost: 1 },
16-
],
17-
G: [[0], [1, 2], [3], []],
18-
}
19-
const dist: number[] = []
8+
describe('bellmanFord', function () {
9+
test('no negative cycle', function () {
10+
const graph: IBellmanFordGraph<number> = {
11+
N: 4,
12+
source: 0,
13+
edges: [
14+
{ to: 1, cost: 2 },
15+
{ to: 2, cost: 2 },
16+
{ to: 3, cost: 2 },
17+
{ to: 3, cost: 1 },
18+
],
19+
G: [[0], [1, 2], [3], []],
20+
}
21+
22+
const result = bellmanFord(graph)
23+
expect(result.hasNegativeCycle).toEqual(false)
24+
25+
assert(result.hasNegativeCycle === false)
26+
expect(result.dist.slice(0, graph.N)).toEqual([0, 2, 4, 4])
27+
})
2028

21-
expect(bellmanFord(graph, { dist })).toEqual(true)
22-
expect(dist.slice(0, graph.N)).toEqual([0, 2, 4, 4])
29+
test('negative cycle', function () {
30+
const graph: IBellmanFordGraph<number> = {
31+
N: 4,
32+
source: 0,
33+
edges: [
34+
{ to: 1, cost: -2 },
35+
{ to: 0, cost: -2 },
36+
{ to: 3, cost: 2 },
37+
{ to: 3, cost: 1 },
38+
],
39+
G: [[0], [1, 2], [3], []],
40+
}
41+
expect(bellmanFord(graph)).toEqual({ hasNegativeCycle: true })
42+
})
2343
})
2444

25-
test('negative cycle', function () {
26-
const dist: number[] = []
27-
const graph: IBellmanFordGraph = {
28-
N: 4,
29-
source: 0,
30-
edges: [
31-
{ to: 1, cost: -2 },
32-
{ to: 0, cost: -2 },
33-
{ to: 3, cost: 2 },
34-
{ to: 3, cost: 1 },
35-
],
36-
G: [[0], [1, 2], [3], []],
37-
}
38-
expect(bellmanFord(graph, { dist })).toEqual(false)
45+
describe('bellmanFordBigint', function () {
46+
test('no negative cycle', function () {
47+
const graph: IBellmanFordGraph<bigint> = {
48+
N: 4,
49+
source: 0,
50+
edges: [
51+
{ to: 1, cost: 2n },
52+
{ to: 2, cost: 2n },
53+
{ to: 3, cost: 2n },
54+
{ to: 3, cost: 1n },
55+
],
56+
G: [[0], [1, 2], [3], []],
57+
}
58+
59+
const result = bellmanFordBigint(graph)
60+
expect(result.hasNegativeCycle).toEqual(false)
61+
62+
assert(result.hasNegativeCycle === false)
63+
expect(result.dist.slice(0, graph.N)).toEqual([0n, 2n, 4n, 4n])
64+
})
65+
66+
test('negative cycle', function () {
67+
const graph: IBellmanFordGraph<bigint> = {
68+
N: 4,
69+
source: 0,
70+
edges: [
71+
{ to: 1, cost: -2n },
72+
{ to: 0, cost: -2n },
73+
{ to: 3, cost: 2n },
74+
{ to: 3, cost: 1n },
75+
],
76+
G: [[0], [1, 2], [3], []],
77+
}
78+
expect(bellmanFordBigint(graph)).toEqual({ hasNegativeCycle: true })
79+
})
3980
})
4081
})
4182

@@ -58,31 +99,32 @@ describe('shortest path', function () {
5899
{ from: Nodes.D, to: Nodes.C, cost: -5 },
59100
]
60101

61-
const graph: IBellmanFordGraph = {
102+
const graph: IBellmanFordGraph<number> = {
62103
N,
63104
source: Nodes.A,
64105
edges,
65106
G: buildEdgeMap(N, edges),
66107
}
67108

68-
let a2aPath: number[] | undefined
69-
let a2bPath: number[] | undefined
70-
let a2cPath: number[] | undefined
71-
let a2dPath: number[] | undefined
72-
73-
const _bellmanFord = new BellmanFord()
74-
const noNegativeCycle: boolean = _bellmanFord.bellmanFord(graph, undefined, context => {
75-
a2aPath = context.getShortestPathTo(Nodes.A)
76-
a2bPath = context.getShortestPathTo(Nodes.B)
77-
a2cPath = context.getShortestPathTo(Nodes.C)
78-
a2dPath = context.getShortestPathTo(Nodes.D)
109+
const _bellmanFord = new BellmanFord<number>({
110+
ZERO: 0,
111+
INF: Math.floor(Number.MAX_SAFE_INTEGER / 2),
79112
})
113+
const result = _bellmanFord.bellmanFord(graph)
114+
115+
expect(result.hasNegativeCycle).toEqual(false)
116+
assert(result.hasNegativeCycle === false)
117+
const { bestFrom } = result
80118

81-
expect(noNegativeCycle).toEqual(true)
82-
expect(a2aPath).toEqual([Nodes.A])
83-
expect(a2bPath).toEqual([Nodes.A, Nodes.B])
84-
expect(a2cPath).toEqual([Nodes.A, Nodes.B, Nodes.C])
85-
expect(a2dPath).toEqual([Nodes.A, Nodes.B, Nodes.C, Nodes.D])
119+
expect(getShortestPath(bestFrom, Nodes.A, Nodes.A)).toEqual([Nodes.A])
120+
expect(getShortestPath(bestFrom, Nodes.A, Nodes.B)).toEqual([Nodes.A, Nodes.B])
121+
expect(getShortestPath(bestFrom, Nodes.A, Nodes.C)).toEqual([Nodes.A, Nodes.B, Nodes.C])
122+
expect(getShortestPath(bestFrom, Nodes.A, Nodes.D)).toEqual([
123+
Nodes.A,
124+
Nodes.B,
125+
Nodes.C,
126+
Nodes.D,
127+
])
86128
})
87129

88130
test('with negative cycle', function () {
@@ -105,28 +147,26 @@ describe('shortest path', function () {
105147
{ from: Nodes.D, to: Nodes.B, cost: 1 },
106148
]
107149

108-
const graph: IBellmanFordGraph = {
150+
const graph: IBellmanFordGraph<number> = {
109151
N,
110152
source: Nodes.A,
111153
edges,
112154
G: buildEdgeMap(N, edges),
113155
}
114-
115-
const noNegativeCycle: boolean = bellmanFord(graph)
116-
expect(noNegativeCycle).toEqual(false)
156+
expect(bellmanFord(graph)).toEqual({ hasNegativeCycle: true })
117157
})
118158
})
119159

120160
describe('oj', function () {
121161
// https://leetcode.com/problems/number-of-ways-to-arrive-at-destination/
122162
testOjCodes(
123-
'leetcode/number-of-ways-to-arrive-at-destination',
163+
TestOjDataProblemKey.LEETCODE_NUMBER_OF_WAYS_TO_ARRIVE_AT_DESTINATION,
124164
import('./oj/number-of-ways-to-arrive-at-destination'),
125165
)
126166

127167
// https://leetcode.com/problems/maximum-path-quality-of-a-graph/
128168
testOjCodes(
129-
'leetcode/maximum-path-quality-of-a-graph',
169+
TestOjDataProblemKey.LEETCODE_MAXIMUM_PATH_QUALITY_OF_A_GRAPH,
130170
import('./oj/maximum-path-quality-of-a-graph'),
131171
)
132172
})

0 commit comments

Comments
 (0)