diff --git a/Data-Structures/Tree/FenwickTree.js b/Data-Structures/Tree/FenwickTree.js new file mode 100644 index 0000000000..66054ffabe --- /dev/null +++ b/Data-Structures/Tree/FenwickTree.js @@ -0,0 +1,56 @@ +/** + * Time Complexity: + * Update: O(log n) + * Query: O(log n) + * Range Query: O(log n) + * + * Space Complexity: O(n) + * + */ + +export default class FenwickTree { + constructor(size) { + this.size = size + this.tree = new Array(size + 1).fill(0) + } + + static fromArray(arr) { + const ft = new FenwickTree(arr.length) + for (let i = 0; i < arr.length; i++) { + ft.update(i, arr[i]) + } + return ft + } + + update(index, delta) { + let i = index + 1 + while (i <= this.size) { + this.tree[i] += delta + i += i & -i // Adding the lowest set bit + } + } + + query(index) { + let i = index + 1 + let sum = 0 + while (i > 0) { + sum += this.tree[i] + i -= i & -i // Removing the lowest set bit + } + return sum + } + + rangeQuery(left, right) { + if (left === 0) return this.query(right) + return this.query(right) - this.query(left - 1) + } + + get(index) { + return this.rangeQuery(index, index) + } + + set(index, value) { + const current = this.get(index) + this.update(index, value - current) + } +} diff --git a/Data-Structures/Tree/test/FenwickTree.test.js b/Data-Structures/Tree/test/FenwickTree.test.js new file mode 100644 index 0000000000..c6c9c1c811 --- /dev/null +++ b/Data-Structures/Tree/test/FenwickTree.test.js @@ -0,0 +1,93 @@ +import FenwickTree from '../FenwickTree' + +describe('Fenwick Tree', () => { + describe('Basic Operations', () => { + test('should handle point updates and prefix queries', () => { + const ft = new FenwickTree(5) + ft.update(0, 1) + ft.update(1, 2) + ft.update(2, 3) + ft.update(3, 4) + ft.update(4, 5) + + expect(ft.query(0)).toBe(1) + expect(ft.query(1)).toBe(3) + expect(ft.query(2)).toBe(6) + expect(ft.query(3)).toBe(10) + expect(ft.query(4)).toBe(15) + }) + + test('should handle range queries', () => { + const ft = new FenwickTree(5) + ft.update(0, 10) + ft.update(1, 20) + ft.update(2, 30) + ft.update(3, 40) + ft.update(4, 50) + + expect(ft.rangeQuery(1, 3)).toBe(90) + expect(ft.rangeQuery(0, 2)).toBe(60) + expect(ft.rangeQuery(2, 4)).toBe(120) + }) + }) + + describe('Edge Cases', () => { + test('should handle empty tree', () => { + const ft = new FenwickTree(3) + expect(ft.query(0)).toBe(0) + expect(ft.query(1)).toBe(0) + expect(ft.query(2)).toBe(0) + }) + + test('should handle single element', () => { + const ft = new FenwickTree(1) + ft.update(0, 42) + expect(ft.query(0)).toBe(42) + expect(ft.rangeQuery(0, 0)).toBe(42) + }) + + test('should handle negative values', () => { + const ft = new FenwickTree(3) + ft.update(0, 5) + ft.update(1, -2) + ft.update(2, 3) + + expect(ft.rangeQuery(0, 2)).toBe(6) + expect(ft.rangeQuery(0, 1)).toBe(3) + }) + }) + + describe('Get and Set', () => { + test('should get value at specific index', () => { + const ft = new FenwickTree(3) + ft.update(0, 5) + ft.update(1, 10) + ft.update(2, 15) + + expect(ft.get(0)).toBe(5) + expect(ft.get(1)).toBe(10) + expect(ft.get(2)).toBe(15) + }) + + test('should set value at specific index', () => { + const ft = new FenwickTree(3) + ft.update(0, 5) + ft.update(1, 10) + ft.update(2, 15) + + ft.set(1, 20) + expect(ft.get(1)).toBe(20) + expect(ft.rangeQuery(0, 2)).toBe(40) + }) + }) + + describe('Static Methods', () => { + test('should build from array', () => { + const arr = [1, 2, 3, 4, 5] + const ft = FenwickTree.fromArray(arr) + + expect(ft.query(4)).toBe(15) + expect(ft.rangeQuery(1, 3)).toBe(9) + }) + }) +})