π³ Range Query Structures: Your Dataβs Super-Fast Librarian
Imagine you have a HUGE library with millions of books. Each book has a number on its spine. Now, someone asks you:
βWhatβs the total of all numbers from book 50 to book 200?β
You could walk through each book one by oneβ¦ but that would take FOREVER! π«
What if you had a magical librarian who already grouped books into sections and knew the totals? Youβd get your answer in SECONDS!
Thatβs exactly what Range Query Structures do for your data!
π― What Youβll Learn
- Segment Tree Fundamentals β The magical tree that groups your data
- Segment Tree Range Query β Ask questions super fast
- Segment Tree Update β Change values without rebuilding
- Binary Indexed Tree (BIT) β A clever, compact alternative
- BIT Operations β How to use the magic trick inside BIT
π² Part 1: Segment Tree Fundamentals
The Big Idea
Think of a Segment Tree like a tournament bracket at a sports event.
[1+2+3+4+5+6+7+8] β Root knows TOTAL
/ \
[1+2+3+4] [5+6+7+8] β Each section
/ \ / \
[1+2] [3+4] [5+6] [7+8]
/ \ / \ / \ / \
1 2 3 4 5 6 7 8 β Original numbers
Each parent node knows the sum of its children!
Why Is This Brilliant?
- The root knows the total of EVERYTHING
- Each middle node knows its sectionβs total
- The leaves (bottom) hold your original numbers
Simple Example:
# Your array: [2, 5, 1, 4, 9, 3]
# The segment tree stores:
#
# [24] (sum of all)
# / \
# [8] [16] (left half, right half)
# / \ / \
# [7] [1] [13] [3]
# / \ | / \ |
# 2 5 1 4 9 3
Building a Segment Tree in Python
def build_tree(arr, tree, node,
start, end):
if start == end:
# Leaf node
tree[node] = arr[start]
else:
mid = (start + end) // 2
left_child = 2 * node + 1
right_child = 2 * node + 2
# Build left and right
build_tree(arr, tree,
left_child, start, mid)
build_tree(arr, tree,
right_child, mid + 1, end)
# Parent = sum of children
tree[node] = (tree[left_child] +
tree[right_child])
Memory Tip: We need about 4 * n space for a segment tree of n elements.
π Part 2: Segment Tree Range Query
The Magic Question
βWhatβs the sum from index 2 to index 5?β
Instead of adding 4 numbers one by one, the segment tree combines pre-computed sections!
How It Works
Think of it like asking for the score in a tennis tournament:
- If the range you want perfectly matches a node β Use that node directly!
- If it overlaps both children β Ask both children and add their answers
- If it doesnβt overlap at all β Return 0 (not relevant)
Query: Sum from index 1 to 4
[sum: 24]
/ \
[sum: 8] [sum: 16]
/ \ / \
[7] [1] [13] [3]
/\ | / \ |
2 5 1 4 9 3
β β β β
index: 1 2 3 4
We need: 5 + 1 + 4 + 9 = 19
Python Code for Range Query
def range_query(tree, node, start,
end, left, right):
# No overlap
if right < start or left > end:
return 0
# Complete overlap
if left <= start and end <= right:
return tree[node]
# Partial overlap
mid = (start + end) // 2
left_sum = range_query(tree, 2*node+1,
start, mid,
left, right)
right_sum = range_query(tree, 2*node+2,
mid+1, end,
left, right)
return left_sum + right_sum
Time Complexity
- Without segment tree: O(n) β check every element π΄
- With segment tree: O(log n) β lightning fast! β‘
For an array of 1 million elements:
- Without: 1,000,000 operations
- With: ~20 operations!
π Part 3: Segment Tree Update
The Problem
What if one number changes?
Old Way: Rebuild the entire tree from scratch π±
Smart Way: Only update the nodes that care about this change!
How Updates Work
When you change a leaf, you only need to update nodes on the path from that leaf to the root.
Change index 2 from 1 to 7:
BEFORE: AFTER:
[24] [30] β +6
/ \ / \
[8] [16] [14] [16] β +6
/ \ / \ / \ / \
[7][1][13][3] [7][7][13][3] β Changed!
/\ | /\ | /\ | /\ |
2 5 1 4 9 3 2 5 7 4 9 3
β β
Old: 1 New: 7
Only 3 nodes changed in a tree with 6 elements!
Python Code for Update
def update(tree, node, start, end,
idx, new_val):
if start == end:
# Found the leaf
tree[node] = new_val
else:
mid = (start + end) // 2
left_child = 2 * node + 1
right_child = 2 * node + 2
if idx <= mid:
update(tree, left_child,
start, mid, idx, new_val)
else:
update(tree, right_child,
mid+1, end, idx, new_val)
# Recalculate parent
tree[node] = (tree[left_child] +
tree[right_child])
Time Complexity: O(log n) β same as query!
π Part 4: Binary Indexed Tree (BIT)
A Clever Alternative
The Binary Indexed Tree (also called Fenwick Tree) is like a compressed segment tree.
Instead of storing every possible range, it uses a clever binary trick to store just enough!
The Core Insight
Every number can be written in binary (1s and 0s).
The lowest set bit (rightmost 1) tells us how many elements to include!
Index (decimal) β Index (binary) β Range Size
1 β 0001 β 1 element
2 β 0010 β 2 elements
3 β 0011 β 1 element
4 β 0100 β 4 elements
5 β 0101 β 1 element
6 β 0110 β 2 elements
7 β 0111 β 1 element
8 β 1000 β 8 elements
How BIT Stores Data
Array: [_, 3, 2, 1, 5, 4, 2, 1, 3]
(index 0 unused, 1-indexed)
BIT: [_, 3, 5, 1, 11, 4, 6, 1, 21]
| | | | | | | |
β β β β β β β β
Stores these ranges:
1: just arr[1]
2: arr[1] + arr[2]
3: just arr[3]
4: arr[1]+arr[2]+arr[3]+arr[4]
5: just arr[5]
6: arr[5] + arr[6]
7: just arr[7]
8: arr[1] through arr[8]
Python BIT Implementation
class BinaryIndexedTree:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def update(self, i, delta):
"""Add delta to index i"""
while i <= self.n:
self.tree[i] += delta
i += i & (-i) # Magic!
def prefix_sum(self, i):
"""Sum from index 1 to i"""
total = 0
while i > 0:
total += self.tree[i]
i -= i & (-i) # Magic!
return total
def range_sum(self, left, right):
"""Sum from left to right"""
return (self.prefix_sum(right) -
self.prefix_sum(left - 1))
βοΈ Part 5: BIT Operations
The Magic Formula: i & (-i)
This tiny expression finds the lowest set bit of a number!
# How it works:
# -i is the two's complement of i
i = 6 # Binary: 0110
-i = -6 # Binary: 1010 (two's complement)
i & (-i) # Result: 0010 = 2
Two Key Operations
1. Moving UP (for updates):
Start at index, add lowest bit repeatedly
6 β 6+2=8 β 8+8=16 β done
Binary: 0110 β 1000 β 10000
2. Moving DOWN (for queries):
Start at index, subtract lowest bit repeatedly
7 β 7-1=6 β 6-2=4 β 4-4=0 β done
Binary: 0111 β 0110 β 0100 β 0000
Visual Example
Query: Sum from 1 to 7
Step 1: BIT[7] covers index 7 only
Step 2: BIT[6] covers indices 5-6
Step 3: BIT[4] covers indices 1-4
Total = BIT[7] + BIT[6] + BIT[4]
= 1 + 6 + 11 = 18 β
Complete Example
# Build BIT from array
arr = [3, 2, 1, 5, 4, 2, 1, 3]
bit = BinaryIndexedTree(len(arr))
for i, val in enumerate(arr):
bit.update(i + 1, val) # 1-indexed
# Query sum from index 2 to 6
result = bit.range_sum(2, 6)
print(result) # 2+1+5+4+2 = 14
# Update: add 3 to index 4
bit.update(4, 3)
# Query again
result = bit.range_sum(2, 6)
print(result) # 2+1+8+4+2 = 17
π Comparison: Segment Tree vs BIT
| Feature | Segment Tree | BIT |
|---|---|---|
| Space | 4n | n |
| Build Time | O(n) | O(n log n) |
| Query Time | O(log n) | O(log n) |
| Update Time | O(log n) | O(log n) |
| Range Updates | Easy | Tricky |
| Flexibility | High | Limited |
When to use which?
- BIT: When you only need prefix sums and point updates
- Segment Tree: When you need more complex operations (min, max, GCD, etc.)
π You Did It!
You now understand:
β
How Segment Trees organize data like a tournament bracket
β
How to query ranges in O(log n) time
β
How to update values efficiently
β
How Binary Indexed Trees use binary magic
β
The i & (-i) trick that powers BIT
These structures turn slow O(n) operations into blazing-fast O(log n) operations. Whether youβre building a database, game leaderboard, or competitive programming solution β youβre now equipped with powerful tools!
graph TD A["Array of Numbers"] --> B{Need Range Queries?} B -->|Simple Sums| C["Binary Indexed Tree"] B -->|Complex Operations| D["Segment Tree"] C --> E["O#40;log n#41; queries"] D --> E E --> F["π Super Fast!"]
Remember: The best data structure is the one you understand and can implement correctly. Practice both, and youβll know exactly when to reach for each one! πͺ
