/** * A binary heap implementation for efficient priority queue operations * Supports O(log n) insert and extract operations */ export class BinaryHeap { private heap: T[] = []; private keyMap?: Map; // For efficient key-based lookups constructor( private compareFn: (a: T, b: T) => number, private extractKey?: (item: T) => string ) { if (extractKey) { this.keyMap = new Map(); } } /** * Get the current size of the heap */ public get size(): number { return this.heap.length; } /** * Check if the heap is empty */ public isEmpty(): boolean { return this.heap.length === 0; } /** * Peek at the top element without removing it */ public peek(): T | undefined { return this.heap[0]; } /** * Insert a new item into the heap * O(log n) time complexity */ public insert(item: T): void { const index = this.heap.length; this.heap.push(item); if (this.keyMap && this.extractKey) { const key = this.extractKey(item); this.keyMap.set(key, index); } this.bubbleUp(index); } /** * Extract the top element from the heap * O(log n) time complexity */ public extract(): T | undefined { if (this.heap.length === 0) return undefined; if (this.heap.length === 1) { const item = this.heap.pop()!; if (this.keyMap && this.extractKey) { this.keyMap.delete(this.extractKey(item)); } return item; } const result = this.heap[0]; const lastItem = this.heap.pop()!; this.heap[0] = lastItem; if (this.keyMap && this.extractKey) { this.keyMap.delete(this.extractKey(result)); this.keyMap.set(this.extractKey(lastItem), 0); } this.bubbleDown(0); return result; } /** * Extract an element that matches the predicate * O(n) time complexity for search, O(log n) for extraction */ public extractIf(predicate: (item: T) => boolean): T | undefined { const index = this.heap.findIndex(predicate); if (index === -1) return undefined; return this.extractAt(index); } /** * Extract an element by its key (if extractKey was provided) * O(log n) time complexity */ public extractByKey(key: string): T | undefined { if (!this.keyMap || !this.extractKey) { throw new Error('extractKey function must be provided to use key-based extraction'); } const index = this.keyMap.get(key); if (index === undefined) return undefined; return this.extractAt(index); } /** * Check if a key exists in the heap * O(1) time complexity */ public hasKey(key: string): boolean { if (!this.keyMap) return false; return this.keyMap.has(key); } /** * Get all elements as an array (does not modify heap) * O(n) time complexity */ public toArray(): T[] { return [...this.heap]; } /** * Clear the heap */ public clear(): void { this.heap = []; if (this.keyMap) { this.keyMap.clear(); } } /** * Extract element at specific index */ private extractAt(index: number): T { const item = this.heap[index]; if (this.keyMap && this.extractKey) { this.keyMap.delete(this.extractKey(item)); } if (index === this.heap.length - 1) { this.heap.pop(); return item; } const lastItem = this.heap.pop()!; this.heap[index] = lastItem; if (this.keyMap && this.extractKey) { this.keyMap.set(this.extractKey(lastItem), index); } // Try bubbling up first const parentIndex = Math.floor((index - 1) / 2); if (parentIndex >= 0 && this.compareFn(this.heap[index], this.heap[parentIndex]) < 0) { this.bubbleUp(index); } else { this.bubbleDown(index); } return item; } /** * Bubble up element at given index to maintain heap property */ private bubbleUp(index: number): void { while (index > 0) { const parentIndex = Math.floor((index - 1) / 2); if (this.compareFn(this.heap[index], this.heap[parentIndex]) >= 0) { break; } this.swap(index, parentIndex); index = parentIndex; } } /** * Bubble down element at given index to maintain heap property */ private bubbleDown(index: number): void { const length = this.heap.length; while (true) { const leftChild = 2 * index + 1; const rightChild = 2 * index + 2; let smallest = index; if (leftChild < length && this.compareFn(this.heap[leftChild], this.heap[smallest]) < 0) { smallest = leftChild; } if (rightChild < length && this.compareFn(this.heap[rightChild], this.heap[smallest]) < 0) { smallest = rightChild; } if (smallest === index) break; this.swap(index, smallest); index = smallest; } } /** * Swap two elements in the heap */ private swap(i: number, j: number): void { const temp = this.heap[i]; this.heap[i] = this.heap[j]; this.heap[j] = temp; if (this.keyMap && this.extractKey) { this.keyMap.set(this.extractKey(this.heap[i]), i); this.keyMap.set(this.extractKey(this.heap[j]), j); } } }