bmssp 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -19
- package/docs/index.html +82 -15
- package/index.mjs +19 -9
- package/package.json +1 -1
- package/src/baseCase.mjs +35 -35
- package/src/bmssp.mjs +351 -63
- package/src/findPivots.mjs +28 -29
- package/src/graph.mjs +174 -0
- package/src/tieBreak.mjs +55 -19
package/src/findPivots.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compareKeyParts,
|
|
3
3
|
toBound,
|
|
4
|
-
makeTies,
|
|
5
4
|
relaxEdge,
|
|
6
5
|
RELAX_LOST,
|
|
7
6
|
} from "./tieBreak.mjs";
|
|
@@ -15,17 +14,19 @@ import {
|
|
|
15
14
|
* the pivots need to be carried into the deeper BMSSP recursion.
|
|
16
15
|
*
|
|
17
16
|
* Preconditions (guaranteed by the caller, Algorithm 3):
|
|
18
|
-
* - Every vertex in S is complete (
|
|
17
|
+
* - Every vertex in S is complete (labels.dist holds its true distance).
|
|
19
18
|
* - Every incomplete vertex v with d(v) < B has a shortest path through some
|
|
20
19
|
* complete vertex in S.
|
|
21
20
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
* Since #205 the engine works on dense vertex indices: S holds indices, the
|
|
22
|
+
* graph is the class's CSR bundle, and the labels are the shared typed
|
|
23
|
+
* arrays (see tieBreak's makeLabels) — updated in place, exactly like the
|
|
24
|
+
* paper's global d̂[·], so improvements made here are visible to the levels
|
|
25
|
+
* above. Relaxation is canonical (#163): dist, hops and preds move together
|
|
26
|
+
* under the composite [length, hops, index] order, and an exact-equality
|
|
27
|
+
* result re-admits a vertex to W through its one canonical label-setter (the
|
|
28
|
+
* paper's `≤` made deterministic). The `< B` test only gates membership in
|
|
29
|
+
* W; label updates are not gated.
|
|
29
30
|
*
|
|
30
31
|
* The paper's tight-edge forest falls out of the canonical labels: each
|
|
31
32
|
* vertex of W \ S hangs off its recorded canonical predecessor, which is
|
|
@@ -44,19 +45,20 @@ import {
|
|
|
44
45
|
*
|
|
45
46
|
* @param {number|[number, number, *]} B - Strict upper bound gating membership
|
|
46
47
|
* in W: a number (Infinity is allowed) or a composite bound
|
|
47
|
-
* @param {Set
|
|
48
|
-
*
|
|
49
|
-
* @param {
|
|
48
|
+
* @param {Set<number>|Iterable<number>} S - Non-empty set of complete frontier
|
|
49
|
+
* source indices
|
|
50
|
+
* @param {{ dist: Float64Array, hops: Uint32Array, preds: Int32Array }} labels
|
|
51
|
+
* - Engine label state (d̂[·] and tie-break labels), updated in place
|
|
52
|
+
* @param {{ offsets: Uint32Array, targets: Uint32Array, weights: Float64Array }} csr
|
|
53
|
+
* - The graph in CSR layout over dense indices
|
|
50
54
|
* @param {number} k - Relaxation rounds / tree-size threshold, >= 1 (floored);
|
|
51
55
|
* the paper's ⌊log^(1/3) n⌋
|
|
52
|
-
* @
|
|
53
|
-
*
|
|
54
|
-
* @returns {{ pivots: Set<*>, W: Set<*> }} The pivot subset of S and the set W
|
|
55
|
-
* of vertices touched below B (W always contains S)
|
|
56
|
+
* @returns {{ pivots: Set<number>, W: Set<number> }} The pivot subset of S and
|
|
57
|
+
* the set W of vertex indices touched below B (W always contains S)
|
|
56
58
|
* @throws {Error} If k is not a number >= 1, S is empty, or any source has no
|
|
57
59
|
* finite distance estimate
|
|
58
60
|
*/
|
|
59
|
-
function findPivots(B, S,
|
|
61
|
+
function findPivots(B, S, labels, csr, k) {
|
|
60
62
|
if (typeof k !== "number" || Number.isNaN(k) || k < 1) {
|
|
61
63
|
throw new Error("k must be a number >= 1");
|
|
62
64
|
}
|
|
@@ -66,12 +68,12 @@ function findPivots(B, S, dHat, adjacency, k, ties = makeTies()) {
|
|
|
66
68
|
throw new Error("S must contain at least one source node");
|
|
67
69
|
}
|
|
68
70
|
for (const x of sources) {
|
|
69
|
-
|
|
70
|
-
if (typeof distance !== "number" || !Number.isFinite(distance)) {
|
|
71
|
+
if (!Number.isFinite(labels.dist[x])) {
|
|
71
72
|
throw new Error("every source must have a finite distance estimate");
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
const boundKey = toBound(B);
|
|
76
|
+
const { offsets, targets, weights } = csr;
|
|
75
77
|
|
|
76
78
|
// W accumulates everything relaxed below B; layer is the paper's W_{i-1}
|
|
77
79
|
const sourceSet = new Set(sources);
|
|
@@ -81,20 +83,17 @@ function findPivots(B, S, dHat, adjacency, k, ties = makeTies()) {
|
|
|
81
83
|
for (let i = 1; i <= rounds; i += 1) {
|
|
82
84
|
const nextLayer = new Set();
|
|
83
85
|
for (const u of layer) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const v = edge[0];
|
|
89
|
-
// Canonical relaxation: d̂ updates are not gated by B (paper), and
|
|
90
|
-
// both improvements and exact canonical equality admit v to the
|
|
86
|
+
for (let e = offsets[u]; e < offsets[u + 1]; e += 1) {
|
|
87
|
+
const v = targets[e];
|
|
88
|
+
// Canonical relaxation: label updates are not gated by B (paper),
|
|
89
|
+
// and both improvements and exact canonical equality admit v to the
|
|
91
90
|
// next layer when its key is below B. Non-lost ⇒ v's stored label
|
|
92
91
|
// is the candidate, so the W gate compares the unpacked stored
|
|
93
92
|
// components — no key allocation (#168).
|
|
94
|
-
const result = relaxEdge(u, v,
|
|
93
|
+
const result = relaxEdge(u, v, weights[e], labels);
|
|
95
94
|
if (
|
|
96
95
|
result !== RELAX_LOST &&
|
|
97
|
-
compareKeyParts(
|
|
96
|
+
compareKeyParts(labels.dist[v], labels.hops[v], v, boundKey) < 0
|
|
98
97
|
) {
|
|
99
98
|
nextLayer.add(v);
|
|
100
99
|
}
|
|
@@ -114,7 +113,7 @@ function findPivots(B, S, dHat, adjacency, k, ties = makeTies()) {
|
|
|
114
113
|
const children = new Map();
|
|
115
114
|
for (const v of W) {
|
|
116
115
|
if (sourceSet.has(v)) continue;
|
|
117
|
-
const parent =
|
|
116
|
+
const parent = labels.preds[v];
|
|
118
117
|
if (!children.has(parent)) children.set(parent, []);
|
|
119
118
|
children.get(parent).push(v);
|
|
120
119
|
}
|
package/src/graph.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// #172 — typed / flexible graph inputs.
|
|
2
|
+
//
|
|
3
|
+
// A small builder (`Graph`) plus a normalizer (`normalizeGraphInput`) that
|
|
4
|
+
// lets the BMSSP constructor accept several input shapes and reduce them to
|
|
5
|
+
// one canonical internal form: `{ edges: [[from, to, weight], ...],
|
|
6
|
+
// vertices: [id, ...] }`.
|
|
7
|
+
//
|
|
8
|
+
// `vertices` carries the EXPLICIT vertex universe — every node the caller
|
|
9
|
+
// declared, including isolated ones with no incident edges. Edge endpoints
|
|
10
|
+
// are still folded in by the BMSSP constructor, so an empty `vertices`
|
|
11
|
+
// (the plain edge-list form) reproduces the pre-#172 "infer nodes from
|
|
12
|
+
// edges" behavior exactly.
|
|
13
|
+
//
|
|
14
|
+
// Node-ID semantics are unchanged from earlier versions: IDs are finite
|
|
15
|
+
// numbers. Value validation (finite IDs, finite non-negative weights) with
|
|
16
|
+
// the "Edge at index N" messages stays in the BMSSP constructor so there is
|
|
17
|
+
// a single source of truth; the pieces here validate only what they must to
|
|
18
|
+
// build a well-formed normalized shape (and the builder validates eagerly at
|
|
19
|
+
// the call site for a friendlier failure).
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A tiny mutable graph builder. Declare vertices (including isolated ones)
|
|
23
|
+
* and directed weighted edges, then hand the instance straight to
|
|
24
|
+
* `new BMSSP(graph)`.
|
|
25
|
+
*
|
|
26
|
+
* All mutators return `this`, so calls chain:
|
|
27
|
+
* `new Graph().addEdge(0, 1, 50).addEdge(1, 2, 75).addVertex(9)`.
|
|
28
|
+
*
|
|
29
|
+
* Node IDs must be finite numbers and weights finite and non-negative — the
|
|
30
|
+
* same contract the edge-array form has always enforced.
|
|
31
|
+
*/
|
|
32
|
+
class Graph {
|
|
33
|
+
constructor() {
|
|
34
|
+
// Declared vertex universe (numbers). addEdge folds in its endpoints too.
|
|
35
|
+
this._vertices = new Set();
|
|
36
|
+
// Declared directed edges as [from, to, weight] triples.
|
|
37
|
+
this._edges = [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Declare a vertex. Safe to call for a node that already exists (idempotent)
|
|
42
|
+
* and the only way to introduce an isolated vertex (one with no edges).
|
|
43
|
+
* @param {number} id finite node ID
|
|
44
|
+
* @returns {Graph} this
|
|
45
|
+
*/
|
|
46
|
+
addVertex(id) {
|
|
47
|
+
if (!Number.isFinite(id)) {
|
|
48
|
+
throw new Error("Graph.addVertex: node id must be a finite number");
|
|
49
|
+
}
|
|
50
|
+
this._vertices.add(id);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add a directed edge `from -> to` of the given weight. Both endpoints are
|
|
56
|
+
* declared automatically.
|
|
57
|
+
* @param {number} from finite source node ID
|
|
58
|
+
* @param {number} to finite target node ID
|
|
59
|
+
* @param {number} weight finite, non-negative edge weight
|
|
60
|
+
* @returns {Graph} this
|
|
61
|
+
*/
|
|
62
|
+
addEdge(from, to, weight) {
|
|
63
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) {
|
|
64
|
+
throw new Error("Graph.addEdge: node ids must be finite numbers");
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isFinite(weight) || weight < 0) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Graph.addEdge: weight must be a finite, non-negative number",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
this._vertices.add(from);
|
|
72
|
+
this._vertices.add(to);
|
|
73
|
+
this._edges.push([from, to, weight]);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {number} id @returns {boolean} whether the vertex is declared */
|
|
78
|
+
hasVertex(id) {
|
|
79
|
+
return this._vertices.has(id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @returns {number} number of declared vertices */
|
|
83
|
+
get vertexCount() {
|
|
84
|
+
return this._vertices.size;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @returns {number} number of declared edges */
|
|
88
|
+
get edgeCount() {
|
|
89
|
+
return this._edges.length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The canonical normalized form the BMSSP constructor consumes. Returns
|
|
94
|
+
* fresh copies so later mutation of this builder cannot affect a graph
|
|
95
|
+
* already constructed from it.
|
|
96
|
+
* @returns {{ edges: number[][], vertices: number[] }}
|
|
97
|
+
*/
|
|
98
|
+
toNormalized() {
|
|
99
|
+
return {
|
|
100
|
+
edges: this._edges.map((edge) => [...edge]),
|
|
101
|
+
vertices: [...this._vertices],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isIterable(value) {
|
|
107
|
+
return value != null && typeof value[Symbol.iterator] === "function";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reduce an adjacency form to `{ edges, vertices }`. Each entry maps a source
|
|
112
|
+
* node to an iterable of `[to, weight]` pairs; the source is always declared
|
|
113
|
+
* (so a node with an empty/absent neighbor list becomes an isolated vertex).
|
|
114
|
+
* When `coerceKeys` is set (plain-object form, whose keys are strings), the
|
|
115
|
+
* source key is converted to a number — finiteness is then checked by the
|
|
116
|
+
* BMSSP constructor's vertex validation.
|
|
117
|
+
*/
|
|
118
|
+
function adjacencyToNormalized(entries, coerceKeys) {
|
|
119
|
+
const edges = [];
|
|
120
|
+
const vertices = [];
|
|
121
|
+
for (const [rawFrom, neighbors] of entries) {
|
|
122
|
+
const from = coerceKeys ? Number(rawFrom) : rawFrom;
|
|
123
|
+
vertices.push(from);
|
|
124
|
+
if (neighbors == null) continue;
|
|
125
|
+
if (!isIterable(neighbors)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Adjacency list for node ${rawFrom} must be an iterable of [to, weight]`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
for (const pair of neighbors) {
|
|
131
|
+
if (!Array.isArray(pair) || pair.length !== 2) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Adjacency entry for node ${rawFrom} must be [to, weight]`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
edges.push([from, pair[0], pair[1]]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { edges, vertices };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Normalize any accepted graph input into `{ edges, vertices }`:
|
|
144
|
+
* - a `Graph` builder instance,
|
|
145
|
+
* - an edge array `[[from, to, weight], ...]` (vertices inferred from edges),
|
|
146
|
+
* - an adjacency `Map<from, Iterable<[to, weight]>>`, or
|
|
147
|
+
* - a plain adjacency object `{ from: [[to, weight], ...] }` (numeric-string
|
|
148
|
+
* keys, coerced to numbers).
|
|
149
|
+
*
|
|
150
|
+
* Only structural checks live here; per-edge value validation stays in the
|
|
151
|
+
* BMSSP constructor so its "Edge at index N" messages remain the single
|
|
152
|
+
* source of truth.
|
|
153
|
+
* @param {*} input
|
|
154
|
+
* @returns {{ edges: number[][], vertices: number[] }}
|
|
155
|
+
*/
|
|
156
|
+
function normalizeGraphInput(input) {
|
|
157
|
+
if (input instanceof Graph) {
|
|
158
|
+
return input.toNormalized();
|
|
159
|
+
}
|
|
160
|
+
if (Array.isArray(input)) {
|
|
161
|
+
return { edges: input, vertices: [] };
|
|
162
|
+
}
|
|
163
|
+
if (input instanceof Map) {
|
|
164
|
+
return adjacencyToNormalized(input.entries(), false);
|
|
165
|
+
}
|
|
166
|
+
if (input !== null && typeof input === "object") {
|
|
167
|
+
return adjacencyToNormalized(Object.entries(input), true);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(
|
|
170
|
+
"Input graph must be an edge array, an adjacency map/object, or a Graph instance",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { Graph, normalizeGraphInput };
|
package/src/tieBreak.mjs
CHANGED
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
* source is a hop-0 root) and "no predecessor" (which never loses a tie).
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
// Sentinel predecessor for sources: compares below every real
|
|
42
|
-
// source's own label never loses an equal-(length, hops) comparison.
|
|
43
|
-
|
|
41
|
+
// Sentinel predecessor for sources: compares below every real vertex index,
|
|
42
|
+
// so a source's own label never loses an equal-(length, hops) comparison.
|
|
43
|
+
// Since #205 the engine works on dense vertex indices (>= 0), so -1 is a
|
|
44
|
+
// valid sentinel AND fits the Int32Array holding the predecessors.
|
|
45
|
+
const NO_PRED = -1;
|
|
44
46
|
|
|
45
47
|
// Running count of compareKeys calls — the paper's cost metric ("comparisons
|
|
46
48
|
// between path lengths"). Every BMSSP-side comparison funnels through
|
|
@@ -110,7 +112,9 @@ function toBound(B) {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
/**
|
|
113
|
-
* Bundle (or create) the tie-break label maps that accompany d
|
|
115
|
+
* Bundle (or create) the tie-break label maps that accompany d̂ at the PUBLIC
|
|
116
|
+
* boundary (the BMSSP class mirrors the engine's arrays into these Maps,
|
|
117
|
+
* keyed by original node ids).
|
|
114
118
|
* @param {Map<*, number>} [hops] - Canonical path edge counts
|
|
115
119
|
* @param {Map<*, *>} [preds] - Canonical predecessor of each vertex
|
|
116
120
|
* @returns {{ hops: Map<*, number>, preds: Map<*, *> }}
|
|
@@ -119,6 +123,31 @@ function makeTies(hops = new Map(), preds = new Map()) {
|
|
|
119
123
|
return { hops, preds };
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Allocate the engine's label state for n vertices (#205): d̂, hops and
|
|
128
|
+
* canonical predecessors as typed arrays over dense vertex indices.
|
|
129
|
+
* Unlabeled vertices read as distance Infinity, hop 0, no predecessor —
|
|
130
|
+
* the same defaults the pre-#205 Maps gave via `?? Infinity` / `?? 0`.
|
|
131
|
+
* @param {number} n - Vertex count
|
|
132
|
+
* @returns {{ dist: Float64Array, hops: Uint32Array, preds: Int32Array }}
|
|
133
|
+
*/
|
|
134
|
+
function makeLabels(n) {
|
|
135
|
+
const dist = new Float64Array(n).fill(Infinity);
|
|
136
|
+
const hops = new Uint32Array(n);
|
|
137
|
+
const preds = new Int32Array(n).fill(NO_PRED);
|
|
138
|
+
return { dist, hops, preds };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The frontier-ordering key of vertex index v under the engine labels.
|
|
143
|
+
* @param {number} v - Dense vertex index
|
|
144
|
+
* @param {{ dist: Float64Array, hops: Uint32Array }} labels
|
|
145
|
+
* @returns {[number, number, number]} [dist[v], hops[v], v]
|
|
146
|
+
*/
|
|
147
|
+
function labelKey(v, labels) {
|
|
148
|
+
return [labels.dist[v], labels.hops[v], v];
|
|
149
|
+
}
|
|
150
|
+
|
|
122
151
|
/**
|
|
123
152
|
* The frontier-ordering key of a vertex under its current labels.
|
|
124
153
|
* @param {*} v - Vertex id
|
|
@@ -155,11 +184,16 @@ function orderKey(v, dHat, ties) {
|
|
|
155
184
|
* with orderKey(v, dHat, ties) — only on the rare paths that enqueue v,
|
|
156
185
|
* instead of on every attempt.
|
|
157
186
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
187
|
+
* Since #205 the labels live in typed arrays over dense vertex indices
|
|
188
|
+
* (see makeLabels); u and v are indices, and index order equals original-id
|
|
189
|
+
* order (the BMSSP class assigns indices by sorted id), so the canonical
|
|
190
|
+
* choices are identical to the pre-#205 Map engine's.
|
|
191
|
+
*
|
|
192
|
+
* @param {number} u - Edge tail index (its labels are read)
|
|
193
|
+
* @param {number} v - Edge head index (its labels may be updated)
|
|
160
194
|
* @param {number} weight - Edge weight, >= 0
|
|
161
|
-
* @param {
|
|
162
|
-
*
|
|
195
|
+
* @param {{ dist: Float64Array, hops: Uint32Array, preds: Int32Array }} labels
|
|
196
|
+
* - Engine label state, updated in place
|
|
163
197
|
* @param {[number, number, *]} [bound] - Optional gate: skip (no d̂ update)
|
|
164
198
|
* unless the resulting order key would be strictly below this bound
|
|
165
199
|
* @returns {number} RELAX_IMPROVED when the labels were updated,
|
|
@@ -167,34 +201,34 @@ function orderKey(v, dHat, ties) {
|
|
|
167
201
|
* (the re-enqueue signal), RELAX_LOST when the candidate loses or the
|
|
168
202
|
* bound gates it (labels untouched)
|
|
169
203
|
*/
|
|
170
|
-
function relaxEdge(u, v, weight,
|
|
171
|
-
const length =
|
|
172
|
-
const hopCount =
|
|
204
|
+
function relaxEdge(u, v, weight, labels, bound) {
|
|
205
|
+
const length = labels.dist[u] + weight;
|
|
206
|
+
const hopCount = labels.hops[u] + 1;
|
|
173
207
|
if (bound !== undefined && compareKeyParts(length, hopCount, v, bound) >= 0) {
|
|
174
208
|
return RELAX_LOST;
|
|
175
209
|
}
|
|
176
210
|
// Candidate path key [length, hopCount, u] vs. v's current path key
|
|
177
|
-
// [
|
|
178
|
-
//
|
|
211
|
+
// [dist[v], hops[v], preds[v]] — compareKeys inlined on the unpacked
|
|
212
|
+
// components (one counted comparison, no arrays)
|
|
179
213
|
comparisonCount += 1;
|
|
180
214
|
let cmp;
|
|
181
|
-
const currentLength =
|
|
215
|
+
const currentLength = labels.dist[v];
|
|
182
216
|
if (length !== currentLength) {
|
|
183
217
|
cmp = length < currentLength ? -1 : 1;
|
|
184
218
|
} else {
|
|
185
|
-
const currentHops =
|
|
219
|
+
const currentHops = labels.hops[v];
|
|
186
220
|
if (hopCount !== currentHops) {
|
|
187
221
|
cmp = hopCount < currentHops ? -1 : 1;
|
|
188
222
|
} else {
|
|
189
|
-
const currentPred =
|
|
223
|
+
const currentPred = labels.preds[v];
|
|
190
224
|
cmp = u !== currentPred ? (u < currentPred ? -1 : 1) : 0;
|
|
191
225
|
}
|
|
192
226
|
}
|
|
193
227
|
if (cmp > 0) return RELAX_LOST;
|
|
194
228
|
if (cmp === 0) return RELAX_EQUAL;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
229
|
+
labels.dist[v] = length;
|
|
230
|
+
labels.hops[v] = hopCount;
|
|
231
|
+
labels.preds[v] = u;
|
|
198
232
|
return RELAX_IMPROVED;
|
|
199
233
|
}
|
|
200
234
|
|
|
@@ -209,7 +243,9 @@ export {
|
|
|
209
243
|
compareKeyParts,
|
|
210
244
|
toBound,
|
|
211
245
|
makeTies,
|
|
246
|
+
makeLabels,
|
|
212
247
|
orderKey,
|
|
248
|
+
labelKey,
|
|
213
249
|
relaxEdge,
|
|
214
250
|
NO_PRED,
|
|
215
251
|
RELAX_LOST,
|