C Programming Data Structures Segment Trees And Binary Indexed Trees Fenwick Tree Complete Guide

 Last Update:2025-06-23T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    9 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of C Programming data structures Segment Trees and Binary Indexed Trees Fenwick Tree


Segment Trees

Segment Trees are a versatile data structure that allows efficient querying and updating of array elements. They are particularly useful for problems involving range queries and point updates. A segment tree can be used to solve problems like finding the sum, minimum, maximum, or greatest common divisor over a range in O(log n) time, while updating an element also takes O(log n) time.

Structure and Construction

A segment tree is a binary tree where each node represents a range of the array. The root node represents the entire range (from 0 to n-1), and each leaf node represents an individual element of the array. The internal nodes represent the union of the ranges of their children.

For an array of size n, the segment tree can be represented as an array segTree[] where:

  • segTree[0] is not used (some implementations start from index 0).
  • segTree[1] represents the root node.
  • segTree[i] has its left child at segTree[2*i] and right child at segTree[2*i + 1].

Building the Segment Tree

The segment tree is built in a bottom-up manner. Each leaf node is initialized with an element from the array, and each internal node is computed based on its children. For a sum-based segment tree, each internal node stores the sum of its children's values.

C Code Example:

#include <stdio.h>

// Function to build the segment tree
void buildSegTree(int arr[], int segTree[], int node, int start, int end) {
    if (start == end) {
        // Leaf node will have a single element
        segTree[node] = arr[start];
    } else {
        int mid = (start + end) / 2;
        // Recurse on the left and right children
        buildSegTree(arr, segTree, 2 * node, start, mid);
        buildSegTree(arr, segTree, 2 * node + 1, mid + 1, end);
        // Internal node will have the sum of both children
        segTree[node] = segTree[2 * node] + segTree[2 * node + 1];
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11};
    int n = sizeof(arr) / sizeof(arr[0]);
    int segTree[4 * n];  // Maximum size of segment tree
    buildSegTree(arr, segTree, 1, 0, n - 1);

    // Example: Print the segment tree
    for (int i = 1; i < 4 * n; i++) {
        printf("%d ", segTree[i]);
    }
    printf("\n");
    return 0;
}

Range Query

To efficiently answer range queries, we can use a recursive function that traverses the segment tree and combines results of relevant nodes.

C Code Example:

// Function to return the sum of elements in range [l, r]
int rangeQuery(int segTree[], int node, int start, int end, int l, int r) {
    if (r < start || end < l) {
        // Range represented by a node is completely outside the given range
        return 0;
    }
    if (l <= start && end <= r) {
        // Range represented by a node is completely inside the given range
        return segTree[node];
    }
    // Range represented by a node is partially inside and partially outside the given range
    int mid = (start + end) / 2;
    int leftSum = rangeQuery(segTree, 2 * node, start, mid, l, r);
    int rightSum = rangeQuery(segTree, 2 * node + 1, mid + 1, end, l, r);
    return leftSum + rightSum;
}

Point Update

Updating a single element in the segment tree involves traversing from the leaf to the root and updating all relevant nodes.

C Code Example:

// Function to update a single element
void updateSegTree(int arr[], int segTree[], int node, int start, int end, int idx, int value) {
    if (start == end) {
        // Leaf node
        arr[idx] = value;
        segTree[node] = value;
    } else {
        int mid = (start + end) / 2;
        if (start <= idx && idx <= mid) {
            // If idx is in the left child, recurse on the left child
            updateSegTree(arr, segTree, 2 * node, start, mid, idx, value);
        } else {
            // If idx is in the right child, recurse on the right child
            updateSegTree(arr, segTree, 2 * node + 1, mid + 1, end, idx, value);
        }
        // Internal node will have the sum of both children
        segTree[node] = segTree[2 * node] + segTree[2 * node + 1];
    }
}

Binary Indexed Trees (Fenwick Trees)

Binary Indexed Trees are another data structure that supports efficient range queries and point updates. They are simpler to implement than segment trees and use less memory (O(n) vs. O(4n) for segment trees). BITs are particularly useful for scenarios where the range queries are cumulative (e.g., prefix sums, cumulative frequency tables).

Structure and Construction

A BIT is an array that allows efficient computation of prefix sums. Each element in the BIT array corresponds to a range of elements in the input array. The key property of BITs is that the i-th element in the BIT array represents the sum of a subset of elements in the input array, specifically the elements whose indices have binary representations ending in the lowest bit set in i.

Updating a BIT

To update an element in a BIT, we increment the element and propagate the changes to the parent nodes.

C Code Example:

