Skip to content

Commit 9d9e348

Browse files
Add Fenwick tree implementation and tests (#7637)
* Add Fenwick tree implementation and tests * Fix FenwickTree test filename and add documentation link
1 parent f5772e3 commit 9d9e348

3 files changed

Lines changed: 317 additions & 8 deletions

File tree

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
package com.thealgorithms.datastructures.trees;
22

3+
/**
4+
* Fenwick Tree for point updates and prefix-sum queries in O(log n).
5+
*
6+
* @see <a href="https://cp-algorithms.com/data_structures/fenwick.html">Fenwick Tree</a>
7+
*/
38
public class FenwickTree {
49

5-
private int n;
6-
private int[] fenTree;
10+
private final int n;
11+
private final int[] fenTree;
712

8-
/* Constructor which takes the size of the array as a parameter */
13+
/** Creates a Fenwick tree with n elements, all initialized to zero. */
914
public FenwickTree(int n) {
15+
if (n < 0) {
16+
throw new IllegalArgumentException("Size must be non-negative");
17+
}
1018
this.n = n;
1119
this.fenTree = new int[n + 1];
1220
}
1321

14-
/* A function which will add the element val at index i*/
22+
/** Adds val to the element at index i. */
1523
public void update(int i, int val) {
16-
// As index starts from 0, increment the index by 1
17-
i += 1;
24+
checkIndex(i);
25+
i += 1; // Convert to the internal 1-based index
1826
while (i <= n) {
1927
fenTree[i] += val;
2028
i += i & (-i);
2129
}
2230
}
2331

24-
/* A function which will return the cumulative sum from index 1 to index i*/
32+
/** Returns the sum of elements from index 0 to i. */
2533
public int query(int i) {
26-
// As index starts from 0, increment the index by 1
34+
checkIndex(i);
2735
i += 1;
2836
int cumSum = 0;
2937
while (i > 0) {
@@ -32,4 +40,11 @@ public int query(int i) {
3240
}
3341
return cumSum;
3442
}
43+
44+
// Check that the index is within the valid range.
45+
private void checkIndex(int i) {
46+
if (i < 0 || i >= n) {
47+
throw new IndexOutOfBoundsException("Index " + i + " out of bounds for size " + n);
48+
}
49+
}
3550
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.thealgorithms.datastructures.trees;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.Random;
7+
import org.junit.jupiter.api.Test;
8+
9+
class FenwickTreeTest {
10+
11+
@Test
12+
void queryOnFreshTreeReturnsZero() {
13+
FenwickTree tree = new FenwickTree(5);
14+
for (int i = 0; i < 5; i++) {
15+
assertEquals(0, tree.query(i));
16+
}
17+
}
18+
19+
@Test
20+
void singleElementTree() {
21+
FenwickTree tree = new FenwickTree(1);
22+
tree.update(0, 7);
23+
assertEquals(7, tree.query(0));
24+
}
25+
26+
@Test
27+
void prefixSumsAfterUpdates() {
28+
FenwickTree tree = new FenwickTree(5);
29+
int[] values = {3, 2, -1, 6, 5};
30+
for (int i = 0; i < values.length; i++) {
31+
tree.update(i, values[i]);
32+
}
33+
assertEquals(3, tree.query(0));
34+
assertEquals(5, tree.query(1));
35+
assertEquals(4, tree.query(2));
36+
assertEquals(10, tree.query(3));
37+
assertEquals(15, tree.query(4));
38+
}
39+
40+
@Test
41+
void repeatedUpdatesOnSameIndexAccumulate() {
42+
FenwickTree tree = new FenwickTree(4);
43+
tree.update(2, 5);
44+
tree.update(2, 3);
45+
tree.update(2, -2);
46+
assertEquals(0, tree.query(1));
47+
assertEquals(6, tree.query(2));
48+
assertEquals(6, tree.query(3));
49+
}
50+
51+
@Test
52+
void negativeValues() {
53+
FenwickTree tree = new FenwickTree(3);
54+
tree.update(0, -4);
55+
tree.update(1, -6);
56+
tree.update(2, 10);
57+
assertEquals(-4, tree.query(0));
58+
assertEquals(-10, tree.query(1));
59+
assertEquals(0, tree.query(2));
60+
}
61+
62+
@Test
63+
void nonPowerOfTwoSize() {
64+
int size = 13;
65+
FenwickTree tree = new FenwickTree(size);
66+
for (int i = 0; i < size; i++) {
67+
tree.update(i, 1);
68+
}
69+
for (int i = 0; i < size; i++) {
70+
assertEquals(i + 1, tree.query(i));
71+
}
72+
}
73+
74+
@Test
75+
void matchesNaivePrefixSumOnRandomData() {
76+
Random random = new Random(42);
77+
int size = 100;
78+
FenwickTree tree = new FenwickTree(size);
79+
int[] naive = new int[size];
80+
81+
for (int step = 0; step < 500; step++) {
82+
int index = random.nextInt(size);
83+
int delta = random.nextInt(21) - 10;
84+
tree.update(index, delta);
85+
naive[index] += delta;
86+
}
87+
88+
int running = 0;
89+
for (int i = 0; i < size; i++) {
90+
running += naive[i];
91+
assertEquals(running, tree.query(i), "Mismatch at index " + i);
92+
}
93+
}
94+
95+
@Test
96+
void zeroSizeTreeRejectsAnyIndex() {
97+
FenwickTree tree = new FenwickTree(0);
98+
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(0, 1));
99+
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(0));
100+
}
101+
102+
@Test
103+
void negativeSizeThrows() {
104+
assertThrows(IllegalArgumentException.class, () -> new FenwickTree(-1));
105+
}
106+
107+
@Test
108+
void outOfBoundsIndicesThrow() {
109+
FenwickTree tree = new FenwickTree(5);
110+
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(-1, 1));
111+
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(5, 1));
112+
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(-1));
113+
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(5));
114+
}
115+
}

