Skip to content

Commit fa96c70

Browse files
committed
feat: Implement array-based version of Segment Tree for fun
1 parent 240a2c2 commit fa96c70

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

src/main/java/dataStructures/heap/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ That said, in practice, the array-based implementation of a heap often provides
2727
former, in cache efficiency and memory locality. This is due to its contiguous memory layout. As such,
2828
the implementation shown here is a 0-indexed array-based heap.
2929

30+
#### Obtain index representing child nodes
31+
Suppose the parent node is captured at index *i* of the array (1-indexed).
32+
**1-indexed**: <br>
33+
Left Child: *i* x 2 <br>
34+
Right Child: *i* x 2 + 1 <br>
35+
36+
The 1-indexed calculation is intuitive. So, when dealing with 0-indexed representation (as in our implementation),
37+
one option is to convert 0-indexed to 1-indexed representation, do the above calculations, and revert. <br>
38+
(Note: Now, we assume parent node is captured at index *i* (0-indexed))
39+
40+
**0-indexed**: <br>
41+
Left Child: (*i* + 1) x 2 - 1 = *i* x 2 + 1 <br>
42+
Right Child: (*i* + 1) x 2 + 1 - 1 = *i* x 2 + 2 <br>
43+
3044
### Relevance of increaseKey and decreaseKey operations
3145

3246
The decision not to include explicit "decrease key" and "increase key" operations in the standard implementations of