// Function to update an element at idx in the BIT
void updateBIT(int BIT[], int n, int idx, int val) {
    // Index in BIT is 1 more than the input array
    idx = idx + 1;
    // Traverse all ancestors of BIT[idx]
    while (idx <= n) {
        // Add val to current node of BIT
        BIT[idx] += val;
        // Update index to that of parent in BIT
        idx += idx & (-idx);
    }
}

Prefix Sum Query

To get the prefix sum up to a certain index, we sum the relevant elements in the BIT.

C Code Example:

// Function to get the prefix sum up to a certain index
int getPrefixSum(int BIT[], int idx) {
    int sum = 0;
    // Index in BIT is 1 more than the input array
    idx = idx + 1;
    // Traverse ancestors of BIT[idx]
    while (idx > 0) {
        // Add current element of BIT to sum
        sum += BIT[idx];
        // Move index to parent node in BIT
        idx -= idx & (-idx);
    }
    return sum;
}

Range Query

Range queries can be efficiently answered using two prefix sum queries.

C Code Example:

// Function to get the sum of elements in range [l, r]
int rangeQuery(int BIT[], int l, int r) {
    // Remove prefix sum up to element before l
    return getPrefixSum(BIT, r) - getPrefixSum(BIT, l - 1);
}

Comparison

  • Memory Usage: Segment Trees typically use more memory (4n) than BITs (n).
  • Implementation Complexity: BITs are generally easier to code and maintain.
  • Range Queries: Segment Trees support more complex range queries (e.g., minimum, maximum, gcd).
  • Point Updates: Both structures support efficient point updates (O(log n)).

Conclusion

Both Segment Trees and Binary Indexed Trees are powerful data structures for handling range queries and point updates efficiently. The choice between them depends on the specific requirements of the problem. For simple cumulative queries, BITs are preferable due to their simplicity and lower memory usage. However, for more complex range queries, segment trees offer greater flexibility.


Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement C Programming data structures Segment Trees and Binary Indexed Trees Fenwick Tree

Segment Trees

A segment tree is a tree data structure for storing intervals or segments. It allows for Fast Query and Update operations, typically in (O(\log n)) time.

Example: Range Sum Query

Problem Statement: Given an array arr[] of size n, support the following operations:

  1. Update arr[i] to new_val.
  2. Get the sum of elements from index l to r (both inclusive).

Solution: Let's build a segment tree for a given array and perform range sum queries:

#include <stdio.h>
#include <math.h>

// Function to build segment tree from given array
void buildSegmentTree(int arr[], int segmentTree[], int n) {
    // Fill the leaves of the segment tree
    for (int i = 0; i < n; i++)
        segmentTree[n + i] = arr[i];

    // Build the tree by calculating values of parent nodes
    for (int i = n - 1; i > 0; --i)
        segmentTree[i] = segmentTree[i << 1] + segmentTree[i << 1 | 1];
}

// Function to update a value in the input array and segment tree
void updateValue(int arr[], int segmentTree[], int n, int index, int newValue) {
    // Update the value in array
    arr[index] = newValue;

    // Update the value in segment tree
    for (segmentTree[index += n] = newValue; index > 1; index >>= 1)
        segmentTree[index >> 1] = segmentTree[index] + segmentTree[index ^ 1];
}

// Function to get sum of elements from index `l` to `r` in the segment tree
int getSum(int segmentTree[], int n, int l, int r) {
    int sum = 0;

    // Loop to find the sum in the range `[l, r]`
    for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
        if (l & 1) {
            sum += segmentTree[l++];
        }
        if (r & 1) {
            sum += segmentTree[--r];
        }
    }
    return sum;
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11};
    int n = sizeof(arr) / sizeof(arr[0]);

    // Total size of segment tree
    int treeSize = 2 * pow(2, int(ceil(log2(n))));

    // Allocate memory for segment tree
    int segmentTree[treeSize];

    // Build segment tree from given array
    buildSegmentTree(arr, segmentTree, n);

    // Query range sums
    printf("Sum of values in range [1, 3] = %d\n", getSum(segmentTree, n, 1, 4));

    // Update value at index 1 to 10
    updateValue(arr, segmentTree, n, 1, 10);

    // Query range sums after update
    printf("Sum of values in range [1, 3] after update = %d\n", getSum(segmentTree, n, 1, 4));

    return 0;
}

Binary Indexed Trees (Fenwick Trees)

A Binary Indexed Tree (Fenwick Tree) is another data structure that supports fast prefix sums and updates on an array.

Example: Range Sum Query

Problem Statement: Given an array arr[] of size n, support the following operations:

  1. Update arr[i] to new_val.
  2. Get the sum of elements from index 0 to i.

