terra-route 0.0.6 → 0.0.7
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 +8 -6
- package/dist/distance/haversine.d.ts +1 -0
- package/dist/heap/min-heap.d.ts +9 -0
- package/dist/terra-route.cjs +1 -1
- package/dist/terra-route.cjs.map +1 -1
- package/dist/terra-route.d.ts +8 -3
- package/dist/terra-route.modern.js +1 -1
- package/dist/terra-route.modern.js.map +1 -1
- package/dist/terra-route.module.js +1 -1
- package/dist/terra-route.module.js.map +1 -1
- package/dist/terra-route.umd.js +1 -1
- package/dist/terra-route.umd.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +1 -1
- package/src/data/network.geojson +822 -0
- package/src/distance/haversine.ts +2 -1
- package/src/heap/fibonacci-heap.spec.ts +98 -0
- package/src/heap/fibonacci-heap.ts +210 -0
- package/src/heap/heap.d.ts +10 -0
- package/src/heap/min-heap.spec.ts +127 -0
- package/src/heap/min-heap.ts +90 -0
- package/src/heap/pairing-heap.spec.ts +101 -0
- package/src/heap/pairing-heap.ts +109 -0
- package/src/terra-route.compare.spec.ts +89 -0
- package/src/terra-route.spec.ts +500 -446
- package/src/terra-route.ts +49 -64
- package/src/test-utils/test-utils.ts +77 -3
- package/src/fibonacci-heap.spec.ts +0 -55
- package/src/fibonacci-heap.ts +0 -131
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Position } from "geojson";
|
|
2
2
|
|
|
3
|
+
/** Distance measured in kilometers */
|
|
3
4
|
export const haversineDistance = (pointOne: Position, pointTwo: Position): number => {
|
|
4
5
|
const toRadians = (latOrLng: number) => (latOrLng * Math.PI) / 180;
|
|
5
6
|
|
|
@@ -22,4 +23,4 @@ export const haversineDistance = (pointOne: Position, pointTwo: Position): numbe
|
|
|
22
23
|
const distance = radius * c;
|
|
23
24
|
|
|
24
25
|
return distance / 1000;
|
|
25
|
-
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { FibonacciHeap } from "./fibonacci-heap";
|
|
2
|
+
|
|
3
|
+
describe("FibonacciHeap", () => {
|
|
4
|
+
it("should report size zero for a new heap", () => {
|
|
5
|
+
const heap = new FibonacciHeap();
|
|
6
|
+
expect(heap.size()).toBe(0);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should insert a single element and extract it as the minimum", () => {
|
|
10
|
+
const heap = new FibonacciHeap();
|
|
11
|
+
heap.insert(42, 42);
|
|
12
|
+
expect(heap.size()).toBe(1);
|
|
13
|
+
expect(heap.extractMin()).toBe(42);
|
|
14
|
+
expect(heap.size()).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should always extract the smallest key first when inserting ascending keys", () => {
|
|
18
|
+
const heap = new FibonacciHeap();
|
|
19
|
+
for (let key = 1; key <= 10; key += 1) {
|
|
20
|
+
heap.insert(key, key);
|
|
21
|
+
}
|
|
22
|
+
expect(heap.extractMin()).toBe(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should extract elements in ascending order regardless of insertion order", () => {
|
|
26
|
+
const heap = new FibonacciHeap();
|
|
27
|
+
const keys = [5, 3, 8, 1, 7, 2, 4, 6];
|
|
28
|
+
keys.forEach((key) => heap.insert(key, key));
|
|
29
|
+
|
|
30
|
+
const extracted: number[] = [];
|
|
31
|
+
while (heap.size() > 0) {
|
|
32
|
+
const value = heap.extractMin();
|
|
33
|
+
// value is typed as number | null but should never be null inside the loop
|
|
34
|
+
extracted.push(value as number);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
expect(extracted).toEqual(keys.slice().sort((first, second) => first - second));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should handle duplicate keys correctly and still extract all values", () => {
|
|
41
|
+
const heap = new FibonacciHeap();
|
|
42
|
+
heap.insert(10, 100);
|
|
43
|
+
heap.insert(10, 200);
|
|
44
|
+
heap.insert(5, 50);
|
|
45
|
+
|
|
46
|
+
expect(heap.extractMin()).toBe(50); // key 5
|
|
47
|
+
// the two duplicate keys (10) can come out in any order for value
|
|
48
|
+
const remaining = [heap.extractMin(), heap.extractMin()].sort();
|
|
49
|
+
expect(remaining).toEqual([100, 200]);
|
|
50
|
+
expect(heap.extractMin()).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should return null when extracting from an empty heap", () => {
|
|
54
|
+
const heap = new FibonacciHeap();
|
|
55
|
+
expect(heap.extractMin()).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should update size correctly after mixed insert and extract operations", () => {
|
|
59
|
+
const heap = new FibonacciHeap();
|
|
60
|
+
heap.insert(20, 20);
|
|
61
|
+
heap.insert(5, 5);
|
|
62
|
+
heap.insert(15, 15);
|
|
63
|
+
expect(heap.size()).toBe(3);
|
|
64
|
+
|
|
65
|
+
heap.extractMin(); // removes value 5
|
|
66
|
+
expect(heap.size()).toBe(2);
|
|
67
|
+
|
|
68
|
+
heap.extractMin(); // removes value 15
|
|
69
|
+
heap.extractMin(); // removes value 20
|
|
70
|
+
expect(heap.size()).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should cope with a larger randomised workload", () => {
|
|
74
|
+
const heap = new FibonacciHeap();
|
|
75
|
+
const elementCount = 1000;
|
|
76
|
+
const insertedKeys: number[] = [];
|
|
77
|
+
|
|
78
|
+
for (let index = 0; index < elementCount; index += 1) {
|
|
79
|
+
// random key in range [0, 9999]
|
|
80
|
+
const randomKey = Math.floor(Math.random() * 10000);
|
|
81
|
+
insertedKeys.push(randomKey);
|
|
82
|
+
heap.insert(randomKey, randomKey);
|
|
83
|
+
}
|
|
84
|
+
insertedKeys.sort((first, second) => first - second);
|
|
85
|
+
|
|
86
|
+
const extracted: number[] = [];
|
|
87
|
+
let extractedValue: number | null;
|
|
88
|
+
do {
|
|
89
|
+
extractedValue = heap.extractMin();
|
|
90
|
+
if (extractedValue !== null) {
|
|
91
|
+
extracted.push(extractedValue);
|
|
92
|
+
}
|
|
93
|
+
} while (extractedValue !== null);
|
|
94
|
+
|
|
95
|
+
expect(extracted).toEqual(insertedKeys);
|
|
96
|
+
expect(heap.size()).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Heap } from "./heap";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal node representation for the Fibonacci heap, expressed as a plain
|
|
5
|
+
* JavaScript object (no dedicated class).
|
|
6
|
+
*/
|
|
7
|
+
interface FibonacciNode {
|
|
8
|
+
key: number;
|
|
9
|
+
value: number;
|
|
10
|
+
degree: number;
|
|
11
|
+
parent: FibonacciNode | null;
|
|
12
|
+
child: FibonacciNode | null;
|
|
13
|
+
next: FibonacciNode; // clockwise pointer in circular list
|
|
14
|
+
prev: FibonacciNode; // counter‑clockwise pointer in circular list
|
|
15
|
+
mark: boolean; // used by decrease‑key (not implemented here)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fibonacci heap that conforms to the Terra Route `Heap` interface.
|
|
20
|
+
* Only the heap itself is a class; each node is a lightweight object created
|
|
21
|
+
* inline where needed.
|
|
22
|
+
*/
|
|
23
|
+
export class FibonacciHeap implements Heap {
|
|
24
|
+
private minNode: FibonacciNode | null = null;
|
|
25
|
+
private totalNodes = 0;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Inserts a new `(key, value)` pair into the heap.
|
|
29
|
+
*/
|
|
30
|
+
public insert(key: number, value: number): void {
|
|
31
|
+
// ---------------- inline node creation ---------------- //
|
|
32
|
+
const node: FibonacciNode = {
|
|
33
|
+
key,
|
|
34
|
+
value,
|
|
35
|
+
degree: 0,
|
|
36
|
+
parent: null,
|
|
37
|
+
child: null,
|
|
38
|
+
next: null as unknown as FibonacciNode,
|
|
39
|
+
prev: null as unknown as FibonacciNode,
|
|
40
|
+
mark: false,
|
|
41
|
+
};
|
|
42
|
+
node.next = node;
|
|
43
|
+
node.prev = node;
|
|
44
|
+
// ------------------------------------------------------- //
|
|
45
|
+
|
|
46
|
+
if (this.minNode === null) {
|
|
47
|
+
this.minNode = node;
|
|
48
|
+
} else {
|
|
49
|
+
// splice node into the circular doubly‑linked root list right after `minNode`
|
|
50
|
+
node.prev = this.minNode;
|
|
51
|
+
node.next = this.minNode.next;
|
|
52
|
+
this.minNode.next.prev = node;
|
|
53
|
+
this.minNode.next = node;
|
|
54
|
+
|
|
55
|
+
if (node.key < this.minNode.key) {
|
|
56
|
+
this.minNode = node;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.totalNodes += 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Removes and returns the value associated with the minimum key. Returns
|
|
65
|
+
* `null` if the heap is empty.
|
|
66
|
+
*/
|
|
67
|
+
public extractMin(): number | null {
|
|
68
|
+
const oldMin = this.minNode;
|
|
69
|
+
if (oldMin === null) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Promote each child of `oldMin` to the root list
|
|
74
|
+
if (oldMin.child !== null) {
|
|
75
|
+
let childStart = oldMin.child;
|
|
76
|
+
const children: FibonacciNode[] = [];
|
|
77
|
+
do {
|
|
78
|
+
children.push(childStart);
|
|
79
|
+
childStart = childStart.next;
|
|
80
|
+
} while (childStart !== oldMin.child);
|
|
81
|
+
|
|
82
|
+
for (const child of children) {
|
|
83
|
+
// detach from child list
|
|
84
|
+
child.parent = null;
|
|
85
|
+
child.prev.next = child.next;
|
|
86
|
+
child.next.prev = child.prev;
|
|
87
|
+
|
|
88
|
+
// add to root list right after `oldMin`
|
|
89
|
+
child.prev = this.minNode!;
|
|
90
|
+
child.next = this.minNode!.next;
|
|
91
|
+
this.minNode!.next.prev = child;
|
|
92
|
+
this.minNode!.next = child;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Remove `oldMin` from root list
|
|
97
|
+
oldMin.prev.next = oldMin.next;
|
|
98
|
+
oldMin.next.prev = oldMin.prev;
|
|
99
|
+
|
|
100
|
+
if (oldMin === oldMin.next) {
|
|
101
|
+
// The heap had exactly one root node and is now empty
|
|
102
|
+
this.minNode = null;
|
|
103
|
+
} else {
|
|
104
|
+
this.minNode = oldMin.next;
|
|
105
|
+
this.consolidate();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.totalNodes -= 1;
|
|
109
|
+
return oldMin.value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Returns the number of elements stored in the heap. */
|
|
113
|
+
public size(): number {
|
|
114
|
+
return this.totalNodes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --------------------------- internal helpers --------------------------- //
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Merges trees of equal degree so that at most one root of each degree
|
|
121
|
+
* remains. Runs in `O(log n)` time and is called after `extractMin`.
|
|
122
|
+
*/
|
|
123
|
+
private consolidate(): void {
|
|
124
|
+
if (this.minNode === null) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// upper bound for the degree of any node is ⌊log₂ n⌋
|
|
129
|
+
const upperBound = Math.floor(Math.log2(this.totalNodes)) + 2;
|
|
130
|
+
const degreeTable: Array<FibonacciNode | null> = new Array(upperBound).fill(null);
|
|
131
|
+
|
|
132
|
+
// snapshot of the current root list prior to mutation
|
|
133
|
+
const rootList: FibonacciNode[] = [];
|
|
134
|
+
let currentRoot = this.minNode;
|
|
135
|
+
do {
|
|
136
|
+
rootList.push(currentRoot);
|
|
137
|
+
currentRoot = currentRoot.next;
|
|
138
|
+
} while (currentRoot !== this.minNode);
|
|
139
|
+
|
|
140
|
+
for (const root of rootList) {
|
|
141
|
+
let x = root;
|
|
142
|
+
let degree = x.degree;
|
|
143
|
+
|
|
144
|
+
while (degreeTable[degree] !== null) {
|
|
145
|
+
let y = degreeTable[degree] as FibonacciNode;
|
|
146
|
+
if (y.key < x.key) {
|
|
147
|
+
// ensure `x` has the smaller key
|
|
148
|
+
const temporary = x;
|
|
149
|
+
x = y;
|
|
150
|
+
y = temporary;
|
|
151
|
+
}
|
|
152
|
+
this.link(y, x);
|
|
153
|
+
degreeTable[degree] = null;
|
|
154
|
+
degree += 1;
|
|
155
|
+
}
|
|
156
|
+
degreeTable[degree] = x;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// rebuild root list and identify new minimum
|
|
160
|
+
this.minNode = null;
|
|
161
|
+
for (const node of degreeTable) {
|
|
162
|
+
if (node === null) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (this.minNode === null) {
|
|
167
|
+
this.minNode = node;
|
|
168
|
+
node.prev = node;
|
|
169
|
+
node.next = node;
|
|
170
|
+
} else {
|
|
171
|
+
// insert `node` right after current min
|
|
172
|
+
node.prev = this.minNode;
|
|
173
|
+
node.next = this.minNode.next;
|
|
174
|
+
this.minNode.next.prev = node;
|
|
175
|
+
this.minNode.next = node;
|
|
176
|
+
|
|
177
|
+
if (node.key < this.minNode.key) {
|
|
178
|
+
this.minNode = node;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Makes `child` a child of `parent`. Assumes both nodes are roots of equal
|
|
186
|
+
* degree. Runs in `O(1)` time.
|
|
187
|
+
*/
|
|
188
|
+
private link(child: FibonacciNode, parent: FibonacciNode): void {
|
|
189
|
+
// detach `child` from the root list
|
|
190
|
+
child.prev.next = child.next;
|
|
191
|
+
child.next.prev = child.prev;
|
|
192
|
+
|
|
193
|
+
// make `child` a child of `parent`
|
|
194
|
+
child.parent = parent;
|
|
195
|
+
child.mark = false;
|
|
196
|
+
|
|
197
|
+
if (parent.child === null) {
|
|
198
|
+
parent.child = child;
|
|
199
|
+
child.prev = child;
|
|
200
|
+
child.next = child;
|
|
201
|
+
} else {
|
|
202
|
+
child.prev = parent.child;
|
|
203
|
+
child.next = parent.child.next;
|
|
204
|
+
parent.child.next.prev = child;
|
|
205
|
+
parent.child.next = child;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
parent.degree += 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { MinHeap } from "./min-heap";
|
|
2
|
+
|
|
3
|
+
describe("MinHeap", () => {
|
|
4
|
+
let minHeap: MinHeap;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
minHeap = new MinHeap();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("inserts and extracts a single element", () => {
|
|
11
|
+
expect(minHeap.size()).toBe(0);
|
|
12
|
+
|
|
13
|
+
minHeap.insert(5, 123);
|
|
14
|
+
|
|
15
|
+
expect(minHeap.size()).toBe(1);
|
|
16
|
+
expect(minHeap.extractMin()).toBe(123);
|
|
17
|
+
expect(minHeap.size()).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("extracts null when the heap is empty", () => {
|
|
21
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
22
|
+
expect(minHeap.size()).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("maintains proper order for ascending key inserts", () => {
|
|
26
|
+
const keys = [1, 2, 3, 4, 5];
|
|
27
|
+
keys.forEach((key) => {
|
|
28
|
+
minHeap.insert(key, key + 100);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Expect the smallest key to be extracted first
|
|
32
|
+
expect(minHeap.extractMin()).toBe(101); // key=1 => value=101
|
|
33
|
+
expect(minHeap.extractMin()).toBe(102); // key=2 => value=102
|
|
34
|
+
expect(minHeap.extractMin()).toBe(103);
|
|
35
|
+
expect(minHeap.extractMin()).toBe(104);
|
|
36
|
+
expect(minHeap.extractMin()).toBe(105);
|
|
37
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("maintains proper order for descending key inserts", () => {
|
|
41
|
+
const keys = [5, 4, 3, 2, 1];
|
|
42
|
+
keys.forEach((key) => {
|
|
43
|
+
minHeap.insert(key, key + 200);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// The smallest key is 1
|
|
47
|
+
expect(minHeap.extractMin()).toBe(201);
|
|
48
|
+
// Next smallest is 2
|
|
49
|
+
expect(minHeap.extractMin()).toBe(202);
|
|
50
|
+
// Then 3, 4, 5
|
|
51
|
+
expect(minHeap.extractMin()).toBe(203);
|
|
52
|
+
expect(minHeap.extractMin()).toBe(204);
|
|
53
|
+
expect(minHeap.extractMin()).toBe(205);
|
|
54
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles duplicate keys by respecting insertion order", () => {
|
|
58
|
+
// Insert multiple items with the same key but different values
|
|
59
|
+
minHeap.insert(10, 1);
|
|
60
|
+
minHeap.insert(10, 2);
|
|
61
|
+
minHeap.insert(10, 3);
|
|
62
|
+
|
|
63
|
+
// The heap should prioritize the earliest inserted item first
|
|
64
|
+
expect(minHeap.extractMin()).toBe(1);
|
|
65
|
+
expect(minHeap.extractMin()).toBe(2);
|
|
66
|
+
expect(minHeap.extractMin()).toBe(3);
|
|
67
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("handles a mix of negative and positive keys", () => {
|
|
71
|
+
const items = [
|
|
72
|
+
{ key: -10, value: 1 },
|
|
73
|
+
{ key: 0, value: 2 },
|
|
74
|
+
{ key: 10, value: 3 },
|
|
75
|
+
{ key: -5, value: 4 },
|
|
76
|
+
{ key: 5, value: 5 },
|
|
77
|
+
];
|
|
78
|
+
items.forEach((item) => minHeap.insert(item.key, item.value));
|
|
79
|
+
|
|
80
|
+
// Order of extracted values should respect key order (lowest key first)
|
|
81
|
+
expect(minHeap.extractMin()).toBe(1); // key = -10
|
|
82
|
+
expect(minHeap.extractMin()).toBe(4); // key = -5
|
|
83
|
+
expect(minHeap.extractMin()).toBe(2); // key = 0
|
|
84
|
+
expect(minHeap.extractMin()).toBe(5); // key = 5
|
|
85
|
+
expect(minHeap.extractMin()).toBe(3); // key = 10
|
|
86
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("reflects correct size throughout inserts and extracts", () => {
|
|
90
|
+
expect(minHeap.size()).toBe(0);
|
|
91
|
+
|
|
92
|
+
minHeap.insert(2, 100);
|
|
93
|
+
minHeap.insert(1, 200);
|
|
94
|
+
minHeap.insert(3, 300);
|
|
95
|
+
|
|
96
|
+
expect(minHeap.size()).toBe(3);
|
|
97
|
+
|
|
98
|
+
minHeap.extractMin(); // remove key=1
|
|
99
|
+
expect(minHeap.size()).toBe(2);
|
|
100
|
+
|
|
101
|
+
minHeap.extractMin(); // remove key=2
|
|
102
|
+
expect(minHeap.size()).toBe(1);
|
|
103
|
+
|
|
104
|
+
minHeap.extractMin(); // remove key=3
|
|
105
|
+
expect(minHeap.size()).toBe(0);
|
|
106
|
+
|
|
107
|
+
// Now empty, should return null
|
|
108
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
109
|
+
expect(minHeap.size()).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("handles random insertion order consistently", () => {
|
|
113
|
+
const items = [5, 1, 4, 1, 3, 2, 8, 0, -1];
|
|
114
|
+
items.forEach((k) => {
|
|
115
|
+
minHeap.insert(k, k + 1000);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Sort items to check expected order
|
|
119
|
+
const sorted = [...items].sort((a, b) => a - b);
|
|
120
|
+
sorted.forEach((expectedKey) => {
|
|
121
|
+
const extractedValue = minHeap.extractMin();
|
|
122
|
+
const expectedValue = expectedKey + 1000;
|
|
123
|
+
expect(extractedValue).toBe(expectedValue);
|
|
124
|
+
});
|
|
125
|
+
expect(minHeap.extractMin()).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Heap } from "./heap";
|
|
2
|
+
|
|
3
|
+
export class MinHeap implements Heap {
|
|
4
|
+
private heap: Array<{ key: number; value: number; index: number }> = [];
|
|
5
|
+
private insertCounter = 0;
|
|
6
|
+
|
|
7
|
+
insert(key: number, value: number): void {
|
|
8
|
+
const node = { key, value, index: this.insertCounter++ };
|
|
9
|
+
let idx = this.heap.length;
|
|
10
|
+
this.heap.push(node);
|
|
11
|
+
|
|
12
|
+
// Optimized Bubble Up
|
|
13
|
+
while (idx > 0) {
|
|
14
|
+
const parentIdx = (idx - 1) >>> 1; // Fast Math.floor((idx - 1) / 2)
|
|
15
|
+
const parent = this.heap[parentIdx];
|
|
16
|
+
if (node.key > parent.key || (node.key === parent.key && node.index > parent.index)) break;
|
|
17
|
+
this.heap[idx] = parent;
|
|
18
|
+
idx = parentIdx;
|
|
19
|
+
}
|
|
20
|
+
this.heap[idx] = node;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extractMin(): number | null {
|
|
24
|
+
const length = this.heap.length;
|
|
25
|
+
if (length === 0) return null;
|
|
26
|
+
|
|
27
|
+
const minNode = this.heap[0];
|
|
28
|
+
const endNode = this.heap.pop()!;
|
|
29
|
+
|
|
30
|
+
if (length > 1) {
|
|
31
|
+
this.heap[0] = endNode;
|
|
32
|
+
this.bubbleDown(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return minNode.value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
size(): number {
|
|
39
|
+
return this.heap.length;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private bubbleDown(idx: number): void {
|
|
43
|
+
const { heap } = this;
|
|
44
|
+
const length = heap.length;
|
|
45
|
+
// Grab the parent node once, then move it down only if needed
|
|
46
|
+
const node = heap[idx];
|
|
47
|
+
const nodeKey = node.key;
|
|
48
|
+
const nodeIndex = node.index;
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line no-constant-condition
|
|
51
|
+
while (true) {
|
|
52
|
+
// Calculate left and right child indexes
|
|
53
|
+
const leftIdx = (idx << 1) + 1;
|
|
54
|
+
if (leftIdx >= length) {
|
|
55
|
+
// No children => we’re already in place
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Assume left child is the smaller one by default
|
|
60
|
+
let smallestIdx = leftIdx;
|
|
61
|
+
let smallestKey = heap[leftIdx].key;
|
|
62
|
+
let smallestIndex = heap[leftIdx].index;
|
|
63
|
+
|
|
64
|
+
const rightIdx = leftIdx + 1;
|
|
65
|
+
if (rightIdx < length) {
|
|
66
|
+
// Compare left child vs. right child
|
|
67
|
+
const rightKey = heap[rightIdx].key;
|
|
68
|
+
const rightIndex = heap[rightIdx].index;
|
|
69
|
+
if (rightKey < smallestKey || (rightKey === smallestKey && rightIndex < smallestIndex)) {
|
|
70
|
+
smallestIdx = rightIdx;
|
|
71
|
+
smallestKey = rightKey;
|
|
72
|
+
smallestIndex = rightIndex;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Compare the smaller child with the parent
|
|
77
|
+
if (smallestKey < nodeKey || (smallestKey === nodeKey && smallestIndex < nodeIndex)) {
|
|
78
|
+
// Swap the smaller child up
|
|
79
|
+
heap[idx] = heap[smallestIdx];
|
|
80
|
+
idx = smallestIdx;
|
|
81
|
+
} else {
|
|
82
|
+
// We’re in the correct position now, so stop
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Place the original node in its final position
|
|
88
|
+
heap[idx] = node;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { PairingHeap } from "./pairing-heap";
|
|
2
|
+
|
|
3
|
+
describe("PairingHeap", () => {
|
|
4
|
+
it("starts empty", () => {
|
|
5
|
+
const heap = new PairingHeap();
|
|
6
|
+
expect(heap.size()).toBe(0);
|
|
7
|
+
expect(heap.extractMin()).toBeNull();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("inserts elements and extracts them in ascending key order", () => {
|
|
11
|
+
const heap = new PairingHeap();
|
|
12
|
+
|
|
13
|
+
heap.insert(10, 10);
|
|
14
|
+
heap.insert(5, 5);
|
|
15
|
+
heap.insert(20, 20);
|
|
16
|
+
|
|
17
|
+
expect(heap.size()).toBe(3);
|
|
18
|
+
|
|
19
|
+
expect(heap.extractMin()).toBe(5);
|
|
20
|
+
expect(heap.extractMin()).toBe(10);
|
|
21
|
+
expect(heap.extractMin()).toBe(20);
|
|
22
|
+
|
|
23
|
+
expect(heap.size()).toBe(0);
|
|
24
|
+
expect(heap.extractMin()).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("handles duplicate keys correctly", () => {
|
|
28
|
+
const heap = new PairingHeap();
|
|
29
|
+
|
|
30
|
+
heap.insert(7, 70);
|
|
31
|
+
heap.insert(7, 71);
|
|
32
|
+
heap.insert(3, 30);
|
|
33
|
+
|
|
34
|
+
// The two smallest keys are equal (3 and 7). The order of equal‑key
|
|
35
|
+
// removals is not defined, but each extracted value must correspond
|
|
36
|
+
// to the minimum remaining key.
|
|
37
|
+
const first = heap.extractMin();
|
|
38
|
+
expect(first).toBe(30);
|
|
39
|
+
|
|
40
|
+
const second = heap.extractMin();
|
|
41
|
+
const third = heap.extractMin();
|
|
42
|
+
const extractedValues = [second, third].sort((a, b) => (a as number) - (b as number));
|
|
43
|
+
|
|
44
|
+
expect(extractedValues).toEqual([70, 71]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("works with negative keys", () => {
|
|
48
|
+
const heap = new PairingHeap();
|
|
49
|
+
|
|
50
|
+
heap.insert(-2, -2);
|
|
51
|
+
heap.insert(-10, -10);
|
|
52
|
+
heap.insert(0, 0);
|
|
53
|
+
|
|
54
|
+
expect(heap.extractMin()).toBe(-10);
|
|
55
|
+
expect(heap.extractMin()).toBe(-2);
|
|
56
|
+
expect(heap.extractMin()).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("maintains size correctly through mixed operations", () => {
|
|
60
|
+
const heap = new PairingHeap();
|
|
61
|
+
|
|
62
|
+
heap.insert(4, 4);
|
|
63
|
+
heap.insert(1, 1);
|
|
64
|
+
heap.insert(3, 3);
|
|
65
|
+
expect(heap.size()).toBe(3);
|
|
66
|
+
|
|
67
|
+
heap.extractMin(); // removes 1
|
|
68
|
+
expect(heap.size()).toBe(2);
|
|
69
|
+
|
|
70
|
+
heap.insert(2, 2);
|
|
71
|
+
expect(heap.size()).toBe(3);
|
|
72
|
+
|
|
73
|
+
heap.extractMin(); // removes 2
|
|
74
|
+
heap.extractMin(); // removes 3
|
|
75
|
+
heap.extractMin(); // removes 4
|
|
76
|
+
expect(heap.size()).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("can handle a large random workload and still produce sorted output", () => {
|
|
80
|
+
const heap = new PairingHeap();
|
|
81
|
+
|
|
82
|
+
const randomValues: number[] = [];
|
|
83
|
+
const elementCount = 10_000;
|
|
84
|
+
|
|
85
|
+
for (let index = 0; index < elementCount; index += 1) {
|
|
86
|
+
const key = Math.floor(Math.random() * 1_000_000) - 500_000;
|
|
87
|
+
randomValues.push(key);
|
|
88
|
+
heap.insert(key, key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
randomValues.sort((a, b) => a - b);
|
|
92
|
+
|
|
93
|
+
for (let index = 0; index < elementCount; index += 1) {
|
|
94
|
+
const extracted = heap.extractMin();
|
|
95
|
+
expect(extracted).toBe(randomValues[index]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
expect(heap.size()).toBe(0);
|
|
99
|
+
expect(heap.extractMin()).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|