-
Notifications
You must be signed in to change notification settings - Fork 927
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(heap): add min/max/median-heaps
- Loading branch information
1 parent
7279e5c
commit 202ca9f
Showing
9 changed files
with
372 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
const Heap = require('./heap'); | ||
const PriorityQueue = require('./priority-queue'); | ||
const MaxHeap = require('./max-heap'); | ||
const MinHeap = require('./min-heap'); | ||
|
||
[[Heap], [PriorityQueue], [MinHeap]].forEach(([DS, arg]) => { | ||
describe('Min-Heap (Priority Queue)', () => { | ||
let heap; | ||
|
||
beforeEach(() => { | ||
heap = new DS(arg); | ||
}); | ||
|
||
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]); | ||
}); | ||
|
||
it('should return null if empty', () => { | ||
heap = new Heap(); | ||
expect(heap.remove()).toBe(null); | ||
}); | ||
}); | ||
|
||
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); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
[[Heap, (a, b) => b - a], [PriorityQueue, (a, b) => b - a], [MaxHeap]].forEach(([DS, arg]) => { | ||
describe('Max-Heap (Priority Queue)', () => { | ||
let heap; | ||
|
||
beforeEach(() => { | ||
heap = new DS(arg); | ||
}); | ||
|
||
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); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const Heap = require('./heap'); | ||
|
||
class MaxHeap extends Heap { | ||
constructor() { | ||
super((a, b) => b - a); | ||
} | ||
} | ||
module.exports = MaxHeap; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
const Heap = require('./heap'); | ||
|
||
/** | ||
* Median Heap using one MaxHeap and one MinHeap. | ||
* | ||
* Each heap contains about one half of the data. | ||
* Every element in the min-heap is greater or equal to the median, | ||
* and every element in the max-heap is less or equal to the median. | ||
* | ||
* @author Adrian Mejia <adrian@adrianmejia.com> | ||
*/ | ||
class MedianHeap { | ||
constructor() { | ||
this.min = new Heap((a, b) => a - b); | ||
this.max = new Heap((a, b) => b - a); | ||
} | ||
|
||
/** | ||
* Add a value to the heap | ||
* @runtime O(log n) | ||
* @param {any} value | ||
*/ | ||
add(value) { | ||
if (value > this.findMedian()) { | ||
// If the new element is greater than the current median, it goes to the min-heap. | ||
this.min.add(value); | ||
} else { | ||
// If it is less than the current median, it goes to the max heap. | ||
this.max.add(value); | ||
} | ||
|
||
// rebalance if the sizes of the heaps differ by more than one element | ||
if (Math.abs(this.min.size() - this.max.size()) > 1) { | ||
// extract the min/max from the heap with more elements and insert it into the other heap. | ||
if (this.min.size() > this.max.size()) { | ||
this.max.add(this.min.remove()); | ||
} else { | ||
this.min.add(this.max.remove()); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Find median | ||
* @runtime O(1) | ||
*/ | ||
findMedian() { | ||
let median; | ||
|
||
if (this.max.size() === this.min.size()) { | ||
// When both heaps contain the same number of elements, | ||
// the total number of elements is even. | ||
// The median is the mean of the two middle elements. | ||
median = (this.max.peek() + this.min.peek()) / 2; | ||
} else if (this.max.size() > this.min.size()) { | ||
// when the max-heap contains one more element than the min-heap, | ||
// the median is in the top of the max-heap. | ||
median = this.max.peek(); | ||
} else { | ||
// When the min-heap contains one more element than the max-heap, | ||
// the median is in the top of the min-heap. | ||
median = this.min.peek(); | ||
} | ||
return median; | ||
} | ||
|
||
/** | ||
* Return size of the heap. | ||
*/ | ||
size() { | ||
return this.min.size() + this.max.size(); | ||
} | ||
} | ||
|
||
module.exports = MedianHeap; |
Oops, something went wrong.