Range Query Structures

Back

Loading concept...

🌳 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

  1. Segment Tree Fundamentals – The magical tree that groups your data
  2. Segment Tree Range Query – Ask questions super fast
  3. Segment Tree Update – Change values without rebuilding
  4. Binary Indexed Tree (BIT) – A clever, compact alternative
  5. 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:

  1. If the range you want perfectly matches a node β†’ Use that node directly!
  2. If it overlaps both children β†’ Ask both children and add their answers
  3. 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&#35;40;log n&#35;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! πŸ’ͺ

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.