From e3d62225ff0c2fd8d4d0083d6526b686b81efc8c Mon Sep 17 00:00:00 2001 From: Rohan Gangawane <147078292+Rohan45create@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:22:10 +0000 Subject: [PATCH 1/2] Add Fenwick tree implementation and tests --- .../datastructures/trees/FenwickTree.java | 27 ++-- .../datastructures/trees/FenwickTree.java | 115 ++++++++++++++++++ 2 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java diff --git a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java index 5378a01f6642..be8a92e02150 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 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); + } + } } 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 000000000000..47ef568e5d1f --- /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)); + } +} From 0ebac5f26019ec0d0d23447d2f51dc2d07bb7f91 Mon Sep 17 00:00:00 2001 From: Rohan Gangawane <147078292+Rohan45create@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:53:27 +0000 Subject: [PATCH 2/2] Fix FenwickTree test filename and add documentation link --- .../datastructures/trees/FenwickTree.java | 6 +- ...{FenwickTree.java => FenwickTreeTest.java} | 0 "ts\"" | 179 ++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) rename src/test/java/com/thealgorithms/datastructures/trees/{FenwickTree.java => FenwickTreeTest.java} (100%) create mode 100644 "ts\"" diff --git a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java index be8a92e02150..e9c355baa4eb 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java @@ -1,6 +1,10 @@ package com.thealgorithms.datastructures.trees; -/** Fenwick Tree for point updates and prefix-sum queries in O(log n). */ +/** + * Fenwick Tree for point updates and prefix-sum queries in O(log n). + * + * @see Fenwick Tree + */ public class FenwickTree { private final int n; diff --git a/src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java b/src/test/java/com/thealgorithms/datastructures/trees/FenwickTreeTest.java similarity index 100% rename from src/test/java/com/thealgorithms/datastructures/trees/FenwickTree.java rename to src/test/java/com/thealgorithms/datastructures/trees/FenwickTreeTest.java diff --git "a/ts\"" "b/ts\"" new file mode 100644 index 000000000000..daddada971db --- /dev/null +++ "b/ts\"" @@ -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)); ++ } ++}