Skip to content

Commit 3f02b48

Browse files
authored
Merge pull request #3 from trekhleb/master
Added Binary Indexed Tree / Fenwick Tree Implementation
2 parents 1881491 + 39934eb commit 3f02b48

File tree

10 files changed

+271
-4
lines changed

10 files changed

+271
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ the data.
3333
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
3434
* [Red-Black Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/red-black-tree)
3535
* [Segment Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/segment-tree) - with min/max/sum range queries examples
36+
* [Fenwick Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/fenwick-tree) (Binary Indexed Tree)
3637
* [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph) (both directed and undirected)
3738
* [Disjoint Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/disjoint-set)
3839

README.zh-TW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ npm test -- -t 'playground'
197197
| **紅黑樹** | log(n) | log(n) | log(n) | log(n) |
198198
| **AVL Tree** | log(n) | log(n) | log(n) | log(n) |
199199

200-
### 陣列搜尋演算法複雜度
200+
### 陣列排序演算法複雜度
201201

202202
| 名稱 | 最佳 | 平均 | 最差 | 記憶體 | 穩定 |
203203
| --------------------- | :-------: | :-------: | :-----------: | :-------: | :-------: |

src/algorithms/math/primality-test/README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Primality Test
22

3-
A primality test is an algorithm for determining whether an input
3+
A **prime number** (or a **prime**) is a natural number greater than `1` that
4+
cannot be formed by multiplying two smaller natural numbers. A natural number
5+
greater than `1` that is not prime is called a composite number. For
6+
example, `5` is prime because the only ways of writing it as a
7+
product, `1 × 5` or `5 × 1`, involve `5` itself. However, `6` is
8+
composite because it is the product of two numbers `(2 × 3)` that are
9+
both smaller than `6`.
10+
11+
![Prime Numbers](https://upload.wikimedia.org/wikipedia/commons/f/f0/Primes-vs-composites.svg)
12+
13+
A **primality test** is an algorithm for determining whether an input
414
number is prime. Among other fields of mathematics, it is used
515
for cryptography. Unlike integer factorization, primality tests
616
do not generally give prime factors, only stating whether the
@@ -11,4 +21,5 @@ size of the input).
1121

1222
## References
1323