src/main/java/dataStructures/segmentTree/SegmentTree.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public void update(int idx, int val) {
9595
if (idx > array.length) {
9696
return;
9797
}
98+
array[idx] = val;
9899
update(root, idx, val);
99100
}
100101

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package dataStructures.segmentTree.arrayRepresentation;
2+
3+
/**
4+
* Array-based implementation of a Segment Tree.
5+
*/
6+
public class SegmentTree {
7+
private int[] tree;
8+
private int[] array;
9+
10+
/**
11+
* Constructor.
12+
* @param nums
13+
*/
14+
public SegmentTree(int[] nums) {
15+
tree = new int[4 * nums.length]; // Need to account for up to 4n nodes.
16+
array = nums;
17+
buildTree(nums, 0, nums.length - 1, 0);
18+
}
19+
20+
/**
21+
* Builds the tree from the given array of numbers.
22+
* Unlikely before where we capture child nodes in the helper node class, here we capture position of child nodes
23+
* in the array-representation of the tree with an additional variable.
24+
* @param nums
25+
* @param start
26+
* @param end
27+
* @param idx tells us which index of the tree array we are at.
28+
*/
29+
private void buildTree(int[] nums, int start, int end, int idx) {
30+
// recall, each node is a position in the array
31+
// explicitly track which position in the array to fill with idx variable
32+
if (start == end) {
33+
tree[idx] = nums[start];
34+
return;
35+
}
36+
int mid = start + (end - start) / 2;
37+
int idxLeftChild = (idx + 1) * 2 - 1; // convert from 0-based to 1-based, do computation, then revert
38+
buildTree(nums, start, mid, idxLeftChild);
39+
int idxRightChild = (idx + 1) * 2 + 1 - 1; // convert from 0-based to 1-based, do computation, then revert
40+
buildTree(nums, mid + 1, end, idxRightChild);
41+
tree[idx] = tree[idxLeftChild] + tree[idxRightChild];
42+
}
43+
44+
/**
45+
* Queries the sum of all values in the specified range.
46+
* @param leftEnd
47+
* @param rightEnd
48+
* @return the sum.
49+
*/
50+
public int query(int leftEnd, int rightEnd) {
51+
return query(0, 0, array.length - 1, leftEnd, rightEnd);
52+
}
53+
54+
private int query(int nodeIdx, int startRange, int endRange, int leftEnd, int rightEnd) {
55+
// this is the case when:
56+
// start end
57+
// range query: ^ ^ --> so simply capture the sum at this node!
58+
if (leftEnd <= startRange && endRange <= rightEnd) {
59+
return tree[nodeIdx];
60+
}
61+
int rangeSum = 0;
62+
int mid = startRange + (endRange - startRange) / 2;
63+
// Consider the 3 possible kinds of range queries
64+
// start mid end
65+
// poss 1: ^ ^
66+
// poss 2: ^ ^
67+
// poss 3: ^ ^
68+
if (leftEnd <= mid) {
69+
int idxLeftChild = (nodeIdx + 1) * 2 - 1;
70+
rangeSum += query(idxLeftChild, startRange, mid, leftEnd, Math.min(rightEnd, mid));
71+
}
72+
if (mid + 1 <= rightEnd) {
73+
int idxRightChild = (nodeIdx + 1) * 2 + 1 - 1;
74+
rangeSum += query(idxRightChild, mid + 1, endRange, Math.max(leftEnd, mid + 1), rightEnd);
75+
}
76+
return rangeSum;
77+
}
78+
79+
/**
80+
* Updates the segment tree based on updates to the array at the specified index with the specified value.
81+
* @param idx
82+
* @param val
83+
*/
84+
public void update(int idx, int val) {
85+
if (idx > array.length) {
86+
return;
87+
}
88+
array[idx] = val;
89+
update(0, 0, array.length - 1, idx, val);
90+
}
91+
92+
private void update(int nodeIdx, int startRange, int endRange, int idx, int val) {
93+
if (startRange == endRange) {
94+
tree[nodeIdx] = val;
95+
return;
96+
}
97+
int mid = startRange + (endRange - startRange) / 2;
98+
if (idx <= mid) {
99+
update(nodeIdx * 2 + 1, startRange, mid, idx, val);
100+
} else {
101+
update(nodeIdx * 2 + 2, mid + 1, endRange, idx, val);
102+
}
103+
tree[nodeIdx] = tree[nodeIdx * 2 + 1] + tree[nodeIdx * 2 + 2];
104+
}
105+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dataStructures.segmentTree.arrayRepresentation;
2+
import static org.junit.Assert.assertEquals;
3+
4+
import org.junit.Test;
5+
6+
/**
7+
* This file is essentially duplicated from the parent.
8+
*/
9+
public class SegmentTreeTest {
10+
@Test
11+
public void construct_shouldConstructSegmentTree() {
12+
int[] arr1 = new int[] {7, 77, 37, 67, 33, 73, 13, 2, 7, 17, 87, 53};
13+
SegmentTree tree1 = new SegmentTree(arr1);
14+
assertEquals(arr1[1] + arr1[2] + arr1[3], tree1.query(1, 3));
15+
assertEquals(arr1[4] + arr1[5] + arr1[6] + arr1[7], tree1.query(4, 7));
16+
int sum1 = 0;
17+
for (int i = 0; i < arr1.length; i++) {
18+
sum1 += arr1[i];
19+
}
20+
assertEquals(sum1, tree1.query(0, arr1.length - 1));
21+
22+
23+
int[] arr2 = new int[] {7, -77, 37, 67, -33, 0, 73, -13, 2, -7, 17, 0, -87, 53, 0}; // some negatives and 0s
24+
SegmentTree tree2 = new SegmentTree(arr1);
25+
assertEquals(arr1[1] + arr1[2] + arr1[3], tree2.query(1, 3));
26+
assertEquals(arr1[4] + arr1[5] + arr1[6] + arr1[7], tree2.query(4, 7));
27+
int sum2 = 0;
28+
for (int i = 0; i < arr1.length; i++) {
29+
sum2 += arr1[i];
30+
}
31+
assertEquals(sum2, tree2.query(0, arr1.length - 1));
32+
}
33+
34+
@Test
35+
public void update_shouldUpdateSegmentTree() {
36+
int[] arr = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
37+
SegmentTree tree = new SegmentTree(arr);
38+
assertEquals(55, tree.query(0, 10));
39+
tree.update(5, 55);
40+
assertEquals(105, tree.query(0, 10));
41+
}
42+
}

0 commit comments

Comments
 (0)