Solution: Let's build a Binary Indexed Tree for a given array and perform prefix sum queries:

#include <stdio.h>

// Helper function to update the Binary Indexed Tree (Fenwick Tree)
void updateBIT(int BIT[], int n, int index, int value) {
    // Traverse all ancestors and add 'value' to all ancestors
    for (; index <= n; index += index & (-index)) {
        BIT[index] += value;
    }
}

// Helper function to get the sum of elements from index 0 to `index` in the BIT
int getSumBIT(int BIT[], int index) {
    int sum = 0;

    // Traverse ancestors of `index` in Binary Indexed Tree to get the sum
    for (; index > 0; index -= index & (-index)) {
        sum += BIT[index];
    }

    return sum;
}

// Constructs and returns a Binary Indexed Tree for the given array of size `n`
void constructBIT(int arr[], int BIT[], int n) {
    // Initialize BIT with 0
    for (int i = 1; i <= n; i++) {
        BIT[i] = 0;
    }

    // Store the actual values in BIT
    for (int i = 0; i < n; i++) {
        updateBIT(BIT, n, i + 1, arr[i]);
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11};
    int n = sizeof(arr) / sizeof(arr[0]);

    // Create and initialize Binary Indexed Tree
    int BIT[n + 1];
    constructBIT(arr, BIT, n);

    // Get the sum of elements in the array from index 0 to 3 (inclusive)
    printf("Sum of values in array from index 0 to 3 = %d\n", getSumBIT(BIT, 4));

    // Update value at index 1 to 10
    updateBIT(BIT, n, 2, 7); // Update index 2 (1-based) by 7 (10 - 3)

    // Get the new sum after update
    printf("Sum of values in array from index 0 to 3 after update = %d\n", getSumBIT(BIT, 4));

    return 0;
}

Explanation

  • Segment Tree:

    • The buildSegmentTree function initializes the segment tree from the given array.
    • The updateValue function updates an element in the array and the corresponding node in the segment tree.
    • The getSum function calculates the sum of elements in a specific range.
  • Binary Indexed Tree (Fenwick Tree):

    • The updateBIT function updates an element in the BIT.
    • The getSumBIT function calculates the prefix sum up to a specific index.
    • The constructBIT function initializes the BIT from the given array.

Top 10 Interview Questions & Answers on C Programming data structures Segment Trees and Binary Indexed Trees Fenwick Tree

1. What is a Segment Tree and what are its primary uses?

Answer:
A Segment Tree is a data structure that allows answering range queries over an array efficiently while also supporting efficient updates to the array. It is a full binary tree where each node represents a contiguous segment of the array. The primary uses include finding the sum of elements in a range, finding the minimum or maximum in a range, and performing range updates.

2. How do you construct a Segment Tree in C?

Answer:
To construct a segment tree in C, you typically initialize a tree array (often 4 times the size of the input array). Here's a simple example for a sum query:

#include <stdio.h>
#include <stdlib.h>

void buildTree(int arr[], int tree[], int node, int start, int end) {
    if (start == end) {
        tree[node] = arr[start];
    } else {
        int mid = (start + end) / 2;
        buildTree(arr, tree, 2 * node, start, mid);
        buildTree(arr, tree, 2 * node + 1, mid + 1, end);
        tree[node] = tree[2 * node] + tree[2 * node + 1];
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 11};
    int n = sizeof(arr) / sizeof(arr[0]);
    int *tree = (int*)malloc(4 * n * sizeof(int));
    buildTree(arr, tree, 1, 0, n - 1);
    // tree is now constructed
    free(tree);
    return 0;
}

3. What is a Binary Indexed Tree (Fenwick Tree) and when is it used?

Answer:
A Binary Indexed Tree, or Fenwick Tree, is another data structure that can be used to manipulate cumulative frequency tables. It's one of the most efficient ways to maintain prefix sums of dynamic arrays. Its operations: querying the prefix sum, updating an element, and building the tree all run in O(log n) time.

4. How do you build a Binary Indexed Tree in C?

Answer:
Here's a basic example of initializing and updating a BIT:

#include <stdio.h>

void updateTree(int tree[], int n, int index, int delta){
    while(index <= n){
        tree[index] += delta;
        index += index & (-index);
    }
}

int queryTree(int tree[], int index){
    int sum = 0;
    while(index > 0){
        sum += tree[index];
        index -= index & (-index);
    }
    return sum;
}

int main(){
    int arr[] = {1, 2, 3, 4, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);
    int *tree = (int*)calloc(n + 1, sizeof(int));
    for(int i = 0; i < n; i++)
        updateTree(tree, n, i + 1, arr[i]);

    printf("Sum of first 3 elements: %d\n", queryTree(tree, 3));

    free(tree);
    return 0;
}

