Skip to content

Commit ac2b726

Browse files
committed
docs: Update README for segment tree
1 parent fa96c70 commit ac2b726

File tree

1 file changed

+87
-1
lines changed
  • src/main/java/dataStructures/segmentTree

1 file changed

+87
-1
lines changed
Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,87 @@
1-
# Segment Tree
1+
# Segment Tree
2+
3+
## Background
4+
Segment Trees are primarily used to solve problems that require answers to queries on intervals of an array
5+
with the possibility of modifying the array elements.
6+
These queries could be finding the sum, minimum, or maximum in a subarray, or similar aggregated results.
7+
8+
### Structure
9+
(Note: See below for a brief description of the array-based implementation of a segment tree)
10+
11+
A Segment Tree for an array of size *n* is a binary tree that stores information about segments of the array.
12+
Each node in the tree represents an interval of the array, with the root representing the entire array.
13+
The structure satisfies the following properties:
14+
1. Leaf Nodes: Each leaf node represents a single element of the array.
15+
2. Internal Nodes: Each internal node represents the sum of the values of its children
16+
(which captures the segment of the array). Summing up, this node captures the whole segment.
17+
3. Height: The height of the Segment Tree is O(log *n*), making queries and updates efficient.
18+
19+
## Complexity Analysis
20+
**Time**: O(log(n)) in general for query and update operations,
21+
except construction which takes O(nlogn)
22+
23+
**Space**: O(n), note for an array-based implementation, the array created should have size 4n (explained later)
24+
25+
where n is the number of elements in the array.
26+
27+
## Operations
28+
### Construction
29+
The construction of a Segment Tree starts with the root node representing the entire array and
30+
recursively dividing the array into two halves until each segment is reduced to a single element.
31+
This process is a divide-and-conquer strategy:
32+
1. Base Case: If the current segment of the array is reduced to a single element, create a leaf node.
33+
2. Recursive Case: Otherwise, split the array segment into two halves, construct the left and right children,
34+
and then merge their results to build the parent node.
35+
36+
This takes O(nlogn). logn in depth, and will visit each leaf node (number of leaf nodes could be roughly 2n) once.
37+
38+
### Querying
39+
To query an interval, say to find the sum of elements in the interval (L, R),
40+
the tree is traversed starting from the root:
41+
1. If the current node's segment is completely within (L, R), its value is part of the answer.
42+
2. If the current node's segment is completely outside (L, R), it is ignored.
43+
3. If the current node's segment partially overlaps with (L, R), the query is recursively applied to its children.
44+
45+
This approach ensures that each level of the tree is visited only once, time complexity of O(logn).
46+
47+
### Updating
48+
Updating an element involves changing the value of a leaf node and then propagating this change up to the root
49+
to ensure the tree reflects the updated array.
50+
This is done by traversing the path from the leaf node to the root
51+
and updating each node along this path (update parent to the sum of its children).
52+
53+
This can be done in O(logn).
54+
55+
## Array-based Segment Tree
56+
The array-based implementation of a Segment Tree is an efficient way to represent the tree in memory, especially
57+
since a Segment Tree is a complete binary tree.
58+
This method utilizes a simple array where each element of the array corresponds to a node in the tree,
59+
including both leaves and internal nodes.
60+
61+
### Why 4n space
62+
The size of the array needed to represent a Segment Tree for an array of size *n* is 2*2^ceil(log2(*n*)) - 1.
63+
We do 2^(ceil(log2(*n*))) because *n* might not be a perfect power of 2,
64+
**so we expand the array size to the next power of 2**.
65+
This adjustment ensures that each level of the tree is fully filled except possibly for the last level,
66+
which is filled from left to right.
67+
68+
**BUT**, 2^(ceil(log2(*n*))) seems overly-complex. To ensure we have sufficient space, we can just consider 2*n
69+
because 2*n >= 2^(ceil(log2(*n*))).
70+
Now, these 2n nodes can be thought of as the 'leaf' nodes (or more precisely, an upper-bound). To account for the
71+
intermediate nodes, we use the property that for a complete binary that is fully filled, the number of leaf nodes
72+
= number of intermediate nodes (recall: sum i -> 0 to n-1 of 2^i = 2^n). So we create an array of size 2n * 2 = 4n to
73+
guarantee we can house the entire segment tree.
74+
75+
### Obtain index representing child nodes
76+
Suppose the parent node is captured at index *i* of the array (1-indexed).
77+
**1-indexed**: <br>
78+
Left Child: *i* x 2 <br>
79+
Right Child: *i* x 2 + 1 <br>
80+
81+
The 1-indexed calculation is intuitive. So, when dealing with 0-indexed representation (as in our implementation),
82+
one option is to convert 0-indexed to 1-indexed representation, do the above calculations, and revert. <br>
83+
(Note: Now, we assume parent node is captured at index *i* (0-indexed))
84+
85+
**0-indexed**: <br>
86+
Left Child: (*i* + 1) x 2 - 1 = *i* x 2 + 1 <br>
87+
Right Child: (*i* + 1) x 2 + 1 - 1 = *i* x 2 + 2 <br>

0 commit comments

Comments
 (0)