3.3.3. Segment Tree
A Segment Tree is a data structure that allows answering range queries over an array effectively, while still being flexible enough to allow modifying the array.
Features of Segment trees:
Leaf Nodes are the elements of the input array. This is like tournament tree described at Section 3.3.4.
Each internal node represents some merging of the leaf nodes. The merging may be different for different problems. If we want to query minimum value of a range, merging is minimum of leaves under a node.
3.3.3.1. Subarray Minimum Query
There are many ways to implement a segement tree. The following is an example to build a minimum segement tree using vector as the underlying data strucutre.
1class segment_tree_min {
2public:
3 explicit segment_tree_min(const vector<int> &values) {
4 n = values.size();
5 data = vector<int>(2 * n);
6 copy(values.begin(), values.end(), &data[0] + n);
7 // fill segment tree inner nodes
8 for (int idx = n - 1; idx > 0; idx--)
9 data[idx] = min(data[idx * 2], data[idx * 2 + 1]);
10 }
11
12 void update(int idx, int value) {
13 // update original data
14 idx += n;
15 data[idx] = value;
16 // populate from bottom to top
17 while (idx > 1) {
18 idx >>= 1;
19 data[idx] = min(data[2 * idx], data[2 * idx + 1]);
20 }
21 }
22
23 int query(int left, int right) { // interval [left, right)
24 int ret = numeric_limits<int>::max();
25 left += n, right += n;
26 while (left < right) {
27 if (left & 1) // odd index
28 ret = min(ret, data[left++]);
29 if (right & 1) // odd index
30 ret = min(ret, data[--right]);
31 left >>= 1, right >>= 1;
32 }
33 return ret;
34 }
35
36private:
37 int n; // size of the original data
38 vector<int> data; // base data structure is a vector
39};
In the above implementation, we deliberately don’t use the first item in the underlying vector. So the node at array[index] has its children at array[index*2] and array[index*2+1]. The node at array[index] has its parent at array[index/2]. [1]
If the original array is {5,2,3,1,4}. First we copy all the array to the second part of the underlying vector. Then we populate the inner nodes.
Finally we will get a segment tree as a full binary tree [2].
[2]
/ \
[2] [4]
/ \ / \
data [2] [3] [4] [5]
original index: [0][1][2][3]
original array: [2][3][4][5]
index: [x][1][2][3][4][5][6][7] (tree index)
value: [x][2][2][4][2][3][4][5] (tree)
input 0,3 => 4, 7
[2]
/ /
[2]---/---
/ / \
[2] [2] [5]
/ / \ / \
data [2] [3] [4] [5] [6]
To make the tree easier to see, we can adjust it like:
The tricky part is the query function, where we need to check if the upper level’s parent node has the left-most or right-most node as a child. If not, we need to add the left-most or right-most node into the calculation.
Footnotes
3.3.3.2. Buddy System Bitmap
1class segment_tree_bitmap {
2 long long tree_size; // even number always
3 vector<bool> flat_stree;
4
5 void build(long long n) {
6 flat_stree.resize(2 * n); // all false when initialization
7 tree_size = 2 * n;
8 }
9
10 void update(long long tree_index, bool b) { // bottom up, TC: O(LogN)
11 flat_stree[tree_index] = b;
12 long long i = tree_index;
13 while (i > 0) {
14 if (i & 1) {
15 flat_stree[i >> 1] = flat_stree[i] & flat_stree[i - 1];
16 } else {
17 flat_stree[i >> 1] = flat_stree[i] & flat_stree[i + 1];
18 }
19 i /= 2;
20 }
21 }
22
23 public:
24 explicit segment_tree_bitmap(long long n) { build(n); }
25
26 long long allocate() { // top down, TC: O(LogN)
27 if (flat_stree[1]) return -1; // capacity is full
28 long long i = 1, j = 2; // i - parent, j - left child
29 // 1. find tree_index to update
30 while (j < tree_size) {
31 if (i & 1) {
32 if (flat_stree[j]) j++;
33 } else {
34 if (j + 1 < tree_size and !flat_stree[j + 1]) j++;
35 }
36 i = j, j = i << 1;
37 }
38 long long tree_index = i;
39 // 2. update
40 update(tree_index, true);
41 return tree_index - tree_size / 2;
42 }
43
44 bool release(long long n) { // TC: O(LogN)
45 long long tree_index = n + tree_size / 2;
46 if (!flat_stree[tree_index]) return false;
47 update(tree_index, false);
48 return true;
49 }
50};