‎ts"‎

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
2+
index 5378a01..be8a92e 100644
3+
--- a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
4+
+++ b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
5+
@@ -1,29 +1,33 @@
6+
package com.thealgorithms.datastructures.trees;
7+

8+
+/** Fenwick Tree for point updates and prefix-sum queries in O(log n). */
9+
public class FenwickTree {
10+

11+
- private int n;
12+
- private int[] fenTree;
13+
+ private final int n;
14+
+ private final int[] fenTree;
15+

16+
- /* Constructor which takes the size of the array as a parameter */
17+
+ /** Creates a Fenwick tree with n elements, all initialized to zero. */
18+
public FenwickTree(int n) {
19+
+ if (n < 0) {
20+
+ throw new IllegalArgumentException("Size must be non-negative");
21+
+ }
22+
this.n = n;
23+
this.fenTree = new int[n + 1];
24+
}
25+

26+
- /* A function which will add the element val at index i*/
27+
+ /** Adds val to the element at index i. */
28+
public void update(int i, int val) {
29+
- // As index starts from 0, increment the index by 1
30+
- i += 1;
31+
+ checkIndex(i);
32+
+ i += 1; // Convert to the internal 1-based index
33+
while (i <= n) {
34+
fenTree[i] += val;
35+
i += i & (-i);
36+
}
37+
}
38+

39+
- /* A function which will return the cumulative sum from index 1 to index i*/
40+
+ /** Returns the sum of elements from index 0 to i. */
41+
public int query(int i) {
42+
- // As index starts from 0, increment the index by 1
43+
+ checkIndex(i);
44+
i += 1;
45+
int cumSum = 0;
46+
while (i > 0) {
47+
@@ -32,4 +36,11 @@ public class FenwickTree {
48+
}
49+
return cumSum;
50+
}
51+
+
52+
+ // Check that the index is within the valid range.
53+
+ private void checkIndex(int i) {
54+
+ if (i < 0 || i >= n) {
55+
+ throw new IndexOutOfBoundsException("Index " + i + " out of bounds for size " + n);
56+
+ }
57+
+ }
58+
}
59+
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java
60+
new file mode 100644
61+
index 0000000..47ef568
62+
--- /dev/null
63+
+++ b/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java
64+
@@ -0,0 +1,115 @@
65+
+package com.thealgorithms.datastructures.trees;
66+
+
67+
+import static org.junit.jupiter.api.Assertions.assertEquals;
68+
+import static org.junit.jupiter.api.Assertions.assertThrows;
69+
+
70+
+import java.util.Random;
71+
+import org.junit.jupiter.api.Test;
72+
+
73+
+class FenwickTreeTest {
74+
+
75+
+ @Test
76+
+ void queryOnFreshTreeReturnsZero() {
77+
+ FenwickTree tree = new FenwickTree(5);
78+
+ for (int i = 0; i < 5; i++) {
79+
+ assertEquals(0, tree.query(i));
80+
+ }
81+
+ }
82+
+
83+
+ @Test
84+
+ void singleElementTree() {
85+
+ FenwickTree tree = new FenwickTree(1);
86+
+ tree.update(0, 7);
87+
+ assertEquals(7, tree.query(0));
88+
+ }
89+
+
90+
+ @Test
91+
+ void prefixSumsAfterUpdates() {
92+
+ FenwickTree tree = new FenwickTree(5);
93+
+ int[] values = {3, 2, -1, 6, 5};
94+
+ for (int i = 0; i < values.length; i++) {
95+
+ tree.update(i, values[i]);
96+
+ }
97+
+ assertEquals(3, tree.query(0));
98+
+ assertEquals(5, tree.query(1));
99+
+ assertEquals(4, tree.query(2));
100+
+ assertEquals(10, tree.query(3));
101+
+ assertEquals(15, tree.query(4));
102+
+ }
103+
+
104+
+ @Test
105+
+ void repeatedUpdatesOnSameIndexAccumulate() {
106+
+ FenwickTree tree = new FenwickTree(4);
107+
+ tree.update(2, 5);
108+
+ tree.update(2, 3);
109+
+ tree.update(2, -2);
110+
+ assertEquals(0, tree.query(1));
111+
+ assertEquals(6, tree.query(2));
112+
+ assertEquals(6, tree.query(3));
113+
+ }
114+
+
115+
+ @Test
116+
+ void negativeValues() {
117+
+ FenwickTree tree = new FenwickTree(3);
118+
+ tree.update(0, -4);
119+
+ tree.update(1, -6);
120+
+ tree.update(2, 10);
121+
+ assertEquals(-4, tree.query(0));
122+
+ assertEquals(-10, tree.query(1));
123+
+ assertEquals(0, tree.query(2));
124+
+ }
125+
+
126+
+ @Test
127+
+ void nonPowerOfTwoSize() {
128+
+ int size = 13;
129+
+ FenwickTree tree = new FenwickTree(size);
130+
+ for (int i = 0; i < size; i++) {
131+
+ tree.update(i, 1);
132+
+ }
133+
+ for (int i = 0; i < size; i++) {
134+
+ assertEquals(i + 1, tree.query(i));
135+
+ }
136+
+ }
137+
+
138+
+ @Test
139+
+ void matchesNaivePrefixSumOnRandomData() {
140+
+ Random random = new Random(42);
141+
+ int size = 100;
142+
+ FenwickTree tree = new FenwickTree(size);
143+
+ int[] naive = new int[size];
144+
+
145+
+ for (int step = 0; step < 500; step++) {
146+
+ int index = random.nextInt(size);
147+
+ int delta = random.nextInt(21) - 10;
148+
+ tree.update(index, delta);
149+
+ naive[index] += delta;
150+
+ }
151+
+
152+
+ int running = 0;
153+
+ for (int i = 0; i < size; i++) {
154+
+ running += naive[i];
155+
+ assertEquals(running, tree.query(i), "Mismatch at index " + i);
156+
+ }
157+
+ }
158+
+
159+
+ @Test
160+
+ void zeroSizeTreeRejectsAnyIndex() {
161+
+ FenwickTree tree = new FenwickTree(0);
162+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(0, 1));
163+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(0));
164+
+ }
165+
+
166+
+ @Test
167+
+ void negativeSizeThrows() {
168+
+ assertThrows(IllegalArgumentException.class, () -> new FenwickTree(-1));
169+
+ }
170+
+
171+
+ @Test
172+
+ void outOfBoundsIndicesThrow() {
173+
+ FenwickTree tree = new FenwickTree(5);
174+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(-1, 1));
175+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(5, 1));
176+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(-1));
177+
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(5));
178+
+ }
179+
+}

0 commit comments

Comments
 (0)