Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
package com.thealgorithms.datastructures.trees;

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

private int n;
private int[] fenTree;
private final int n;
private final int[] fenTree;

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

/* A function which will add the element val at index i*/
/** Adds val to the element at index i. */
public void update(int i, int val) {
// As index starts from 0, increment the index by 1
i += 1;
checkIndex(i);
i += 1; // Convert to the internal 1-based index
while (i <= n) {
fenTree[i] += val;
i += i & (-i);
}
}

/* A function which will return the cumulative sum from index 1 to index i*/
/** Returns the sum of elements from index 0 to i. */
public int query(int i) {
// As index starts from 0, increment the index by 1
checkIndex(i);
i += 1;
int cumSum = 0;
while (i > 0) {
Expand All @@ -32,4 +40,11 @@ public int query(int i) {
}
return cumSum;
}

// Check that the index is within the valid range.
private void checkIndex(int i) {
if (i < 0 || i >= n) {
throw new IndexOutOfBoundsException("Index " + i + " out of bounds for size " + n);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.thealgorithms.datastructures.trees;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Random;
import org.junit.jupiter.api.Test;

class FenwickTreeTest {
Comment thread
Rohan45create marked this conversation as resolved.

@Test
void queryOnFreshTreeReturnsZero() {
FenwickTree tree = new FenwickTree(5);
for (int i = 0; i < 5; i++) {
assertEquals(0, tree.query(i));
}
}

@Test
void singleElementTree() {
FenwickTree tree = new FenwickTree(1);
tree.update(0, 7);
assertEquals(7, tree.query(0));
}

@Test
void prefixSumsAfterUpdates() {
FenwickTree tree = new FenwickTree(5);
int[] values = {3, 2, -1, 6, 5};
for (int i = 0; i < values.length; i++) {
tree.update(i, values[i]);
}
assertEquals(3, tree.query(0));
assertEquals(5, tree.query(1));
assertEquals(4, tree.query(2));
assertEquals(10, tree.query(3));
assertEquals(15, tree.query(4));
}

@Test
void repeatedUpdatesOnSameIndexAccumulate() {
FenwickTree tree = new FenwickTree(4);
tree.update(2, 5);
tree.update(2, 3);
tree.update(2, -2);
assertEquals(0, tree.query(1));
assertEquals(6, tree.query(2));
assertEquals(6, tree.query(3));
}

@Test
void negativeValues() {
FenwickTree tree = new FenwickTree(3);
tree.update(0, -4);
tree.update(1, -6);
tree.update(2, 10);
assertEquals(-4, tree.query(0));
assertEquals(-10, tree.query(1));
assertEquals(0, tree.query(2));
}

@Test
void nonPowerOfTwoSize() {
int size = 13;
FenwickTree tree = new FenwickTree(size);
for (int i = 0; i < size; i++) {
tree.update(i, 1);
}
for (int i = 0; i < size; i++) {
assertEquals(i + 1, tree.query(i));
}
}

@Test
void matchesNaivePrefixSumOnRandomData() {
Random random = new Random(42);
int size = 100;
FenwickTree tree = new FenwickTree(size);
int[] naive = new int[size];

for (int step = 0; step < 500; step++) {
int index = random.nextInt(size);
int delta = random.nextInt(21) - 10;
tree.update(index, delta);
naive[index] += delta;
}

int running = 0;
for (int i = 0; i < size; i++) {
running += naive[i];
assertEquals(running, tree.query(i), "Mismatch at index " + i);
}
}

@Test
void zeroSizeTreeRejectsAnyIndex() {
FenwickTree tree = new FenwickTree(0);
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(0, 1));
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(0));
}

@Test
void negativeSizeThrows() {
assertThrows(IllegalArgumentException.class, () -> new FenwickTree(-1));
}

@Test
void outOfBoundsIndicesThrow() {
FenwickTree tree = new FenwickTree(5);
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(-1, 1));
assertThrows(IndexOutOfBoundsException.class, () -> tree.update(5, 1));
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(-1));
assertThrows(IndexOutOfBoundsException.class, () -> tree.query(5));
}
}
179 changes: 179 additions & 0 deletions ts"
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
index 5378a01..be8a92e 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java
@@ -1,29 +1,33 @@
package com.thealgorithms.datastructures.trees;

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

- private int n;
- private int[] fenTree;
+ private final int n;
+ private final int[] fenTree;

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

