Skip to content

Commit

Permalink
Refactor segment tree implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
trekhleb committed Jun 5, 2018
1 parent 5784a4a commit 434a564
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 251 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ the data.
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
* [Red-Black Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/red-black-tree)
* [Segment Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/segment-tree) - with min/max/sum range queries examples
* [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph) (both directed and undirected)
* [Disjoint Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/disjoint-set)

Expand Down
57 changes: 27 additions & 30 deletions src/data-structures/tree/segment-tree/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
# Segment Tree

A segment tree is a data structure designed to perform
certain array operations efficiently - especially those
involving range queries.

A common application is the [Range Minimum Query](https://en.wikipedia.org/wiki/Range_minimum_query) (RMQ) problem,
where we are given an array of numbers and need to
support operations of updating values of the array and
finding the minimum of a contiguous subarray.
A segment tree implementation for the RMQ problem
takes `O(n)` to initialize, and `O(log n)` per query or
update. The "minimum" operation can be replaced by any
array operation (such as sum).

A segment tree is a binary tree with contiguous
sub-arrays as nodes. The root of the tree represents the
In computer science, a segment tree also known as a statistic tree
is a tree data structure used for storing information about intervals,
or segments. It allows querying which of the stored segments contain
a given point. It is, in principle, a static structure; that is,
it's a structure that cannot be modified once it's built. A similar
data structure is the interval tree.

A segment tree is a binary tree. The root of the tree represents the
whole array. The two children of the root represent the
first and second halves of the array. Similarly, the
children of each node corresponds to the two halves of
the array corresponding to the node. If the array has
size `n`, we can prove that the segment tree has size at
most `4n`. Each node stores the minimum of its
corresponding sub-array.

In the implementation, we do not explicitly store this
tree structure, but represent it using a `4n` sized array.
The left child of node i is `2i+1` and the right child
is `2i+2`. This is a standard way to represent segment
trees, and lends itself to an efficient implementation.
the array corresponding to the node.

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

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

## Application

A segment tree is a data structure designed to perform
certain array operations efficiently - especially those
involving range queries.

Applications of the segment tree are in the areas of computational geometry,
and geographic information systems.

Current implementation of Segment Tree implies that you may
pass any binary (with two input params) function to it and
thus you're able to do range query for variety of functions.
In tests you may fins examples of doing `min`, `max` and `sam` range
queries on SegmentTree.

## References

- [Wikipedia](https://en.wikipedia.org/wiki/Segment_tree)
Expand Down
233 changes: 126 additions & 107 deletions src/data-structures/tree/segment-tree/SegmentTree.js
Original file line number Diff line number Diff line change
@@ -1,149 +1,168 @@
/**
* Segment Tree implementation for Range Query data structure
* Tracks a array of numbers. 0 indexed
* operation is a binary function (eg sum, min) - needs to be associative
* identity is the identity of the operation
* i.e, operation(x, identity) = x (eg 0 for sum, Infinity for min)
* Supports methods
* update(index, val) - set value of index
* query(l, r) - finds operation(values in range [l, r]) (both inclusive)
*
* As is customary, we store the tree implicitly with i being the parent of 2i, 2i+1.
*/
import isPowerOfTwo from '../../../algorithms/math/is-power-of-two/isPowerOfTwo';

export default class SegmentTree {
/**
* array initialises the numbers
* @param {number[]} array
* @param {number[]} inputArray
* @param {function} operation - binary function (i.e. sum, min)
* @param {number} operationFallback - operation fallback value (i.e. 0 for sum, Infinity for min)
*/
constructor(array, operation, identity) {
this.n = array.length;
this.array = array;
this.tree = new Array(4 * this.n);

constructor(inputArray, operation, operationFallback) {
this.inputArray = inputArray;
this.operation = operation;
this.identity = identity;

// use Range Min Query by default
if (this.operation === undefined) {
this.operation = Math.min;
this.identity = Infinity;
}
this.operationFallback = operationFallback;

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

this.build();
this.buildSegmentTree();
}

/**
* Stub for recursive call
* @param {number[]} inputArray
* @return {number[]}
*/
build() {
this.buildRec(1, 0, this.n - 1);
}
initSegmentTree(inputArray) {
let segmentTreeArrayLength;
const inputArrayLength = inputArray.length;

/**
* Left child index
* @param {number} root
*/
left(root) {
return 2 * root;
if (isPowerOfTwo(inputArrayLength)) {
// If original array length is a power of two.
segmentTreeArrayLength = (2 * inputArrayLength) - 1;
} else {
// If original array length is not a power of two then we need to find
// next number that is a power of two and use it to calculate
// tree array size. This is happens because we need to fill empty children
// in perfect binary tree with nulls.And those nulls need extra space.
const currentPower = Math.floor(Math.log2(inputArrayLength));
const nextPower = currentPower + 1;
const nextPowerOfTwoNumber = 2 ** nextPower;
segmentTreeArrayLength = (2 * nextPowerOfTwoNumber) - 1;
}

return new Array(segmentTreeArrayLength).fill(null);
}

/**
* Right child index
* @param {number} root
* Build segment tree.
*/
right(root) {
return (2 * root) + 1;
buildSegmentTree() {
const leftIndex = 0;
const rightIndex = this.inputArray.length - 1;
const position = 0;
this.buildTreeRecursively(leftIndex, rightIndex, position);
}

/**
* root is the index in the tree, [l,r] (inclusive) is the current array segment being built
* @param {number} root
* @param {number} l
* @param {number} r
* Build segment tree recursively.
*
* @param {number} leftInputIndex
* @param {number} rightInputIndex
* @param {number} position
*/
buildRec(root, l, r) {
if (l === r) {
this.tree[root] = this.array[l];
} else {
const mid = Math.floor((l + r) / 2);
// build left and right nodes
this.buildRec(this.left(root), l, mid);
this.buildRec(this.right(root), mid + 1, r);
this.tree[root] = this.operation(this.tree[this.left(root)], this.tree[this.right(root)]);
buildTreeRecursively(leftInputIndex, rightInputIndex, position) {
// If low input index and high input index are equal that would mean
// the we have finished splitting and we are already came to the leaf
// of the segment tree. We need to copy this leaf value from input
// array to segment tree.
if (leftInputIndex === rightInputIndex) {
this.segmentTree[position] = this.inputArray[leftInputIndex];
return;
}

// Split input array on two halves and process them recursively.
const middleIndex = Math.floor((leftInputIndex + rightInputIndex) / 2);
// Process left half of the input array.
this.buildTreeRecursively(leftInputIndex, middleIndex, this.getLeftChildIndex(position));
// Process right half of the input array.
this.buildTreeRecursively(middleIndex + 1, rightInputIndex, this.getRightChildIndex(position));

// Once every tree leaf is not empty we're able to build tree bottom up using
// provided operation function.
this.segmentTree[position] = this.operation(
this.segmentTree[this.getLeftChildIndex(position)],
this.segmentTree[this.getRightChildIndex(position)],
);
}

/**
* Stub for recursive call
* @param {number} lindex
* @param {number} rindex
* Do range query on segment tree in context of this.operation function.
*
* @param {number} queryLeftIndex
* @param {number} queryRightIndex
* @return {number}
*/
query(lindex, rindex) {
return this.queryRec(1, lindex, rindex, 0, this.n - 1);
rangeQuery(queryLeftIndex, queryRightIndex) {
const leftIndex = 0;
const rightIndex = this.inputArray.length - 1;
const position = 0;

return this.rangeQueryRecursive(
queryLeftIndex,
queryRightIndex,
leftIndex,
rightIndex,
position,
);
}

/**
* [lindex, rindex] is the query region
* [l,r] is the current region being processed
* Guaranteed that [lindex,rindex] contained in [l,r]
* @param {number} root
* @param {number} lindex
* @param {number} rindex
* @param {number} l
* @param {number} r
* Do range query on segment tree recursively in context of this.operation function.
*
* @param {number} queryLeftIndex - left index of the query
* @param {number} queryRightIndex - right index of the query
* @param {number} leftIndex - left index of input array segment
* @param {number} rightIndex - right index of input array segment
* @param {number} position - root position in binary tree
* @return {number}
*/
queryRec(root, lindex, rindex, l, r) {
// console.log(root, lindex, rindex, l, r);
if (lindex > rindex) {
// happens when mid+1 > r - no segment
return this.identity;
rangeQueryRecursive(queryLeftIndex, queryRightIndex, leftIndex, rightIndex, position) {
if (queryLeftIndex <= leftIndex && queryRightIndex >= rightIndex) {
// Total overlap.
return this.segmentTree[position];
}
if (l === lindex && r === rindex) {
// query region matches current region - use tree value
return this.tree[root];

if (queryLeftIndex > rightIndex || queryRightIndex < leftIndex) {
// No overlap.
return this.operationFallback;
}
const mid = Math.floor((l + r) / 2);
// get left and right results and combine
const leftResult = this.queryRec(this.left(root), lindex, Math.min(rindex, mid), l, mid);
const rightResult = this.queryRec(
this.right(root), Math.max(mid + 1, lindex), rindex,
mid + 1, r,

// Partial overlap.
const middleIndex = Math.floor((leftIndex + rightIndex) / 2);

const leftOperationResult = this.rangeQueryRecursive(
queryLeftIndex,
queryRightIndex,
leftIndex,
middleIndex,
this.getLeftChildIndex(position),
);
return this.operation(leftResult, rightResult);

const rightOperationResult = this.rangeQueryRecursive(
queryLeftIndex,
queryRightIndex,
middleIndex + 1,
rightIndex,
this.getRightChildIndex(position),
);

return this.operation(leftOperationResult, rightOperationResult);
}

/**
* Set array[index] to value
* @param {number} index
* @param {number} value
* Left child index.
* @param {number} parentIndex
* @return {number}
*/
update(index, value) {
this.array[index] = value;
this.updateRec(1, index, value, 0, this.n - 1);
getLeftChildIndex(parentIndex) {
return (2 * parentIndex) + 1;
}

/**
* @param {number} root
* @param {number} index
* @param {number} value
* @param {number} l
* @param {number} r
* Right child index.
* @param {number} parentIndex
* @return {number}
*/
updateRec(root, index, value, l, r) {
if (l === r) {
// we are at tree node containing array[index]
this.tree[root] = value;
} else {
const mid = Math.floor((l + r) / 2);
// update whichever child index is in, update this.tree[root]
if (index <= mid) {
this.updateRec(this.left(root), index, value, l, mid);
} else {
this.updateRec(this.right(root), index, value, mid + 1, r);
}
this.tree[root] = this.operation(this.tree[this.left(root)], this.tree[this.right(root)]);
}
getRightChildIndex(parentIndex) {
return (2 * parentIndex) + 2;
}
}
Loading

0 comments on commit 434a564

Please sign in to comment.