What Is A Segment Tree?
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Introduction
Welcome to the realm of Segment Trees! If you've meandered through the pathways of data structures, you've likely bumped into various trees. But today, we're introducing a special kind of tree that's less talked about yet incredibly powerful: the Segment Tree.
What Exactly is a Segment Tree?
Imagine you're building the next big thing since sliced bread: an app that allows users to rate their bread slices (because, why not?). You need a quick way to query ratings within a range or update a rating. This is where a using a segment tree would shine.
At its core, a Segment Tree is a binary tree used for storing information about segments or intervals. It facilitates faster querying, like finding the sum, minimum, or maximum of these intervals. It's the kind of tool you didn't know you needed until now.
Theoretical
The Theory
Imagine a vast library, home to a plethora of books on myriad subjects. The library is organized into sections, each housing books on a particular topic. Now, you're the librarian, and you wish to quickly answer questions like: - How many books are there in a specific range of sections? - What's the thickest book between section A and section B?
Checking each book or even each section individually would be tedious. Enter the "Section Summary Card System" (or SSCS, because who doesn't love acronyms?). This system gives you a summarized view of every possible range of sections, allowing you to answer queries rapidly.
The Segment Tree is akin to this SCSS. It provides summarized information about various segments (or intervals) of an array, enabling efficient answers to range queries.
The Theoretical Dive
With the analogy in mind, let's understand the Segment Tree's core essence.
Structure: At its core, a Segment Tree is a binary tree. Each node represents an interval of an array, with the root representing the entire array. The two children of a node represent the two halves of the node's interval. This hierarchical structure ensures that we can access any sub-interval of the array in logarithmic time.
Storage: Each node in the Segment Tree stores aggregate information about its interval. This information could be the sum of elements in that interval, the maximum element, the minimum, etc. When we say "aggregate," it means that the information at a parent node can be derived from its children. For example, if nodes store sums, then the parent's value is the sum of its two children's values.
Building the Tree: The tree is constructed recursively. The process can be visualized as:
- The root node represents the entire array.
- Split the current interval into two halves.
- The left child represents the left half, and the right child represents the right half.
- Repeat the process for each child until you reach individual array elements.
Querying: To fetch information about a particular interval, we traverse the tree from the root. At each node, we decide:
- If the node's interval lies entirely inside the query interval, fetch the node's value.
- If the node's interval is outside the query interval, ignore it.
- If the node's interval partially overlaps the query interval, split the query and delve deeper into both children.
Updates: When an element of the array changes, the tree nodes representing intervals containing that element must be updated. This update is propagated from the leaf node (representing a single element) up to the root.

Why Segment Trees?
With the availability of simpler data structures, why venture into the realm of Segment Trees? The answer lies in their power and efficiency. Segment Trees offer a unique blend of logarithmic time complexities for both updates and queries. They are versatile and can be adapted for various range-query problems beyond just sums, such as range minimums, maximums, and even gcds.
Implementation
Implementation
Crafting Our Segment Tree
Let's roll up our sleeves and carve out this tree in Python:
class SegmentTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.build(arr, 0, 0, self.n-1)
def build(self, arr, v, l, r):
if l == r:
self.tree[v] = arr[l]
else:
m = (l + r) // 2
self.build(arr, v*2, l, m)
self.build(arr, v*2+1, m+1, r)
self.tree[v] = self.tree[v*2] + self.tree[v*2+1]
// javascript version
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
this.build(arr, 1, 0, this.n-1);
}
build(arr, v, l, r) {
if (l === r) {
this.tree[v] = arr[l];
} else {
let m = Math.floor((l + r) / 2);
this.build(arr, v*2, l, m);
this.build(arr, v*2+1, m+1, r);
this.tree[v] = this.tree[v*2] + this.tree[v*2+1];
}
}
}
// java version
public class SegmentTree {
private int[] tree;
private int n;
public SegmentTree(int[] arr) {
this.n = arr.length;
this.tree = new int[4 * n];
build(arr, 1, 0, n-1);
}
private void build(int[] arr, int v, int l, int r) {
if (l == r) {
tree[v] = arr[l];
} else {
int m = (l + r) / 2;
build(arr, v*2, l, m);
build(arr, v*2+1, m+1, r);
tree[v] = tree[v*2] + tree[v*2+1];
}
}
}
// c++ version
#include <vector>
class SegmentTree {
public:
SegmentTree(std::vector<int>& arr) {
n = arr.size();
tree.resize(4 * n, 0);
build(arr, 1, 0, n-1);
}
private:
std::vector<int> tree;
int n;
void build(std::vector<int>& arr, int v, int l, int r) {
if (l == r) {
tree[v] = arr[l];
} else {
int m = (l + r) / 2;
build(arr, v*2, l, m);
build(arr, v*2+1, m+1, r);
tree[v] = tree[v*2] + tree[v*2+1];
}
}
};
You might be wondering, "What's this magic?" Let's break it down:
- We begin by initializing our tree to four times the size of our input array.
- The
buildfunction is where the magic happens. It recursively constructs our segment tree. If we're at a leaf node, we store the array element. Otherwise, we build the left and right subtrees and store their sum.
Understanding the Segment Tree with an Example
Consider an example array: arr = [1, 3, 5, 7, 9, 11].
Step 1: Initialization
When we instantiate our segment tree with this array, we initialize an array tree with zeros, which is four times the size of arr. This ensures that we have enough space to represent the tree. So our tree starts as:
tree = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Step 2: Building the Tree
The build function starts working on the array. Since the input does not represent a single element (i.e., l is not equal to r), we find the middle of the array and recursively build for the left and right halves.
For our array, the process would look something like:
- Start with the entire array:
l=0,r=5(entire array). Middlem=2. - Recursively build the left half:
l=0,r=2(elements [1, 3, 5]). Here again, we'd split this into two halves:[1, 3]and[5]. - Continue this process until we reach individual elements.
- Once we've reached individual elements (leaf nodes), we begin storing values in the
treearray and then propagate those values upwards.
Visually, the tree array evolves as:
Step 1 (Initialization): [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Step 2 (Building for leaf nodes): [0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 7, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Step 3 (Building upwards): [0, 0, 0, 0, 4, 5, 16, 11, 1, 3, 0, 0, 7, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Step 4 (Final tree after aggregating values): [0, 36, 9, 27, 4, 5, 16, 11, 1, 3, 0, 0, 7, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Note: The above representation is a simplification. The tree's structure and values will vary based on the aggregation function used (in this case, sum).
By the end of the build process, our tree array has stored values at appropriate positions. The root of the tree (position 1 in the array) contains the aggregated value of the entire array. The left and right children of each node split this aggregated value further down into segments, and so on.
This segment tree structure allows us to efficiently query a range of values (like the sum between two indices) and update individual values.

More Manipulation
Querying Our Tree
With the tree built, it's time to extract some information from it!
# python version
def query(self, v, l, r, ql, qr):
if ql <= l and r <= qr:
return self.tree[v]
if qr < l or r < ql:
return 0
m = (l + r) // 2
return self.query(v*2, l, m, ql, qr) + self.query(v*2+1, m+1, r, ql, qr)
// javascript version
query(v, l, r, ql, qr) {
if (ql <= l && r <= qr) {
return this.tree[v];
}
if (qr < l || r < ql) {
return 0;
}
let m = Math.floor((l + r) / 2);
return this.query(v*2, l, m, ql, qr) + this.query(v*2+1, m+1, r, ql, qr);
}
// java version
public int query(int v, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return tree[v];
}
if (qr < l || r < ql) {
return 0;
}
int m = (l + r) / 2;
return query(v*2, l, m, ql, qr) + query(v*2+1, m+1, r, ql, qr);
}
// c++ version
int query(int v, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return tree[v];
}
if (qr < l || r < ql) {
return 0;
}
int m = (l + r) / 2;
return query(v*2, l, m, ql, qr) + query(v*2+1, m+1, r, ql, qr);
}
Each version of the query function captures the same logic and functionality as your original Python function but in the respective languages.
This function allows you to ask, "Hey tree, can you tell me the sum between indices ql and qr?" And the tree, being the wise entity it is, either provides an answer or divides the task among its children.

Updating: Because Change is the Only Constant
Sometimes, a bread slice gets extra crispy and its rating changes. We need to account for that.
# python version
def update(self, v, l, r, pos, val):
if l == r:
self.tree[v] = val
else:
m = (l + r) // 2
if pos <= m:
self.update(v*2, l, m, pos, val)
else:
self.update(v*2+1, m+1, r, pos, val)
self.tree[v] = self.tree[v*2] + self.tree[v*2+1]
// javascript version
update(v, l, r, pos, val) {
if (l === r) {
this.tree[v] = val;
} else {
let m = Math.floor((l + r) / 2);
if (pos <= m) {
this.update(v*2, l, m, pos, val);
} else {
this.update(v*2+1, m+1, r, pos, val);
}
this.tree[v] = this.tree[v*2] + this.tree[v*2+1];
}
}
// java version
public void update(int v, int l, int r, int pos, int val) {
if (l == r) {
tree[v] = val;
} else {
int m = (l + r) / 2;
if (pos <= m) {
update(v*2, l, m, pos, val);
} else {
update(v*2+1, m+1, r, pos, val);
}
tree[v] = tree[v*2] + tree[v*2+1];
}
}
// c++ version
void update(int v, int l, int r, int pos, int val) {
if (l == r) {
tree[v] = val;
} else {
int m = (l + r) / 2;
if (pos <= m) {
update(v*2, l, m, pos, val);
} else {
update(v*2+1, m+1, r, pos, val);
}
tree[v] = tree[v*2] + tree[v*2+1];
}
}
With this update method, we locate our slice, adjust its rating, and inform the tree about this delightful crispiness.

A Slice of Action
Let's witness our Segment Tree in action:
# python version
ratings = [4, 3, 5, 2, 1]
bread_tree = SegmentTree(ratings)
print(bread_tree.query(1, 0, bread_tree.n-1, 1, 3)) # Output: 10 (Because 3 + 5 + 2 = 10)
bread_tree.update(1, 0, bread_tree.n-1, 2, 4) # The third slice's rating changed!
print(bread_tree.query(1, 0, bread_tree.n-1, 1, 3)) # Output: 9 (Because 3 + 4 + 2 = 9)
// javascript version
let ratings = [4, 3, 5, 2, 1];
let breadTree = new SegmentTree(ratings);
console.log(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Output: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.n-1, 2, 4); // The third slice's rating changed!
console.log(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Output: 9 (Because 3 + 4 + 2 = 9)
// java version
int[] ratings = {4, 3, 5, 2, 1};
SegmentTree breadTree = new SegmentTree(ratings);
System.out.println(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Output: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.n-1, 2, 4); // The third slice's rating changed!
System.out.println(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Output: 9 (Because 3 + 4 + 2 = 9)
// c++ version
std::vector<int> ratings = {4, 3, 5, 2, 1};
SegmentTree breadTree(ratings);
std::cout << breadTree.query(1, 0, breadTree.n-1, 1, 3) << std::endl; // Output: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.n-1, 2, 4); // The third slice's rating changed!
std::cout << breadTree.query(1, 0, breadTree.n-1, 1, 3) << std::endl; // Output: 9 (Because 3 + 4 + 2 = 9)
Each version captures the logic and functionality of your original Python code, but in the respective languages.
And there you have it! Your bread slices are now neatly organized in a Segment Tree.
```py
class SegmentTree:
def __init__(self, arr):
# Get the length of the input array
self.n = len(arr)
# Initialize the tree with 4 times the size of the input array
self.tree = [0] * (4 * self.n)
# Start building the tree from the root
self.build(arr, 1, 0, self.n-1)
def build(self, arr, v, l, r):
# If the left and right boundary is the same (i.e., a leaf node)
if l == r:
# Assign the value from the array to the leaf node
self.tree[v] = arr[l]
else:
# Find the middle of the current segment
m = (l + r) // 2
# Recursively build the left child
self.build(arr, v*2, l, m)
# Recursively build the right child
self.build(arr, v*2+1, m+1, r)
# Store the sum of the children at the current node
self.tree[v] = self.tree[v*2] + self.tree[v*2+1]
def query(self, v, l, r, ql, qr):
# If the current segment is entirely inside the query range
if ql <= l and r <= qr:
# Return the value stored in the current node
return self.tree[v]
# If the current segment is entirely outside the query range
if qr < l or r < ql:
# Return a neutral value (0 for sum)
return 0
# Find the middle of the current segment
m = (l + r) // 2
# Recursively query the left and right children, then return their sum
return self.query(v*2, l, m, ql, qr) + self.query(v*2+1, m+1, r, ql, qr)
def update(self, v, l, r, pos, val):
# If we've reached the leaf node corresponding to the array element
if l == r:
# Update the value
self.tree[v] = val
else:
# Find the middle of the current segment
m = (l + r) // 2
# If the update position is in the left half
if pos <= m:
# Recursively update the left child
self.update(v*2, l, m, pos, val)
else: # If the update position is in the right half
# Recursively update the right child
self.update(v*2+1, m+1, r, pos, val)
# Update the current node based on children values
self.tree[v] = self.tree[v*2] + self.tree[v*2+1]
def main():
# Sample array of ratings
ratings = [4, 3, 5, 2, 1]
# Create a segment tree for the ratings
bread_tree = SegmentTree(ratings)
# Query the segment tree for the sum of ratings from index 1 to 3
print(bread_tree.query(1, 0, bread_tree.n-1, 1, 3)) # Expected: 10 (Because 3 + 5 + 2 = 10)
# Update the rating of the third slice
bread_tree.update(1, 0, bread_tree.n-1, 2, 4)
# Query the segment tree again for the sum of ratings from index 1 to 3
print(bread_tree.query(1, 0, bread_tree.n-1, 1, 3)) # Expected: 9 (Because 3 + 4 + 2 = 9)
if __name__ == "__main__":
# Run the main driver function
main()
```
```js
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
this.build(arr, 1, 0, this.n-1);
}
build(arr, v, l, r) {
if (l === r) {
this.tree[v] = arr[l];
} else {
let m = Math.floor((l + r) / 2);
this.build(arr, v*2, l, m);
this.build(arr, v*2+1, m+1, r);
this.tree[v] = this.tree[v*2] + this.tree[v*2+1];
}
}
query(v, l, r, ql, qr) {
if (ql <= l && r <= qr) {
return this.tree[v];
}
if (qr < l || r < ql) {
return 0;
}
let m = Math.floor((l + r) / 2);
return this.query(v*2, l, m, ql, qr) + this.query(v*2+1, m+1, r, ql, qr);
}
update(v, l, r, pos, val) {
if (l === r) {
this.tree[v] = val;
} else {
let m = Math.floor((l + r) / 2);
if (pos <= m) {
this.update(v*2, l, m, pos, val);
} else {
this.update(v*2+1, m+1, r, pos, val);
}
this.tree[v] = this.tree[v*2] + this.tree[v*2+1];
}
}
}
function main() {
let ratings = [4, 3, 5, 2, 1];
let breadTree = new SegmentTree(ratings);
console.log(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Expected: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.n-1, 2, 4);
console.log(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Expected: 9 (Because 3 + 4 + 2 = 9)
}
main();
```
```java
public class Main {
private int[] tree;
private int n;
Main(int[] arr) {
this.n = arr.length;
this.tree = new int[4 * n];
build(arr, 1, 0, n-1);
}
private void build(int[] arr, int v, int l, int r) {
if (l == r) {
tree[v] = arr[l];
} else {
int m = (l + r) / 2;
build(arr, v*2, l, m);
build(arr, v*2+1, m+1, r);
tree[v] = tree[v*2] + tree[v*2+1];
}
}
public int query(int v, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return tree[v];
}
if (qr < l || r < ql) {
return 0;
}
int m = (l + r) / 2;
return query(v*2, l, m, ql, qr) + query(v*2+1, m+1, r, ql, qr);
}
public void update(int v, int l, int r, int pos, int val) {
if (l == r) {
tree[v] = val;
} else {
int m = (l + r) / 2;
if (pos <= m) {
update(v*2, l, m, pos, val);
} else {
update(v*2+1, m+1, r, pos, val);
}
tree[v] = tree[v*2] + tree[v*2+1];
}
}
public static void main(String[] args) {
int[] ratings = {4, 3, 5, 2, 1};
Main breadTree = new Main(ratings);
System.out.println(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Expected: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.n-1, 2, 4);
System.out.println(breadTree.query(1, 0, breadTree.n-1, 1, 3)); // Expected: 9 (Because 3 + 4 + 2 = 9)
}
}
```
```cpp
// c++ version
#include <iostream>
#include <vector>
class SegmentTree {
private:
std::vector<int> tree;
int n;
public:
SegmentTree(const std::vector<int>& arr) : n(arr.size()), tree(4 * n) {
build(arr, 1, 0, n-1);
}
void build(const std::vector<int>& arr, int v, int l, int r) {
if (l == r) {
tree[v] = arr[l];
} else {
int m = (l + r) / 2;
build(arr, v*2, l, m);
build(arr, v*2+1, m+1, r);
tree[v] = tree[v*2] + tree[v*2+1];
}
}
int query(int v, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return tree[v];
}
if (qr < l || r < ql) {
return 0;
}
int m = (l + r) / 2;
return query(v*2, l, m, ql, qr) + query(v*2+1, m+1, r, ql, qr);
}
void update(int v, int l, int r, int pos, int val) {
if (l == r) {
tree[v] = val;
} else {
int m = (l + r) / 2;
if (pos <= m) {
update(v*2, l, m, pos, val);
} else {
update(v*2+1, m+1, r, pos, val);
}
tree[v] = tree[v*2] + tree[v*2+1];
}
}
int getN() const {
return n;
}
};
int main() {
std::vector<int> ratings = {4, 3, 5, 2, 1};
SegmentTree breadTree(ratings);
std::cout << breadTree.query(1, 0, breadTree.getN()-1, 1, 3) << std::endl; // Expected: 10 (Because 3 + 5 + 2 = 10)
breadTree.update(1, 0, breadTree.getN()-1, 2, 4); // Update the third slice's rating
std::cout << breadTree.query(1, 0, breadTree.getN()-1, 1, 3) << std::endl; // Expected: 9 (Because 3 + 4 + 2 = 9)
return 0;
}
```
Conclusion
Conclusion
Segment Trees might initially seem like an intricate puzzle, but with a bit of practice and patience, they become an indispensable tool in your coding toolkit. They offer a beautiful blend of theory and practicality, making tasks involving array intervals seem like a breeze. Here's to more efficient coding and perfectly rated bread slices!
One Pager Cheat Sheet
One Pager Cheat Sheet
- A Segment Tree is a special binary tree data structure used for storing information about segments or intervals, facilitating faster querying like finding the sum, minimum, or maximum of these intervals.
- A Segment Tree is a
binary treethat provides summarized information about various segments of an array, allowing efficient responses to range queries; each node represents an array interval, storing aggregate information about that interval, and the tree is structured such that any sub-interval can be accessed inlogarithmic time. The Segment Tree is built recursively, splitting intervals into halves represented by child nodes, and allows for quick queries and updates by traversing from the root node and updating from the leaf up. The Segment Tree stands out for its power and efficiency in complex data structures, offeringlogarithmic time complexitiesfor both updates and queries, and flexibility for a variety of range-query problems. - The SegmentTree class in Python includes an initialization function that prepares a tree of four times the size of the original array, and a
buildfunction that recursively constructs the segment tree; if at a leaf node, the function stores the array element, otherwise, it constructs left and right subtrees and stores their sum. This allows efficient querying of a range of values and updating of individual values in the segment tree. - The
queryandupdatefunctions in the Segment Tree allow you to respectively retrieve the sum of the tree node values between two indices and adjust a tree node value, exemplified here with a bread rating system where bread slice ratings can be adjusted and their sum queried for. -
Segment Trees are an invaluable coding tool that simplify tasks involving
array intervals, blending theory and practicality for more efficient coding.