|
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