Skip to content

Commit

Permalink
Implement heap increasePriority and let Dijkstra use it
Browse files Browse the repository at this point in the history
  • Loading branch information
caojoshua committed Jul 2, 2023
1 parent 227cffb commit c070c72
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 32 deletions.
84 changes: 64 additions & 20 deletions data_structures/heap/heap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@
*/

export abstract class Heap<T> {
private heap: T[];
protected heap: T[];
// A comparison function. Returns true if a should be the parent of b.
private compare: (a: any, b: any) => boolean;
private compare: (a: T, b: T) => boolean;

constructor(elements: T[] = [], compare: (a: T, b: T) => boolean) {
constructor(compare: (a: T, b: T) => boolean) {
this.heap = [];
this.compare = compare;
for (let element of elements) {
this.insert(element);
}
}

/**
Expand Down Expand Up @@ -68,17 +65,20 @@ export abstract class Heap<T> {
return this.size() === 0;
}

private bubbleUp(): void {
let index = this.size() - 1;
protected swap(a: number, b: number) {
[this.heap[a], this.heap[b]] = [
this.heap[b],
this.heap[a],
];
}

protected bubbleUp(index = this.size() - 1): void {
let parentIndex;

while (index > 0) {
parentIndex = Math.floor((index - 1) / 2);
if (this.isRightlyPlaced(index, parentIndex)) break;
[this.heap[parentIndex], this.heap[index]] = [
this.heap[index],
this.heap[parentIndex],
];
this.swap(parentIndex, index);
index = parentIndex;
}
}
Expand All @@ -95,10 +95,7 @@ export abstract class Heap<T> {
rightChildIndex
);
if (this.isRightlyPlaced(childIndexToSwap, index)) break;
[this.heap[childIndexToSwap], this.heap[index]] = [
this.heap[index],
this.heap[childIndexToSwap],
];
this.swap(childIndexToSwap, index);
index = childIndexToSwap;
leftChildIndex = this.getLeftChildIndex(index);
rightChildIndex = this.getRightChildIndex(index);
Expand Down Expand Up @@ -140,13 +137,60 @@ export abstract class Heap<T> {
}

export class MinHeap<T> extends Heap<T> {
constructor(elements: T[] = [], compare = (a: T, b: T) => { return a < b }) {
super(elements, compare);
constructor(compare = (a: T, b: T) => { return a < b }) {
super(compare);
}
}

export class MaxHeap<T> extends Heap<T> {
constructor(elements: T[] = [], compare = (a: T, b: T) => { return a > b }) {
super(elements, compare);
constructor(compare = (a: T, b: T) => { return a > b }) {
super(compare);
}
}

// Priority queue that supports increasePriority() in O(log(n)). The limitation is that there can only be a single element for each key, and the max number or keys must be specified at heap construction. Most of the functions are wrappers around MinHeap functions and update the keys array.
export class PriorityQueue<T> extends MinHeap<T> {
// Maps from the n'th node to its index within the heap.
private keys: number[];
// Maps from element to its index with keys.
private keys_index: (a: T) => number;

constructor(keys_index: (a: T) => number, num_keys: number, compare = (a: T, b: T) => { return a < b }) {
super(compare);
this.keys = Array(num_keys).fill(-1);
this.keys_index = keys_index;
}

protected swap(a: number, b: number) {
let akey = this.keys_index(this.heap[a]);
let bkey = this.keys_index(this.heap[b]);
[this.keys[akey], this.keys[bkey]] = [this.keys[bkey], this.keys[akey]];
super.swap(a, b);
}

public insert(value: T) {
this.keys[this.keys_index(value)] = this.size();
super.insert(value);
}

public extract(): T {
// Unmark the the highest priority element and set key to zero for the last element in the heap.
this.keys[this.keys_index(this.heap[0])] = -1;
if (this.size() > 1) {
this.keys[this.keys_index(this.heap[this.size() - 1])] = 0;
}
return super.extract();
}

public increasePriority(idx: number, value: T) {
if (this.keys[idx] == -1) {
// If the key does not exist, insert the value.
this.insert(value);
return;
}
let key = this.keys[idx];
// Increase the priority and bubble it up the heap.
this.heap[key] = value;
this.bubbleUp(key);
}
}
5 changes: 4 additions & 1 deletion data_structures/heap/test/max_heap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ describe("MaxHeap", () => {
];

beforeEach(() => {
heap = new MaxHeap(elements);
heap = new MaxHeap();
for (let element of elements) {
heap.insert(element);
}
});

it("should initialize a heap from input array", () => {
Expand Down
34 changes: 29 additions & 5 deletions data_structures/heap/test/min_heap.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MinHeap } from "../heap";
import { MinHeap, PriorityQueue } from "../heap";

describe("MinHeap", () => {
let heap: MinHeap<number>;
Expand All @@ -7,7 +7,10 @@ describe("MinHeap", () => {
];

beforeEach(() => {
heap = new MinHeap(elements);
heap = new MinHeap();
for (let element of elements) {
heap.insert(element);
}
});

it("should initialize a heap from input array", () => {
Expand All @@ -27,7 +30,7 @@ describe("MinHeap", () => {
heap.check();
});

const extract_all = (heap: MinHeap<number>) => {
const extract_all = (heap: MinHeap<number>, elements: number[]) => {
[...elements].sort((a, b) => a - b).forEach((element: number) => {
expect(heap.extract()).toEqual(element);
});
Expand All @@ -36,7 +39,7 @@ describe("MinHeap", () => {
}

it("should remove and return the min elements in order", () => {
extract_all(heap);
extract_all(heap, elements);
});

it("should insert all, then remove and return the min elements in order", () => {
Expand All @@ -46,6 +49,27 @@ describe("MinHeap", () => {
});
heap.check();
expect(heap.size()).toEqual(elements.length);
extract_all(heap);
extract_all(heap, elements);
});

it("should increase priority", () => {
let heap = new PriorityQueue((a: number) => { return a; }, elements.length);
elements.forEach((element: number) => {
heap.insert(element);
});
heap.check();
expect(heap.size()).toEqual(elements.length);

heap.increasePriority(55, 14);
heap.increasePriority(18, 16);
heap.increasePriority(81, 72);
heap.increasePriority(9, 0);
heap.increasePriority(43, 33);
heap.check();
// Elements after increasing priority
const newElements: number[] = [
12, 4, 33, 42, 0, 7, 39, 16, 14, 1, 51, 34, 72, 16,
];
extract_all(heap, newElements);
});
});
14 changes: 8 additions & 6 deletions graph/dijkstra.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MinHeap } from '../data_structures/heap/min_heap';
import { MinHeap, PriorityQueue } from '../data_structures/heap/heap';
/**
* @function dijkstra
* @description Compute the shortest path from a source node to all other nodes. The input graph is in adjacency list form. It is a multidimensional array of edges. graph[i] holds the edges for the i'th node. Each edge is a 2-tuple where the 0'th item is the destination node, and the 1'th item is the edge weight.
Expand All @@ -11,22 +11,24 @@ import { MinHeap } from '../data_structures/heap/min_heap';
* @see https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
*/
export const dijkstra = (graph: [number, number][][], start: number): number[] => {
// We use a priority queue to make sure we always visit the closest node. By using a MinHeap,
// finding the next node is O(log(V))
let priorityQueue = new MinHeap([start]);
// We use a priority queue to make sure we always visit the closest node. The
// queue makes comparisons based on path weights.
let priorityQueue = new PriorityQueue((a: [number, number]) => { return a[0] }, graph.length, (a: [number, number], b: [number, number]) => { return a[1] < b[1] });
priorityQueue.insert([start, 0]);
// We save the shortest distance to each node in `distances`. If a node is
// unreachable from the start node, its distance is Infinity.
let distances = Array(graph.length).fill(Infinity);
distances[start] = 0;

while (priorityQueue.size() > 0) {
const node = priorityQueue.extract();
const [node, _] = priorityQueue.extract();
graph[node].forEach(([child, weight]) => {
let new_distance = distances[node] + weight;
if (new_distance < distances[child]) {
// Found a new shortest path to child node. Record its distance and add child to the queue.
// If the child already exists in the queue, the priority will be updated. This will make sure the queue will be at most size V (number of vertices).
priorityQueue.increasePriority(child, [child, weight]);
distances[child] = new_distance;
priorityQueue.insert(child);
}
});
}
Expand Down

0 comments on commit c070c72

Please sign in to comment.