14-
[Wikipedia](https://en.wikipedia.org/wiki/Primality_test)
24+
- [Prime Numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number)
25+
- [Primality Test on Wikipedia](https://en.wikipedia.org/wiki/Primality_test)

src/algorithms/math/primality-test/__test__/trialDivision.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ function primalityTest(testFunction) {
2323
expect(testFunction(192)).toBeFalsy();
2424
expect(testFunction(200)).toBeFalsy();
2525
expect(testFunction(400)).toBeFalsy();
26+
27+
// It should also deal with floats.
28+
expect(testFunction(0.5)).toBeFalsy();
29+
expect(testFunction(1.3)).toBeFalsy();
30+
expect(testFunction(10.5)).toBeFalsy();
2631
}
2732

2833
describe('trialDivision', () => {

src/algorithms/math/primality-test/trialDivision.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
* @return {boolean}
44
*/
55
export default function trialDivision(number) {
6+
// Check if number is integer.
7+
if (number % 1 !== 0) {
8+
return false;
9+
}
10+
611
if (number <= 1) {
712
// If number is less than one then it isn't prime by definition.
813
return false;

src/data-structures/heap/MinHeap.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ export default class MinHeap {
141141
*/
142142
remove(item, customFindingComparator) {
143143
// Find number of items to remove.
144-
const numberOfItemsToRemove = this.find(item).length;
145144
const customComparator = customFindingComparator || this.compare;
145+
const numberOfItemsToRemove = this.find(item, customComparator).length;
146146

147147
for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
148148
// We need to find item index to remove each time after removal since

src/data-structures/heap/__test__/MinHeap.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import MinHeap from '../MinHeap';
2+
import Comparator from '../../../utils/comparator/Comparator';
23

34
describe('MinHeap', () => {
45
it('should create an empty min heap', () => {
@@ -147,4 +148,25 @@ describe('MinHeap', () => {
147148
expect(minHeap.remove(3).toString()).toEqual('4');
148149
expect(minHeap.remove(4).toString()).toEqual('');
149150
});
151+
152+
it('should be possible to remove items from heap with custom finding comparator', () => {
153+
const minHeap = new MinHeap();
154+
minHeap.add('dddd');
155+
minHeap.add('ccc');
156+
minHeap.add('bb');
157+
minHeap.add('a');
158+
159+
expect(minHeap.toString()).toBe('a,bb,ccc,dddd');
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+
minHeap.remove('hey', comparator);
170+
expect(minHeap.toString()).toBe('a,bb,dddd');
171+
});
150172
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
export default class FenwickTree {
2+
/**
3+
* Constructor creates empty fenwick tree of size 'arraySize',
4+
* however, array size is size+1, because index is 1-based.
5+
*
6+
* @param {number} arraySize
7+
*/
8+
constructor(arraySize) {
9+
this.arraySize = arraySize;
10+
11+
// Fill tree array with zeros.
12+
this.treeArray = Array(this.arraySize + 1).fill(0);
13+
}
14+
15+
/**
16+
* Adds value to existing value at position.
17+
*
18+
* @param {number} position
19+
* @param {number} value
20+
* @return {FenwickTree}
21+
*/
22+
increase(position, value) {
23+
if (position < 1 || position > this.arraySize) {
24+
throw new Error('Position is out of allowed range');
25+
}
26+
27+
for (let i = position; i <= this.arraySize; i += (i & -i)) {
28+
this.treeArray[i] += value;
29+
}
30+
31+
return this;
32+
}
33+
34+
/**
35+
* Query sum from index 1 to position.
36+
*
37+
* @param {number} position
38+
* @return {number}
39+
*/
40+
query(position) {
41+
if (position < 1 || position > this.arraySize) {
42+
throw new Error('Position is out of allowed range');
43+
}
44+
45+
let sum = 0;
46+
47+
for (let i = position; i > 0; i -= (i & -i)) {
48+
sum += this.treeArray[i];
49+
}
50+
51+
return sum;
52+
}
53+
54+
/**
55+
* Query sum from index leftIndex to rightIndex.
56+
*
57+
* @param {number} leftIndex
58+
* @param {number} rightIndex
59+
* @return {number}
60+
*/
61+
queryRange(leftIndex, rightIndex) {
62+
if (leftIndex > rightIndex) {
63+
throw new Error('Left index can not be greater then right one');
64+
}
65+
66+
if (leftIndex === 1) {
67+
return this.query(rightIndex);
68+
}
69+
70+
return this.query(rightIndex) - this.query(leftIndex - 1);
71+
}
72+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Fenwick Tree / Binary Indexed Tree
2+
3+
A **Fenwick tree** or **binary indexed tree** is a data
4+
structure that can efficiently update elements and
5+
calculate prefix sums in a table of numbers.
6+
7+
When compared with a flat array of numbers, the Fenwick tree achieves a
8+
much better balance between two operations: element update and prefix sum
9+
calculation. In a flat array of `n` numbers, you can either store the elements,
10+
or the prefix sums. In the first case, computing prefix sums requires linear
11+
time; in the second case, updating the array elements requires linear time
12+
(in both cases, the other operation can be performed in constant time).
13+
Fenwick trees allow both operations to be performed in `O(log n)` time.
14+
This is achieved by representing the numbers as a tree, where the value of
15+
each node is the sum of the numbers in that subtree. The tree structure allows
16+
operations to be performed using only `O(log n)` node accesses.
17+
18+
## Implementation Notes
19+
20+
Binary Indexed Tree is represented as an array. Each node of Binary Indexed Tree
21+
stores sum of some elements of given array. Size of Binary Indexed Tree is equal
22+
to `n` where `n` is size of input array. In current implementation we have used
23+
size as `n+1` for ease of implementation. All the indexes are 1-based.
24+
25+
![Binary Indexed Tree](https://www.geeksforgeeks.org/wp-content/uploads/BITSum.png)
26+
27+
On the picture below you may see animated example of
28+
creation of binary indexed tree for the
29+
array `[1, 2, 3, 4, 5]` by inserting one by one.
30+
31+
![Fenwick Tree](https://upload.wikimedia.org/wikipedia/commons/d/dc/BITDemo.gif)
32+
33+
## References
34+
35+
- [Wikipedia](https://en.wikipedia.org/wiki/Fenwick_tree)
36+
- [GeeksForGeeks](https://www.geeksforgeeks.org/binary-indexed-tree-or-fenwick-tree-2/)
37+
- [YouTube](https://www.youtube.com/watch?v=CWDQJGaN1gY&index=18&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import FenwickTree from '../FenwickTree';
2+
3+
describe('FenwickTree', () => {
4+
it('should create empty fenwick tree of correct size', () => {
5+
const tree1 = new FenwickTree(5);
6+
expect(tree1.treeArray.length).toBe(5 + 1);
7+
8+
for (let i = 0; i < 5; i += 1) {
9+
expect(tree1.treeArray[i]).toBe(0);
10+
}
11+
12+
const tree2 = new FenwickTree(50);
13+
expect(tree2.treeArray.length).toBe(50 + 1);
14+
});
15+
16+
it('should create correct fenwick tree', () => {
17+
const inputArray = [3, 2, -1, 6, 5, 4, -3, 3, 7, 2, 3];
18+
19+
const tree = new FenwickTree(inputArray.length);
20+
expect(tree.treeArray.length).toBe(inputArray.length + 1);
21+
22+
inputArray.forEach((value, index) => {
23+
tree.increase(index + 1, value);
24+
});
25+
26+
expect(tree.treeArray).toEqual([0, 3, 5, -1, 10, 5, 9, -3, 19, 7, 9, 3]);
27+
28+
expect(tree.query(1)).toBe(3);
29+
expect(tree.query(2)).toBe(5);
30+
expect(tree.query(3)).toBe(4);
31+
expect(tree.query(4)).toBe(10);
32+
expect(tree.query(5)).toBe(15);
33+
expect(tree.query(6)).toBe(19);
34+
expect(tree.query(7)).toBe(16);
35+
expect(tree.query(8)).toBe(19);
36+
expect(tree.query(9)).toBe(26);
37+
expect(tree.query(10)).toBe(28);
38+
expect(tree.query(11)).toBe(31);
39+
40+
expect(tree.queryRange(1, 1)).toBe(3);
41+
expect(tree.queryRange(1, 2)).toBe(5);
42+
expect(tree.queryRange(2, 4)).toBe(7);
43+
expect(tree.queryRange(6, 9)).toBe(11);
44+
45+
tree.increase(3, 1);
46+
47+
expect(tree.query(1)).toBe(3);
48+
expect(tree.query(2)).toBe(5);
49+
expect(tree.query(3)).toBe(5);
50+
expect(tree.query(4)).toBe(11);
51+
expect(tree.query(5)).toBe(16);
52+
expect(tree.query(6)).toBe(20);
53+
expect(tree.query(7)).toBe(17);
54+
expect(tree.query(8)).toBe(20);
55+
expect(tree.query(9)).toBe(27);
56+
expect(tree.query(10)).toBe(29);
57+
expect(tree.query(11)).toBe(32);
58+
59+
expect(tree.queryRange(1, 1)).toBe(3);
60+
expect(tree.queryRange(1, 2)).toBe(5);
61+
expect(tree.queryRange(2, 4)).toBe(8);
62+
expect(tree.queryRange(6, 9)).toBe(11);
63+
});
64+
65+
it('should correctly execute queries', () => {
66+
const tree = new FenwickTree(5);
67+
68+
tree.increase(1, 4);
69+
tree.increase(3, 7);
70+
71+
expect(tree.query(1)).toBe(4);
72+
expect(tree.query(3)).toBe(11);
73+
expect(tree.query(5)).toBe(11);
74+
expect(tree.queryRange(2, 3)).toBe(7);
75+
76+
tree.increase(2, 5);
77+
expect(tree.query(5)).toBe(16);
78+
79+
tree.increase(1, 3);
80+
expect(tree.queryRange(1, 1)).toBe(7);
81+
expect(tree.query(5)).toBe(19);
82+
expect(tree.queryRange(1, 5)).toBe(19);
83+
});
84+
85+
it('should throw exceptions', () => {
86+
const tree = new FenwickTree(5);
87+
88+
const increaseAtInvalidLowIndex = () => {
89+
tree.increase(0, 1);
90+
};
91+
92+
const increaseAtInvalidHighIndex = () => {
93+
tree.increase(10, 1);
94+
};
95+
96+
const queryInvalidLowIndex = () => {
97+
tree.query(0);
98+
};
99+
100+
const queryInvalidHighIndex = () => {
101+
tree.query(10);
102+
};
103+
104+
const rangeQueryInvalidIndex = () => {
105+
tree.queryRange(3, 2);
106+
};
107+
108+
expect(increaseAtInvalidLowIndex).toThrowError();
109+
expect(increaseAtInvalidHighIndex).toThrowError();
110+
expect(queryInvalidLowIndex).toThrowError();
111+
expect(queryInvalidHighIndex).toThrowError();
112+
expect(rangeQueryInvalidIndex).toThrowError();
113+
});
114+
});

0 commit comments

Comments
 (0)