176 lines
3.7 KiB
JavaScript
176 lines
3.7 KiB
JavaScript
/**
|
|
* PriorityQueue - Min-heap priority queue for D* Lite
|
|
* Uses two-element key pairs [k1, k2] for ordering
|
|
*/
|
|
export class PriorityQueue {
|
|
constructor() {
|
|
this.heap = [];
|
|
this.keyMap = new Map(); // key string -> index in heap
|
|
}
|
|
|
|
get size() {
|
|
return this.heap.length;
|
|
}
|
|
|
|
isEmpty() {
|
|
return this.heap.length === 0;
|
|
}
|
|
|
|
/**
|
|
* Compare two key pairs
|
|
* Returns negative if a < b, positive if a > b, 0 if equal
|
|
*/
|
|
static compareKeys(a, b) {
|
|
if (a[0] !== b[0]) return a[0] - b[0];
|
|
return a[1] - b[1];
|
|
}
|
|
|
|
/**
|
|
* Get the minimum key without removing
|
|
*/
|
|
topKey() {
|
|
if (this.heap.length === 0) return [Infinity, Infinity];
|
|
return this.heap[0].priority;
|
|
}
|
|
|
|
/**
|
|
* Insert or update a node with a priority
|
|
*/
|
|
insertOrUpdate(node, priority) {
|
|
const key = node.key;
|
|
|
|
if (this.keyMap.has(key)) {
|
|
// Update existing entry
|
|
const index = this.keyMap.get(key);
|
|
const oldPriority = this.heap[index].priority;
|
|
this.heap[index].priority = priority;
|
|
this.heap[index].node = node;
|
|
|
|
// Determine whether to bubble up or sift down
|
|
if (PriorityQueue.compareKeys(priority, oldPriority) < 0) {
|
|
this._bubbleUp(index);
|
|
} else {
|
|
this._siftDown(index);
|
|
}
|
|
} else {
|
|
// Insert new entry
|
|
const entry = { node, priority };
|
|
this.heap.push(entry);
|
|
const index = this.heap.length - 1;
|
|
this.keyMap.set(key, index);
|
|
this._bubbleUp(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove and return the minimum node
|
|
*/
|
|
pop() {
|
|
if (this.heap.length === 0) return null;
|
|
|
|
const min = this.heap[0];
|
|
this.keyMap.delete(min.node.key);
|
|
|
|
const last = this.heap.pop();
|
|
if (this.heap.length > 0) {
|
|
this.heap[0] = last;
|
|
this.keyMap.set(last.node.key, 0);
|
|
this._siftDown(0);
|
|
}
|
|
|
|
return min.node;
|
|
}
|
|
|
|
/**
|
|
* Remove a specific node by key
|
|
*/
|
|
remove(node) {
|
|
const key = node.key;
|
|
if (!this.keyMap.has(key)) return;
|
|
|
|
const index = this.keyMap.get(key);
|
|
this.keyMap.delete(key);
|
|
|
|
const last = this.heap.pop();
|
|
if (index < this.heap.length) {
|
|
this.heap[index] = last;
|
|
this.keyMap.set(last.node.key, index);
|
|
this._bubbleUp(index);
|
|
this._siftDown(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a node is in the queue
|
|
*/
|
|
contains(node) {
|
|
return this.keyMap.has(node.key);
|
|
}
|
|
|
|
/**
|
|
* Clear the queue
|
|
*/
|
|
clear() {
|
|
this.heap = [];
|
|
this.keyMap.clear();
|
|
}
|
|
|
|
// --- Internal heap operations ---
|
|
|
|
_parent(i) {
|
|
return Math.floor((i - 1) / 2);
|
|
}
|
|
|
|
_leftChild(i) {
|
|
return 2 * i + 1;
|
|
}
|
|
|
|
_rightChild(i) {
|
|
return 2 * i + 2;
|
|
}
|
|
|
|
_swap(i, j) {
|
|
const temp = this.heap[i];
|
|
this.heap[i] = this.heap[j];
|
|
this.heap[j] = temp;
|
|
|
|
this.keyMap.set(this.heap[i].node.key, i);
|
|
this.keyMap.set(this.heap[j].node.key, j);
|
|
}
|
|
|
|
_bubbleUp(i) {
|
|
while (i > 0) {
|
|
const parent = this._parent(i);
|
|
if (PriorityQueue.compareKeys(this.heap[i].priority, this.heap[parent].priority) < 0) {
|
|
this._swap(i, parent);
|
|
i = parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_siftDown(i) {
|
|
const n = this.heap.length;
|
|
while (true) {
|
|
let smallest = i;
|
|
const left = this._leftChild(i);
|
|
const right = this._rightChild(i);
|
|
|
|
if (left < n && PriorityQueue.compareKeys(this.heap[left].priority, this.heap[smallest].priority) < 0) {
|
|
smallest = left;
|
|
}
|
|
if (right < n && PriorityQueue.compareKeys(this.heap[right].priority, this.heap[smallest].priority) < 0) {
|
|
smallest = right;
|
|
}
|
|
|
|
if (smallest !== i) {
|
|
this._swap(i, smallest);
|
|
i = smallest;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|