5. Which is more suitable between a Segment Tree and a Binary Indexed Tree for a problem involving prefix sums?

Answer:
For problems that involve only prefix sums (and point updates), a Binary Indexed Tree is usually more efficient because it has a simpler implementation and lower constant factors. However, Segment Trees are more suitable when you need to perform range queries and updates.

6. How would you handle a point update in a Segment Tree?

Answer:
ToUpdate a value in a segment tree, you need to propagate the change up the tree. Here's a sample update function:

void update(int tree[], int node, int start, int end, int idx, int val) {
    if (start == end) {
        // Leaf node
        arr[idx] += val;
        tree[node] += val;
    } else {
        int mid = (start + end) / 2;
        if(start <= idx && idx <= mid){
            // If idx is in the left child
            update(tree, 2 * node, start, mid, idx, val);
        } else {
            // If idx is in the right child
            update(tree, 2 * node + 1, mid + 1, end, idx, val);
        }
        // Internal node will have the sum of both of its children
        tree[node] = tree[2 * node] + tree[2 * node + 1];
    }
}

7. How do you perform a range query in a Segment Tree?

Answer:
To query a range, you need to consider several cases to make sure you cover the range effectively:

int query(int tree[], int node, int start, int end, int l, int r) {
    if(r < start || end < l){
        // Range represented by a node is completely outside the given range
        return 0;
    }
    if(l <= start && end <= r){
        // Range represented by a node is completely inside the given range
        return tree[node];
    }
    // Segment tree is partly inside and partly outside the given range
    int mid = (start + end) / 2;
    int p1 = query(tree, 2 * node, start, mid, l, r);
    int p2 = query(tree, 2 * node + 1, mid + 1, end, l, r);
    return (p1 + p2);
}

8. How can you implement lazy propagation in a Segment Tree?

Answer:
Lazy propagation is used to defer updates to the underlying array, thus reducing the number of times a node is recursively updated:

void updateRange(int tree[], int lazy[], int node, int start, int end, int l, int r, int val) {
    if(lazy[node] != 0){
        tree[node] += (end - start + 1) * lazy[node];
        if(start != end){
            lazy[node * 2] += lazy[node];
            lazy[node * 2 + 1] += lazy[node];
        }
        lazy[node] = 0;
    }
    if(start > end || start > r || end < l){
        // Current segment is not within range [l, r]
        return;
    }
    if(start >= l && end <= r){
        // Segment is fully within range
        tree[node] += (end - start + 1) * val;
        if(start != end){
            // Not leaf node
            lazy[node * 2] += val;
            lazy[node * 2 + 1] += val;
        }
        return;
    }
    // Segment is partially within range
    int mid = (start + end) / 2;
    updateRange(tree, lazy, node * 2, start, mid, l, r, val);
    updateRange(tree, lazy, node * 2 + 1, mid + 1, end, l, r, val);
    tree[node] = tree[node * 2] + tree[node * 2 + 1];
}

int queryRange(int tree[], int lazy[], int node, int start, int end, int l, int r) {
    if(start > end || start > r || end < l){
        return 0;
    }
    if(lazy[node] != 0) {
        // There are pending updates
        tree[node] += (end - start + 1) * lazy[node];
        if(start != end) {
            lazy[node*2] += lazy[node];
            lazy[node*2+1] += lazy[node];
        }
        lazy[node] = 0;
    }
    if(start >= l && end <= r){
        return tree[node];
    }
    int mid = (start + end) / 2;
    int p1 = queryRange(tree, lazy, node*2, start, mid, l, r);
    int p2 = queryRange(tree, lazy, node*2+1, mid+1, end, l, r);
    return (p1 + p2);
}

9. Can Segment Trees and Binary Indexed Trees be used for problems beyond sums and minimums?

Answer:
Yes, both Segment Trees and Binary Indexed Trees can be adapted to handle a variety of other queries and updates, such as finding the maximum, the greatest common divisor (GCD), the least common multiple (LCM), and even custom operations. However, more complex operations might require additional logic for handling updates and queries.

10. What are the time complexities associated with Segment Trees and Binary Indexed Trees?

Answer:

  • Segment Trees:

    • Construction: O(n)
    • Range Query: O(log n)
    • Point Update: O(log n)
    • Range Update with Lazy Propagation: O(log n)
  • Binary Indexed Trees (Fenwick Trees):

    • Construction: O(n log n) via repeated point updates
    • Prefix Sum Query: O(log n)
    • Point Update: O(log n)

You May Like This Related .NET Topic

Login to post a comment.