Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 10e633f

Browse files
kersovtrekhleb
authored andcommittedAug 16, 2018
Add MaxHeap (trekhleb#167)
* Add MaxHeap * Add parent class for MinHeap and MaxHeap
1 parent a191ade commit 10e633f

File tree

4 files changed

+460
-167
lines changed

4 files changed

+460
-167
lines changed
 

‎src/data-structures/heap/Heap.js

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import Comparator from '../../utils/comparator/Comparator';
2+
3+
/**
4+
* Parent class for heaps
5+
* @class
6+
*/
7+
class Heap {
8+
/**
9+
* @constructs Heap
10+
* @param {Function} [comparatorFunction]
11+
*/
12+
constructor(comparatorFunction) {
13+
// Array representation of the heap.
14+
this.heapContainer = [];
15+
this.compare = new Comparator(comparatorFunction);
16+
}
17+
18+
/**
19+
* @param {number} parentIndex
20+
* @return {number}
21+
*/
22+
getLeftChildIndex(parentIndex) {
23+
return (2 * parentIndex) + 1;
24+
}
25+
26+
/**
27+
* @param {number} parentIndex
28+
* @return {number}
29+
*/
30+
getRightChildIndex(parentIndex) {
31+
return (2 * parentIndex) + 2;
32+
}
33+
34+
/**
35+
* @param {number} childIndex
36+
* @return {number}
37+
*/
38+
getParentIndex(childIndex) {
39+
return Math.floor((childIndex - 1) / 2);
40+
}
41+
42+
/**
43+
* @param {number} childIndex
44+
* @return {boolean}
45+
*/
46+
hasParent(childIndex) {
47+
return this.getParentIndex(childIndex) >= 0;
48+
}
49+
50+
/**
51+
* @param {number} parentIndex
52+
* @return {boolean}
53+
*/
54+
hasLeftChild(parentIndex) {
55+
return this.getLeftChildIndex(parentIndex) < this.heapContainer.length;
56+
}
57+
58+
/**
59+
* @param {number} parentIndex
60+
* @return {boolean}
61+
*/
62+
hasRightChild(parentIndex) {
63+
return this.getRightChildIndex(parentIndex) < this.heapContainer.length;
64+
}
65+
66+
/**
67+
* @param {number} parentIndex
68+
* @return {*}
69+
*/
70+
leftChild(parentIndex) {
71+
return this.heapContainer[this.getLeftChildIndex(parentIndex)];
72+
}
73+
74+
/**
75+
* @param {number} parentIndex
76+
* @return {*}
77+
*/
78+
rightChild(parentIndex) {
79+
return this.heapContainer[this.getRightChildIndex(parentIndex)];
80+
}
81+
82+
/**
83+
* @param {number} childIndex
84+
* @return {*}
85+
*/
86+
parent(childIndex) {
87+
return this.heapContainer[this.getParentIndex(childIndex)];
88+
}
89+
90+
/**
91+
* @param {number} indexOne
92+
* @param {number} indexTwo
93+
*/
94+
swap(indexOne, indexTwo) {
95+
const tmp = this.heapContainer[indexTwo];
96+
this.heapContainer[indexTwo] = this.heapContainer[indexOne];
97+
this.heapContainer[indexOne] = tmp;
98+
}
99+
100+
/**
101+
* @return {*}
102+
*/
103+
peek() {
104+
if (this.heapContainer.length === 0) {
105+
return null;
106+
}
107+
108+
return this.heapContainer[0];
109+
}
110+
111+
/**
112+
* @return {*}
113+
*/
114+
poll() {
115+
if (this.heapContainer.length === 0) {
116+
return null;
117+
}
118+
119+
if (this.heapContainer.length === 1) {
120+
return this.heapContainer.pop();
121+
}
122+
123+
const item = this.heapContainer[0];
124+
125+
// Move the last element from the end to the head.
126+
this.heapContainer[0] = this.heapContainer.pop();
127+
this.heapifyDown();
128+
129+
return item;
130+
}
131+
132+
/**
133+
* @param {*} item
134+
* @return {MinHeap}
135+
*/
136+
add(item) {
137+
this.heapContainer.push(item);
138+
this.heapifyUp();
139+
return this;
140+
}
141+
142+
/**
143+
* @param {*} item
144+
* @param {Comparator} [customComparator]
145+
* @return {Number[]}
146+
*/
147+
find(item, customComparator) {
148+
const foundItemIndices = [];
149+
const comparator = customComparator || this.compare;
150+
151+
for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) {
152+
if (comparator.equal(item, this.heapContainer[itemIndex])) {
153+
foundItemIndices.push(itemIndex);
154+
}
155+
}
156+
157+
return foundItemIndices;
158+
}
159+
160+
/**
161+
* @return {boolean}
162+
*/
163+
isEmpty() {
164+
return !this.heapContainer.length;
165+
}
166+
167+
/**
168+
* @return {string}
169+
*/
170+
toString() {
171+
return this.heapContainer.toString();
172+
}
173+
}
174+
175+
export default Heap;