- /* A function which will add the element val at index i*/
+ /** Adds val to the element at index i. */
public void update(int i, int val) {
- // As index starts from 0, increment the index by 1
- i += 1;
+ checkIndex(i);
+ i += 1; // Convert to the internal 1-based index
while (i <= n) {
fenTree[i] += val;
i += i & (-i);
}
}

- /* A function which will return the cumulative sum from index 1 to index i*/
+ /** Returns the sum of elements from index 0 to i. */
public int query(int i) {
- // As index starts from 0, increment the index by 1
+ checkIndex(i);
i += 1;
int cumSum = 0;
while (i > 0) {
@@ -32,4 +36,11 @@ public class FenwickTree {
}
return cumSum;
}
+
+ // Check that the index is within the valid range.
+ private void checkIndex(int i) {
+ if (i < 0 || i >= n) {
+ throw new IndexOutOfBoundsException("Index " + i + " out of bounds for size " + n);
+ }
+ }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java
new file mode 100644
index 0000000..47ef568
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java
@@ -0,0 +1,115 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Random;
+import org.junit.jupiter.api.Test;
+
+class FenwickTreeTest {
+
+ @Test
+ void queryOnFreshTreeReturnsZero() {
+ FenwickTree tree = new FenwickTree(5);
+ for (int i = 0; i < 5; i++) {
+ assertEquals(0, tree.query(i));
+ }
+ }
+
+ @Test
+ void singleElementTree() {
+ FenwickTree tree = new FenwickTree(1);
+ tree.update(0, 7);
+ assertEquals(7, tree.query(0));
+ }
+
+ @Test
+ void prefixSumsAfterUpdates() {
+ FenwickTree tree = new FenwickTree(5);
+ int[] values = {3, 2, -1, 6, 5};
+ for (int i = 0; i < values.length; i++) {
+ tree.update(i, values[i]);
+ }
+ assertEquals(3, tree.query(0));
+ assertEquals(5, tree.query(1));
+ assertEquals(4, tree.query(2));
+ assertEquals(10, tree.query(3));
+ assertEquals(15, tree.query(4));
+ }
+
+ @Test
+ void repeatedUpdatesOnSameIndexAccumulate() {
+ FenwickTree tree = new FenwickTree(4);
+ tree.update(2, 5);
+ tree.update(2, 3);
+ tree.update(2, -2);
+ assertEquals(0, tree.query(1));
+ assertEquals(6, tree.query(2));
+ assertEquals(6, tree.query(3));
+ }
+
+ @Test
+ void negativeValues() {
+ FenwickTree tree = new FenwickTree(3);
+ tree.update(0, -4);
+ tree.update(1, -6);
+ tree.update(2, 10);
+ assertEquals(-4, tree.query(0));
+ assertEquals(-10, tree.query(1));
+ assertEquals(0, tree.query(2));
+ }
+
+ @Test
+ void nonPowerOfTwoSize() {
+ int size = 13;
+ FenwickTree tree = new FenwickTree(size);
+ for (int i = 0; i < size; i++) {
+ tree.update(i, 1);
+ }
+ for (int i = 0; i < size; i++) {
+ assertEquals(i + 1, tree.query(i));
+ }
+ }
+
+ @Test
+ void matchesNaivePrefixSumOnRandomData() {
+ Random random = new Random(42);
+ int size = 100;
+ FenwickTree tree = new FenwickTree(size);
+ int[] naive = new int[size];
+
+ for (int step = 0; step < 500; step++) {
+ int index = random.nextInt(size);
+ int delta = random.nextInt(21) - 10;
+ tree.update(index, delta);
+ naive[index] += delta;
+ }
+
+ int running = 0;
+ for (int i = 0; i < size; i++) {
+ running += naive[i];
+ assertEquals(running, tree.query(i), "Mismatch at index " + i);
+ }
+ }
+
+ @Test
+ void zeroSizeTreeRejectsAnyIndex() {
+ FenwickTree tree = new FenwickTree(0);
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(0, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(0));
+ }
+
+ @Test
+ void negativeSizeThrows() {
+ assertThrows(IllegalArgumentException.class, () -> new FenwickTree(-1));
+ }
+
+ @Test
+ void outOfBoundsIndicesThrow() {
+ FenwickTree tree = new FenwickTree(5);
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(-1, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.update(5, 1));
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(-1));
+ assertThrows(IndexOutOfBoundsException.class, () -> tree.query(5));
+ }
+}
Loading