From 17294077af23115c064595518519c0e520dd8196 Mon Sep 17 00:00:00 2001 From: Adrian Mejia Date: Fri, 8 May 2020 15:13:34 -0400 Subject: [PATCH] :sparkles: feat (heap): Add Heap implementation (PriorityQueue) --- src/data-structures/trees/heap.js | 55 +++++++++ src/data-structures/trees/heap.spec.js | 152 +++++++++++++++++++++++++ src/index.js | 2 + 3 files changed, 209 insertions(+) create mode 100644 src/data-structures/trees/heap.js create mode 100644 src/data-structures/trees/heap.spec.js diff --git a/src/data-structures/trees/heap.js b/src/data-structures/trees/heap.js new file mode 100644 index 00000000..09032737 --- /dev/null +++ b/src/data-structures/trees/heap.js @@ -0,0 +1,55 @@ +class Heap { + constructor(comparator = (a, b) => a - b) { + this.array = []; + this.comparator = (i1, i2) => comparator(this.array[i1], this.array[i2]); + } + + add(value) { + this.array.push(value); + this.bubbleUp(); + } + + peek() { + return this.array[0]; + } + + remove() { + this.swap(0, this.size() - 1); + const value = this.array.pop(); + this.bubbleDown(); + return value; + } + + size() { + return this.array.length; + } + + bubbleUp() { + let index = this.size() - 1; + const parent = (i) => Math.ceil(i / 2 - 1); + while (parent(index) >= 0 && this.comparator(parent(index), index) > 0) { + this.swap(parent(index), index); + index = parent(index); + } + } + + bubbleDown() { + let index = 0; + const left = (i) => 2 * i + 1; + const right = (i) => 2 * i + 2; + const getTopChild = (i) => (right(i) < this.size() + && this.comparator(left(i), right(i)) > 0 ? right(i) : left(i)); + + while (left(index) < this.size() && this.comparator(index, getTopChild(index)) > 0) { + const next = getTopChild(index); + this.swap(index, next); + index = next; + } + } + + swap(i1, i2) { + [this.array[i1], this.array[i2]] = [this.array[i2], this.array[i1]]; + } +} + +module.exports = Heap; diff --git a/src/data-structures/trees/heap.spec.js b/src/data-structures/trees/heap.spec.js new file mode 100644 index 00000000..b50924d7 --- /dev/null +++ b/src/data-structures/trees/heap.spec.js @@ -0,0 +1,152 @@ +const Heap = require('./heap'); + +describe('Min-Heap (Priority Queue)', () => { + let heap; + + beforeEach(() => { + heap = new Heap(); + }); + + describe('#contructor', () => { + it('should initialize', () => { + expect(heap).not.toBe(undefined); + }); + }); + + describe('#add', () => { + it('should add an element', () => { + expect(heap.add(1)).toBe(undefined); + expect(heap.array).toEqual([1]); + expect(heap.size()).toBe(1); + }); + + it('should keep things in order', () => { + heap.add(3); + expect(heap.array[0]).toEqual(3); + heap.add(2); + expect(heap.array[0]).toEqual(2); + heap.add(1); + expect(heap.array[0]).toEqual(1); + expect(heap.size()).toEqual(3); + }); + }); + + describe('#remove', () => { + it('should work', () => { + heap.add(1); + heap.add(0); + expect(heap.remove()).toBe(0); + expect(heap.size()).toBe(1); + expect(heap.array).toEqual([1]); + }); + }); + + describe('when has elements', () => { + beforeEach(() => { + heap.add(1); + heap.add(2); + heap.add(3); + heap.add(0); + }); + + describe('#peek', () => { + it('should get min', () => { + expect(heap.peek()).toEqual(0); + }); + }); + + describe('#remove', () => { + it('should get min', () => { + expect(heap.remove()).toEqual(0); + expect(heap.remove()).toEqual(1); + expect(heap.remove()).toEqual(2); + expect(heap.remove()).toEqual(3); + expect(heap.size()).toBe(0); + }); + }); + }); +}); + +describe('Max-Heap (Priority Queue)', () => { + let heap; + + beforeEach(() => { + heap = new Heap((a, b) => b - a); + }); + + describe('#contructor', () => { + it('should initialize', () => { + expect(heap).not.toBe(undefined); + }); + }); + + describe('#add', () => { + it('should add an element', () => { + expect(heap.add(1)).toBe(undefined); + expect(heap.array).toEqual([1]); + expect(heap.size()).toBe(1); + }); + + it('should keep things in order', () => { + heap.add(1); + expect(heap.array[0]).toEqual(1); + heap.add(2); + expect(heap.array[0]).toEqual(2); + heap.add(3); + expect(heap.array[0]).toEqual(3); + expect(heap.size()).toEqual(3); + }); + }); + + describe('#remove', () => { + it('should work', () => { + heap.add(1); + heap.add(0); + expect(heap.remove()).toBe(1); + expect(heap.size()).toBe(1); + expect(heap.array).toEqual([0]); + }); + + it('should work with duplicates', () => { + heap.add(3); + heap.add(2); + heap.add(3); + heap.add(1); + heap.add(2); + heap.add(4); + heap.add(5); + heap.add(5); + heap.add(6); + + expect(heap.remove()).toEqual(6); + expect(heap.remove()).toEqual(5); + expect(heap.remove()).toEqual(5); + expect(heap.remove()).toEqual(4); + }); + }); + + describe('when has elements', () => { + beforeEach(() => { + heap.add(1); + heap.add(2); + heap.add(3); + heap.add(0); + }); + + describe('#peek', () => { + it('should get min', () => { + expect(heap.peek()).toEqual(3); + }); + }); + + describe('#remove', () => { + it('should get min when duplicates', () => { + expect(heap.remove()).toEqual(3); + expect(heap.remove()).toEqual(2); + expect(heap.remove()).toEqual(1); + expect(heap.remove()).toEqual(0); + expect(heap.size()).toBe(0); + }); + }); + }); +}); diff --git a/src/index.js b/src/index.js index 2c29579d..72cf1f06 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ const AvlTree = require('./data-structures/trees/avl-tree'); const RedBlackTree = require('./data-structures/trees/red-black-tree'); const LRUCache = require('./data-structures/custom/lru-cache'); const Trie = require('./data-structures/trees/trie'); +const Heap = require('./data-structures/trees/heap'); // algorithms const bubbleSort = require('./algorithms/sorting/bubble-sort'); @@ -40,6 +41,7 @@ module.exports = { RedBlackTree, LRUCache, Trie, + Heap, bubbleSort, insertionSort, selectionSort,