‎src/data-structures/heap/MaxHeap.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import Heap from './Heap';
2+
3+
/**
4+
* Creates a new MaxHeap
5+
* @class
6+
* @augments Heap
7+
*/
8+
class MaxHeap extends Heap {
9+
/**
10+
* @param {*} item
11+
* @param {Comparator} [customFindingComparator]
12+
* @return {MaxHeap}
13+
*/
14+
remove(item, customFindingComparator) {
15+
// Find number of items to remove.
16+
const customComparator = customFindingComparator || this.compare;
17+
const numberOfItemsToRemove = this.find(item, customComparator).length;
18+
19+
for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
20+
// We need to find item index to remove each time after removal since
21+
// indices are being change after each heapify process.
22+
const indexToRemove = this.find(item, customComparator).pop();
23+
24+
// If we need to remove last child in the heap then just remove it.
25+
// There is no need to heapify the heap afterwards.
26+
if (indexToRemove === (this.heapContainer.length - 1)) {
27+
this.heapContainer.pop();
28+
} else {
29+
// Move last element in heap to the vacant (removed) position.
30+
this.heapContainer[indexToRemove] = this.heapContainer.pop();
31+
32+
// Get parent.
33+
const parentItem = this.hasParent(indexToRemove) ? this.parent(indexToRemove) : null;
34+
const leftChild = this.hasLeftChild(indexToRemove) ? this.leftChild(indexToRemove) : null;
35+
36+
// If there is no parent or parent is greater then node to delete then heapify down.
37+
// Otherwise heapify up.
38+
if (
39+
leftChild !== null
40+
&& (
41+
parentItem === null
42+
|| this.compare.greaterThan(parentItem, this.heapContainer[indexToRemove])
43+
)
44+
) {
45+
this.heapifyDown(indexToRemove);
46+
} else {
47+
this.heapifyUp(indexToRemove);
48+
}
49+
}
50+
}
51+
52+
return this;
53+
}
54+
55+
/**
56+
* @param {number} [customStartIndex]
57+
*/
58+
heapifyUp(customStartIndex) {
59+
// Take last element (last in array or the bottom left in a tree) in
60+
// a heap container and lift him up until we find the parent element
61+
// that is greater then the current new one.
62+
let currentIndex = customStartIndex || this.heapContainer.length - 1;
63+
64+
while (
65+
this.hasParent(currentIndex)
66+
&& this.compare.greaterThan(this.heapContainer[currentIndex], this.parent(currentIndex))
67+
) {
68+
this.swap(currentIndex, this.getParentIndex(currentIndex));
69+
currentIndex = this.getParentIndex(currentIndex);
70+
}
71+
}
72+
73+
/**
74+
* @param {number} [customStartIndex]
75+
*/
76+
heapifyDown(customStartIndex) {
77+
// Compare the root element to its children and swap root with the smallest
78+
// of children. Do the same for next children after swap.
79+
let currentIndex = customStartIndex || 0;
80+
let nextIndex = null;
81+
82+
while (this.hasLeftChild(currentIndex)) {
83+
if (
84+
this.hasRightChild(currentIndex)
85+
&& this.compare.greaterThan(this.rightChild(currentIndex), this.leftChild(currentIndex))
86+
) {
87+
nextIndex = this.getRightChildIndex(currentIndex);
88+
} else {
89+
nextIndex = this.getLeftChildIndex(currentIndex);
90+
}
91+
92+
if (
93+
this.compare.greaterThan(this.heapContainer[currentIndex], this.heapContainer[nextIndex])
94+
) {
95+
break;
96+
}
97+
98+
this.swap(currentIndex, nextIndex);
99+
currentIndex = nextIndex;
100+
}
101+
}
102+
}
103+
104+
export default MaxHeap;

