Skip to content

Commit 434a564

Browse files
committedJun 5, 2018
Refactor segment tree implementation.
1 parent 5784a4a commit 434a564

File tree

4 files changed

+233
-251
lines changed

4 files changed

+233
-251
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ the data.
3232
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
3333
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
3434
* [Red-Black Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/red-black-tree)
35+
* [Segment Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/segment-tree) - with min/max/sum range queries examples
3536
* [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph) (both directed and undirected)
3637
* [Disjoint Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/disjoint-set)
3738

‎src/data-structures/tree/segment-tree/README.md

+27-30
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,23 @@
11
# Segment Tree
22

3-
A segment tree is a data structure designed to perform
4-
certain array operations efficiently - especially those
5-
involving range queries.
6-
7-
A common application is the [Range Minimum Query](https://en.wikipedia.org/wiki/Range_minimum_query) (RMQ) problem,
8-
where we are given an array of numbers and need to
9-
support operations of updating values of the array and
10-
finding the minimum of a contiguous subarray.
11-
A segment tree implementation for the RMQ problem
12-
takes `O(n)` to initialize, and `O(log n)` per query or
13-
update. The "minimum" operation can be replaced by any
14-
array operation (such as sum).
15-
16-
A segment tree is a binary tree with contiguous
17-
sub-arrays as nodes. The root of the tree represents the
3+
In computer science, a segment tree also known as a statistic tree
4+
is a tree data structure used for storing information about intervals,
5+
or segments. It allows querying which of the stored segments contain
6+
a given point. It is, in principle, a static structure; that is,
7+
it's a structure that cannot be modified once it's built. A similar
8+
data structure is the interval tree.
9+
10+
A segment tree is a binary tree. The root of the tree represents the
1811
whole array. The two children of the root represent the
1912
first and second halves of the array. Similarly, the
2013
children of each node corresponds to the two halves of
21-
the array corresponding to the node. If the array has
22-
size `n`, we can prove that the segment tree has size at
23-
most `4n`. Each node stores the minimum of its
24-
corresponding sub-array.
25-
26-
In the implementation, we do not explicitly store this
27-
tree structure, but represent it using a `4n` sized array.
28-
The left child of node i is `2i+1` and the right child
29-
is `2i+2`. This is a standard way to represent segment
30-
trees, and lends itself to an efficient implementation.
14+
the array corresponding to the node.
3115

3216
We build the tree bottom up, with the value of each node
33-
being the minimum of its children's values. This will
34-
take time `O(n)`, with one operation for each node. Updates
35-
are also done bottom up, with values being recomputed
36-
starting from the leaf, and up to the root. The number
17+
being the "minimum" (or any other function) of its children's values. This will
18+
take `O(n log n)` time. The number
3719
of operations done is the height of the tree, which
38-
is `O(log n)`. To answer queries, each node splits the
20+
is `O(log n)`. To do range queries, each node splits the
3921
query into two parts, one sub-query for each child.
4022
If a query contains the whole subarray of a node, we
4123
can use the precomputed value at the node. Using this
@@ -44,6 +26,21 @@ operations are done.
4426

4527
![Segment Tree](https://www.geeksforgeeks.org/wp-content/uploads/segment-tree1.png)
4628

29+
## Application
30+
31+
A segment tree is a data structure designed to perform
32+
certain array operations efficiently - especially those
33+
involving range queries.
34+
35+
Applications of the segment tree are in the areas of computational geometry,
36+
and geographic information systems.
37+
38+
Current implementation of Segment Tree implies that you may
39+
pass any binary (with two input params) function to it and
40+
thus you're able to do range query for variety of functions.
41+
In tests you may fins examples of doing `min`, `max` and `sam` range
42+
queries on SegmentTree.
43+
4744
## References
4845

4946
- [Wikipedia](https://en.wikipedia.org/wiki/Segment_tree)
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,168 @@
1-
/**
2-
* Segment Tree implementation for Range Query data structure
3-
* Tracks a array of numbers. 0 indexed
4-
* operation is a binary function (eg sum, min) - needs to be associative
5-
* identity is the identity of the operation
6-
* i.e, operation(x, identity) = x (eg 0 for sum, Infinity for min)
7-
* Supports methods
8-
* update(index, val) - set value of index
9-
* query(l, r) - finds operation(values in range [l, r]) (both inclusive)
10-
*
11-
* As is customary, we store the tree implicitly with i being the parent of 2i, 2i+1.
12-
*/
1+
import isPowerOfTwo from '../../../algorithms/math/is-power-of-two/isPowerOfTwo';
132

143
export default class SegmentTree {
154
/**
16-
* array initialises the numbers
17-
* @param {number[]} array
5+
* @param {number[]} inputArray
6+
* @param {function} operation - binary function (i.e. sum, min)
7+
* @param {number} operationFallback - operation fallback value (i.e. 0 for sum, Infinity for min)
188
*/
19-
constructor(array, operation, identity) {
20-
this.n = array.length;
21-
this.array = array;
22-
this.tree = new Array(4 * this.n);
23-
9+
constructor(inputArray, operation, operationFallback) {
10+
this.inputArray = inputArray;
2411
this.operation = operation;
25-
this.identity = identity;
26-
27-
// use Range Min Query by default
28-
if (this.operation === undefined) {
29-
this.operation = Math.min;
30-
this.identity = Infinity;
31-
}
12+
this.operationFallback = operationFallback;
3213

14+
// Init array representation of segment tree.
15+
this.segmentTree = this.initSegmentTree(this.inputArray);
3316

34-
this.build();
17+
this.buildSegmentTree();
3518
}
3619

3720
/**
38-
* Stub for recursive call
21+
* @param {number[]} inputArray
22+
* @return {number[]}
3923
*/
40-
build() {
41-
this.buildRec(1, 0, this.n - 1);
42-
}
24+
initSegmentTree(inputArray) {
25+
let segmentTreeArrayLength;
26+
const inputArrayLength = inputArray.length;
4327

44-
/**
45-
* Left child index
46-
* @param {number} root
47-
*/
48-
left(root) {
49-
return 2 * root;
28+
if (isPowerOfTwo(inputArrayLength)) {
29+
// If original array length is a power of two.
30+
segmentTreeArrayLength = (2 * inputArrayLength) - 1;
31+
} else {
32+
// If original array length is not a power of two then we need to find
33+
// next number that is a power of two and use it to calculate
34+
// tree array size. This is happens because we need to fill empty children
35+
// in perfect binary tree with nulls.And those nulls need extra space.
36+
const currentPower = Math.floor(Math.log2(inputArrayLength));
37+
const nextPower = currentPower + 1;
38+
const nextPowerOfTwoNumber = 2 ** nextPower;
39+
segmentTreeArrayLength = (2 * nextPowerOfTwoNumber) - 1;
40+
}
41+
42+
return new Array(segmentTreeArrayLength).fill(null);
5043
}
5144

5245
/**
53-
* Right child index
54-
* @param {number} root
46+
* Build segment tree.
5547
*/
56-
right(root) {
57-
return (2 * root) + 1;
48+
buildSegmentTree() {
49+
const leftIndex = 0;
50+
const rightIndex = this.inputArray.length - 1;
51+
const position = 0;
52+
this.buildTreeRecursively(leftIndex, rightIndex, position);
5853
}
5954

6055
/**
61-
* root is the index in the tree, [l,r] (inclusive) is the current array segment being built
62-
* @param {number} root
63-
* @param {number} l
64-
* @param {number} r
56+
* Build segment tree recursively.
57+
*
58+
* @param {number} leftInputIndex
59+
* @param {number} rightInputIndex
60+
* @param {number} position
6561
*/
66-
buildRec(root, l, r) {
67-
if (l === r) {
68-
this.tree[root] = this.array[l];
69-
} else {
70-
const mid = Math.floor((l + r) / 2);
71-
// build left and right nodes
72-
this.buildRec(this.left(root), l, mid);
73-
this.buildRec(this.right(root), mid + 1, r);
74-
this.tree[root] = this.operation(this.tree[this.left(root)], this.tree[this.right(root)]);
62+
buildTreeRecursively(leftInputIndex, rightInputIndex, position) {
63+
// If low input index and high input index are equal that would mean
64+
// the we have finished splitting and we are already came to the leaf
65+
// of the segment tree. We need to copy this leaf value from input
66+
// array to segment tree.
67+
if (leftInputIndex === rightInputIndex) {
68+
this.segmentTree[position] = this.inputArray[leftInputIndex];
69+
return;
7570
}
71+
72+
// Split input array on two halves and process them recursively.
73+
const middleIndex = Math.floor((leftInputIndex + rightInputIndex) / 2);
74+
// Process left half of the input array.
75+
this.buildTreeRecursively(leftInputIndex, middleIndex, this.getLeftChildIndex(position));
76+
// Process right half of the input array.
77+
this.buildTreeRecursively(middleIndex + 1, rightInputIndex, this.getRightChildIndex(position));
78+
79+
// Once every tree leaf is not empty we're able to build tree bottom up using
80+
// provided operation function.
81+
this.segmentTree[position] = this.operation(
82+
this.segmentTree[this.getLeftChildIndex(position)],
83+
this.segmentTree[this.getRightChildIndex(position)],
84+
);
7685
}
7786

7887
/**
79-
* Stub for recursive call
80-
* @param {number} lindex
81-
* @param {number} rindex
88+
* Do range query on segment tree in context of this.operation function.
89+
*
90+
* @param {number} queryLeftIndex
91+
* @param {number} queryRightIndex
92+
* @return {number}
8293
*/
83-
query(lindex, rindex) {
84-
return this.queryRec(1, lindex, rindex, 0, this.n - 1);
94+
rangeQuery(queryLeftIndex, queryRightIndex) {
95+
const leftIndex = 0;
96+
const rightIndex = this.inputArray.length - 1;
97+
const position = 0;
98+
99+
return this.rangeQueryRecursive(
100+
queryLeftIndex,
101+
queryRightIndex,
102+
leftIndex,
103+
rightIndex,
104+
position,
105+
);
85106
}
86107

87108
/**
88-
* [lindex, rindex] is the query region
89-
* [l,r] is the current region being processed
90-
* Guaranteed that [lindex,rindex] contained in [l,r]
91-
* @param {number} root
92-
* @param {number} lindex
93-
* @param {number} rindex
94-
* @param {number} l
95-
* @param {number} r
109+
* Do range query on segment tree recursively in context of this.operation function.
110+
*
111+
* @param {number} queryLeftIndex - left index of the query
112+
* @param {number} queryRightIndex - right index of the query
113+
* @param {number} leftIndex - left index of input array segment
114+
* @param {number} rightIndex - right index of input array segment
115+
* @param {number} position - root position in binary tree
116+
* @return {number}
96117
*/
97-
queryRec(root, lindex, rindex, l, r) {
98-
// console.log(root, lindex, rindex, l, r);
99-
if (lindex > rindex) {
100-
// happens when mid+1 > r - no segment
101-
return this.identity;
118+
rangeQueryRecursive(queryLeftIndex, queryRightIndex, leftIndex, rightIndex, position) {
119+
if (queryLeftIndex <= leftIndex && queryRightIndex >= rightIndex) {
120+
// Total overlap.
121+
return this.segmentTree[position];
102122
}
103-
if (l === lindex && r === rindex) {
104-
// query region matches current region - use tree value
105-
return this.tree[root];
123+
124+
if (queryLeftIndex > rightIndex || queryRightIndex < leftIndex) {
125+
// No overlap.
126+
return this.operationFallback;
106127
}
107-
const mid = Math.floor((l + r) / 2);
108-
// get left and right results and combine
109-
const leftResult = this.queryRec(this.left(root), lindex, Math.min(rindex, mid), l, mid);
110-
const rightResult = this.queryRec(
111-
this.right(root), Math.max(mid + 1, lindex), rindex,
112-
mid + 1, r,
128+
129+
// Partial overlap.
130+
const middleIndex = Math.floor((leftIndex + rightIndex) / 2);
131+
132+
const leftOperationResult = this.rangeQueryRecursive(
133+
queryLeftIndex,
134+
queryRightIndex,
135+
leftIndex,
136+
middleIndex,
137+
this.getLeftChildIndex(position),
113138
);
114-
return this.operation(leftResult, rightResult);
139+
140+
const rightOperationResult = this.rangeQueryRecursive(
141+
queryLeftIndex,
142+
queryRightIndex,
143+
middleIndex + 1,
144+
rightIndex,
145+
this.getRightChildIndex(position),
146+
);
147+
148+
return this.operation(leftOperationResult, rightOperationResult);
115149
}
116150

117151
/**
118-
* Set array[index] to value
119-
* @param {number} index
120-
* @param {number} value
152+
* Left child index.
153+
* @param {number} parentIndex
154+
* @return {number}
121155
*/
122-
update(index, value) {
123-
this.array[index] = value;
124-
this.updateRec(1, index, value, 0, this.n - 1);
156+
getLeftChildIndex(parentIndex) {
157+
return (2 * parentIndex) + 1;
125158
}
126159

127160
/**
128-
* @param {number} root
129-
* @param {number} index
130-
* @param {number} value
131-
* @param {number} l
132-
* @param {number} r
161+
* Right child index.
162+
* @param {number} parentIndex
163+
* @return {number}
133164
*/
134-
updateRec(root, index, value, l, r) {
135-
if (l === r) {
136-
// we are at tree node containing array[index]
137-
this.tree[root] = value;
138-
} else {
139-
const mid = Math.floor((l + r) / 2);
140-
// update whichever child index is in, update this.tree[root]
141-
if (index <= mid) {
142-
this.updateRec(this.left(root), index, value, l, mid);
143-
} else {
144-
this.updateRec(this.right(root), index, value, mid + 1, r);
145-
}
146-
this.tree[root] = this.operation(this.tree[this.left(root)], this.tree[this.right(root)]);
147-
}
165+
getRightChildIndex(parentIndex) {
166+
return (2 * parentIndex) + 2;
148167
}
149168
}

0 commit comments

Comments
 (0)
Please sign in to comment.