0% completed
We have seen the introduction to Segment Trees, how they work, and how they are stored. Now, we will learn about the essential operations on a Segment Tree. This lesson will cover the following concepts:
By the end of this lesson, you will have a comprehensive understanding of how to work with Segment Trees, perform range queries, and update elements efficiently.
To construct the segment tree, we will use a recursive approach to build the tree, starting from the leaves and combining results up to the root. This approach ensures that both querying and updating operations are performed efficiently, making it ideal for large datasets.
Our approach involves dividing the array into smaller segments until each segment contains a single element. Then, we combine these segments to form the tree. This method leverages the divide and conquer strategy, making the problem more manageable and the solution more efficient.
Initialization:
arr
of size n
.tree
of size 4 * n
to store the Segment Tree. The size 4 * n
ensures there is enough space for all nodes, even in the worst case.Build Tree:
start == end
):
tree[node] = arr[start]
mid = (start + end) / 2
.buildTree(arr, 2 * node, start, mid, tree)
buildTree(arr, 2 * node + 1, mid + 1, end, tree)
tree[node] = tree[2 * node] + tree[2 * node + 1]
Implementation Details:
1
.i
is at index 2i
.i
is at index 2i + 1
.Using the array [2, 4, 6, 8, 10, 12]
, let's walk through the construction of the Segment Tree:
Initialization:
[2, 4, 6, 8, 10, 12]
4 * 6 = 24
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
Build Tree:
node 1
: Segment [0, 5]
mid = 2
node 2
, Segment [0, 2]
mid = 1
node 4
, Segment [0, 1]
mid = 0
node 8
, Segment [0, 0]
tree[8] = arr[0] = 2
node 9
, Segment [1, 1]
tree[9] = arr[1] = 4
tree[4] = tree[8] + tree[9] = 2 + 4 = 6
node 5
, Segment [2, 2]
tree[5] = arr[2] = 6
tree[2] = tree[4] + tree[5] = 6 + 6 = 12
node 3
, Segment [3, 5]
mid = 4
node 6
, Segment [3, 4]
mid = 3
node 12
, Segment [3, 3]
tree[12] = arr[3] = 8
node 13
, Segment [4, 4]
tree[13] = arr[4] = 10
tree[6] = tree[12] + tree[13] = 8 + 10 = 18
node 7
, Segment [5, 5]
tree[7] = arr[5] = 12
tree[3] = tree[6] + tree[7] = 18 + 12 = 30
tree[1] = tree[2] + tree[3] = 12 + 30 = 42
Resulting tree:
[null, 42, 12, 30, 6, 6, 18, 12, 2, 4, null, null, 8, 10, null, null, null, null, null, null, null, null, null, null]
Time Complexity:
Space Complexity:
Once the Segment Tree is built, it can be used to perform efficient range queries. A common use case for a Segment Tree is querying the sum of elements in a given range. The structure of the Segment Tree allows us to break down the query into smaller segments, quickly summing the required values. This ensures that range queries can be performed in O(\log n) time, making it highly efficient compared to a naive approach that takes O(n) time.
Construct the Segment Tree:
Initialization:
[L, R]
.Recursive Query Function:
[L, R]
is completely outside the segment range [start, end]
of the current node, return 0
.[start, end]
is completely within the query range [L, R]
, return the value stored in the current node.[start, end]
and the query range [L, R]
, split the range further:
mid = (start + end) / 2
.leftSum = query(2 * node, start, mid, L, R, tree)
.rightSum = query(2 * node + 1, mid + 1, end, L, R, tree)
.result = leftSum + rightSum
.Combine Results:
[L, R]
.Using the input [2, 4, 6, 8, 10, 12]
.
Using the Segment Tree from the previous section [0, 42, 12, 30, 6, 6, 18, 12, 2, 4, 0, 0, 8, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
, let's query the sum of elements in the range [1, 4]
:
Initialization:
[1, 4]
0
Recursive Query:
1
(segment [0, 5]
):
[1, 4]
partially overlaps with the segment [0, 5]
.mid = 2
2
(segment [0, 2]
):
[1, 4]
partially overlaps with the segment [0, 2]
.mid = 1
4
(segment [0, 1]
):
[1, 4]
partially overlaps with the segment [0, 1]
.mid = 0
8
(segment [0, 0]
):
[1, 4]
does not overlap with the segment [0, 0]
.0
9
(segment [1, 1]
):
[1, 4]
completely overlaps with the segment [1, 1]
.4
0 + 4 = 4
4
to node 4
5
(segment [2, 2]
):
[1, 4]
completely overlaps with the segment [2, 2]
.6
4 + 6 = 10
10
to node 2
3
(segment [3, 5]
):
[1, 4]
partially overlaps with the segment [3, 5]
.mid = 4
6
(segment [3, 4]
):
[1, 4]
completely overlaps with the segment [3, 4]
.18
7
(segment [5, 5]
):
[1, 4]
does not overlap with the segment [5, 5]
.0
18 + 0 = 18
18
to node 3
10 + 18 = 28
Result:
[1, 4]
is 28
.Time Complexity:
Space Complexity:
Updating a Segment Tree involves changing the value of an element in the original array and then updating the corresponding nodes in the tree to reflect this change. This ensures that the Segment Tree remains accurate for subsequent queries. The update operation is efficient, taking O(\log n) time, as it only affects the nodes along the path from the updated element to the root.
Initialization:
idx
of the element to be updated in the array.val
to be updated at this index.Recursive Update Function:
[start, end]
corresponds to the single element to be updated (start == end
):
tree[node] = val
.mid = (start + end) / 2
.idx
lies within the left segment [start, mid]
, recursively update the left child: update(2 * node, start, mid, idx, val, tree)
.idx
lies within the right segment [mid + 1, end]
, recursively update the right child: update(2 * node + 1, mid + 1, end, idx, val, tree)
.tree[node] = tree[2 * node] + tree[2 * node + 1]
.Using the input [2, 4, 6, 8, 10, 12]
. let's update the element at index 2
to 7
:
Using the Segment Tree from the previous section [0, 42, 12, 30, 6, 6, 18, 12, 2, 4, 0, 0, 8, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].
Initialization:
2
7
Recursive Update:
Start with the root node 1
(segment [0, 5]
):
2
lies within the segment [0, 5]
, we need to continue updating the children.mid = (start + end) / 2 = (0 + 5) / 2 = 2
.Update Left Child:
Move to the left child node 2
(segment [0, 2]
):
2
lies within the segment [0, 2]
, so we need to update this segment further.mid = (start + end) / 2 = (0 + 2) / 2 = 1
.Update Right Child of Node 2:
5
(segment [2, 2]
):
start
equals end
and it matches the update index 2
.tree[5] = 7
.Combine Results:
2
and update its value to reflect the change:tree[2] = tree[4] + tree[5] = 6 + 7 = 13
.Update Root Node:
1
and update its value to reflect the change:tree[1] = tree[2] + tree[3] = 13 + 30 = 43
.Resulting tree:
[0, 43, 13, 30, 6, 7, 18, 12, 2, 4, 0, 0, 8, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Time Complexity:
Space Complexity:
Now, let's start solving the problem on segment tree.
.....
.....
.....