‎src/data-structures/heap/MinHeap.js

+9-167
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,11 @@
1-
import Comparator from '../../utils/comparator/Comparator';
2-
3-
export default class MinHeap {
4-
/**
5-
* @param {Function} [comparatorFunction]
6-
*/
7-
constructor(comparatorFunction) {
8-
// Array representation of the heap.
9-
this.heapContainer = [];
10-
this.compare = new Comparator(comparatorFunction);
11-
}
12-
13-
/**
14-
* @param {number} parentIndex
15-
* @return {number}
16-
*/
17-
getLeftChildIndex(parentIndex) {
18-
return (2 * parentIndex) + 1;
19-
}
20-
21-
/**
22-
* @param {number} parentIndex
23-
* @return {number}
24-
*/
25-
getRightChildIndex(parentIndex) {
26-
return (2 * parentIndex) + 2;
27-
}
28-
29-
/**
30-
* @param {number} childIndex
31-
* @return {number}
32-
*/
33-
getParentIndex(childIndex) {
34-
return Math.floor((childIndex - 1) / 2);
35-
}
36-
37-
/**
38-
* @param {number} childIndex
39-
* @return {boolean}
40-
*/
41-
hasParent(childIndex) {
42-
return this.getParentIndex(childIndex) >= 0;
43-
}
44-
45-
/**
46-
* @param {number} parentIndex
47-
* @return {boolean}
48-
*/
49-
hasLeftChild(parentIndex) {
50-
return this.getLeftChildIndex(parentIndex) < this.heapContainer.length;
51-
}
52-
53-
/**
54-
* @param {number} parentIndex
55-
* @return {boolean}
56-
*/
57-
hasRightChild(parentIndex) {
58-
return this.getRightChildIndex(parentIndex) < this.heapContainer.length;
59-
}
60-
61-
/**
62-
* @param {number} parentIndex
63-
* @return {*}
64-
*/
65-
leftChild(parentIndex) {
66-
return this.heapContainer[this.getLeftChildIndex(parentIndex)];
67-
}
68-
69-
/**
70-
* @param {number} parentIndex
71-
* @return {*}
72-
*/
73-
rightChild(parentIndex) {
74-
return this.heapContainer[this.getRightChildIndex(parentIndex)];
75-
}
76-
77-
/**
78-
* @param {number} childIndex
79-
* @return {*}
80-
*/
81-
parent(childIndex) {
82-
return this.heapContainer[this.getParentIndex(childIndex)];
83-
}
84-
85-
/**
86-
* @param {number} indexOne
87-
* @param {number} indexTwo
88-
*/
89-
swap(indexOne, indexTwo) {
90-
const tmp = this.heapContainer[indexTwo];
91-
this.heapContainer[indexTwo] = this.heapContainer[indexOne];
92-
this.heapContainer[indexOne] = tmp;
93-
}
94-
95-
/**
96-
* @return {*}
97-
*/
98-
peek() {
99-
if (this.heapContainer.length === 0) {
100-
return null;
101-
}
102-
103-
return this.heapContainer[0];
104-
}
105-
106-
/**
107-
* @return {*}
108-
*/
109-
poll() {
110-
if (this.heapContainer.length === 0) {
111-
return null;
112-
}
113-
114-
if (this.heapContainer.length === 1) {
115-
return this.heapContainer.pop();
116-
}
117-
118-
const item = this.heapContainer[0];
119-
120-
// Move the last element from the end to the head.
121-
this.heapContainer[0] = this.heapContainer.pop();
122-
this.heapifyDown();
123-
124-
return item;
125-
}
126-
127-
/**
128-
* @param {*} item
129-
* @return {MinHeap}
130-
*/
131-
add(item) {
132-
this.heapContainer.push(item);
133-
this.heapifyUp();
134-
return this;
135-
}
1+
import Heap from './Heap';
1362

3+
/**
4+
* Creates a new MinHeap
5+
* @class
6+
* @augments Heap
7+
*/
8+
class MinHeap extends Heap {
1379
/**
13810
* @param {*} item
13911
* @param {Comparator} [customFindingComparator]
@@ -180,24 +52,6 @@ export default class MinHeap {
18052
return this;
18153
}
18254

183-
/**
184-
* @param {*} item
185-
* @param {Comparator} [customComparator]
186-
* @return {Number[]}
187-
*/
188-
find(item, customComparator) {
189-
const foundItemIndices = [];
190-
const comparator = customComparator || this.compare;
191-
192-
for (let itemIndex = 0; itemIndex < this.heapContainer.length; itemIndex += 1) {
193-
if (comparator.equal(item, this.heapContainer[itemIndex])) {
194-
foundItemIndices.push(itemIndex);
195-
}
196-
}
197-
198-
return foundItemIndices;
199-
}
200-
20155
/**
20256
* @param {number} [customStartIndex]
20357
*/
@@ -243,18 +97,6 @@ export default class MinHeap {
24397
currentIndex = nextIndex;
24498
}
24599
}
246-
247-
/**
248-
* @return {boolean}
249-
*/
250-
isEmpty() {
251-
return !this.heapContainer.length;
252-
}
253-
254-
/**
255-
* @return {string}
256-
*/
257-
toString() {
258-
return this.heapContainer.toString();
259-
}
260100
}
101+
102+
export default MinHeap;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import MaxHeap from '../MaxHeap';
2+
import Comparator from '../../../utils/comparator/Comparator';
3+
4+
describe('MaxHeap', () => {
5+
it('should create an empty max heap', () => {
6+
const maxHeap = new MaxHeap();
7+
8+
expect(maxHeap).toBeDefined();
9+
expect(maxHeap.peek()).toBeNull();
10+
expect(maxHeap.isEmpty()).toBe(true);
11+
});
12+
13+
it('should add items to the heap and heapify it up', () => {
14+
const maxHeap = new MaxHeap();
15+
16+
maxHeap.add(5);
17+
expect(maxHeap.isEmpty()).toBe(false);
18+
expect(maxHeap.peek()).toBe(5);
19+
expect(maxHeap.toString()).toBe('5');
20+
21+
maxHeap.add(3);
22+
expect(maxHeap.peek()).toBe(5);
23+
expect(maxHeap.toString()).toBe('5,3');
24+
25+
maxHeap.add(10);
26+
expect(maxHeap.peek()).toBe(10);
27+
expect(maxHeap.toString()).toBe('10,3,5');
28+
29+
maxHeap.add(1);
30+
expect(maxHeap.peek()).toBe(10);
31+
expect(maxHeap.toString()).toBe('10,3,5,1');
32+
33+
maxHeap.add(1);
34+
expect(maxHeap.peek()).toBe(10);
35+
expect(maxHeap.toString()).toBe('10,3,5,1,1');
36+
37+
expect(maxHeap.poll()).toBe(10);
38+
expect(maxHeap.toString()).toBe('5,3,1,1');
39+
40+
expect(maxHeap.poll()).toBe(5);
41+
expect(maxHeap.toString()).toBe('3,1,1');
42+
43+
expect(maxHeap.poll()).toBe(3);
44+
expect(maxHeap.toString()).toBe('1,1');
45+
});
46+
47+
it('should poll items from the heap and heapify it down', () => {
48+
const maxHeap = new MaxHeap();
49+
50+
maxHeap.add(5);
51+
maxHeap.add(3);
52+
maxHeap.add(10);
53+
maxHeap.add(11);
54+
maxHeap.add(1);
55+
56+
expect(maxHeap.toString()).toBe('11,10,5,3,1');
57+
58+
expect(maxHeap.poll()).toBe(11);
59+
expect(maxHeap.toString()).toBe('10,3,5,1');
60+
61+
expect(maxHeap.poll()).toBe(10);
62+
expect(maxHeap.toString()).toBe('5,3,1');
63+
64+
expect(maxHeap.poll()).toBe(5);
65+
expect(maxHeap.toString()).toBe('3,1');
66+
67+
expect(maxHeap.poll()).toBe(3);
68+
expect(maxHeap.toString()).toBe('1');
69+
70+
expect(maxHeap.poll()).toBe(1);
71+
expect(maxHeap.toString()).toBe('');
72+
73+
expect(maxHeap.poll()).toBeNull();
74+
expect(maxHeap.toString()).toBe('');
75+
});
76+
77+
it('should heapify down through the right branch as well', () => {
78+
const maxHeap = new MaxHeap();
79+
80+
maxHeap.add(3);
81+
maxHeap.add(12);
82+
maxHeap.add(10);
83+
84+
expect(maxHeap.toString()).toBe('12,3,10');
85+
86+
maxHeap.add(11);
87+
expect(maxHeap.toString()).toBe('12,11,10,3');
88+
89+
expect(maxHeap.poll()).toBe(12);
90+
expect(maxHeap.toString()).toBe('11,3,10');
91+
});
92+
93+
it('should be possible to find item indices in heap', () => {
94+
const maxHeap = new MaxHeap();
95+
96+
maxHeap.add(3);
97+
maxHeap.add(12);
98+
maxHeap.add(10);
99+
maxHeap.add(11);
100+
maxHeap.add(11);
101+
102+
expect(maxHeap.toString()).toBe('12,11,10,3,11');
103+
104+
expect(maxHeap.find(5)).toEqual([]);
105+
expect(maxHeap.find(12)).toEqual([0]);
106+
expect(maxHeap.find(11)).toEqual([1, 4]);
107+
});
108+
109+
it('should be possible to remove items from heap with heapify down', () => {
110+
const maxHeap = new MaxHeap();
111+
112+
maxHeap.add(3);
113+
maxHeap.add(12);
114+
maxHeap.add(10);
115+
maxHeap.add(11);
116+
maxHeap.add(11);
117+
118+
expect(maxHeap.toString()).toBe('12,11,10,3,11');
119+
120+
expect(maxHeap.remove(12).toString()).toEqual('11,11,10,3');
121+
expect(maxHeap.remove(12).peek()).toEqual(11);
122+
expect(maxHeap.remove(11).toString()).toEqual('10,3');
123+
expect(maxHeap.remove(10).peek()).toEqual(3);
124+
});
125+
126+
it('should be possible to remove items from heap with heapify up', () => {
127+
const maxHeap = new MaxHeap();
128+
129+
maxHeap.add(3);
130+
maxHeap.add(10);
131+
maxHeap.add(5);
132+
maxHeap.add(6);
133+
maxHeap.add(7);
134+
maxHeap.add(4);
135+
maxHeap.add(6);
136+
maxHeap.add(8);
137+
maxHeap.add(2);
138+
maxHeap.add(1);
139+
140+
expect(maxHeap.toString()).toBe('10,8,6,7,6,4,5,3,2,1');
141+
expect(maxHeap.remove(4).toString()).toEqual('10,8,6,7,6,1,5,3,2');
142+
expect(maxHeap.remove(3).toString()).toEqual('10,8,6,7,6,1,5,2');
143+
expect(maxHeap.remove(5).toString()).toEqual('10,8,6,7,6,1,2');
144+
expect(maxHeap.remove(10).toString()).toEqual('8,7,6,2,6,1');
145+
expect(maxHeap.remove(6).toString()).toEqual('8,7,1,2');
146+
expect(maxHeap.remove(2).toString()).toEqual('8,7,1');
147+
expect(maxHeap.remove(1).toString()).toEqual('8,7');
148+
expect(maxHeap.remove(7).toString()).toEqual('8');
149+
expect(maxHeap.remove(8).toString()).toEqual('');
150+
});
151+
152+
it('should be possible to remove items from heap with custom finding comparator', () => {
153+
const maxHeap = new MaxHeap();
154+
maxHeap.add('a');
155+
maxHeap.add('bb');
156+
maxHeap.add('ccc');
157+
maxHeap.add('dddd');
158+
159+
expect(maxHeap.toString()).toBe('dddd,ccc,bb,a');
160+
161+
const comparator = new Comparator((a, b) => {
162+
if (a.length === b.length) {
163+
return 0;
164+
}
165+
166+
return a.length < b.length ? -1 : 1;
167+
});
168+
169+
maxHeap.remove('hey', comparator);
170+
expect(maxHeap.toString()).toBe('dddd,a,bb');
171+
});
172+
});

0 commit comments

Comments
 (0)
Please sign in to comment.