Segment tree | Efficient implementation

Let us consider the following problem to understand Segment Trees without recursion.

We have an array arr[0 . . . n-1]. We should be able to,

  1. Find the sum of elements from index l to r where 0
  2. Change value of a specified element of the array to a new value x. We need to do arr[i] = x where 0

A simple solution is to run a loop from l to r and calculate sum of elements in given range. To update a value, simply do arr[i] = x. The first operation takes O(n) time and second operation takes O(1) time.

Another solution is to create another array and store sum from start to i at the ith index in this array. Sum of a given range can now be calculated in O(1) time, but update operation takes O(n) time now. This works well if the number of query operations are large and very few updates.

What if the number of query and updates are equal? Can we perform both the operations in O(log n) time once given the array? We can use a Segment Tree to do both operations in O(Logn) time. We have discussed the complete implementation of segment trees in our previous post. In this post we will discuss about a more easy and yet efficient implementation of segment trees than the previous post.

Consider the array and segment tree as shown below :

You can see from the above image that the original array is at the bottom and is 0-indexed with 16 elements. The tree contains a total of 31 nodes where the leaf nodes or the elements of original array starts from node 16. So, we can easily construct a segment tree for this array using a 2*N sized array where N is number of elements in original array. The leaf nodes will start from index N in this array and will go upto index (2*N – 1). Therefore and element at index i in original array will be at index (i + N) in the segment tree array. Now to calculate the parents, we will start from index (N – 1) and move upward. For an index i , its left child will be at (2 * i) and right child will be at (2*i + 1) index. So the values at nodes at (2 * i) and (2*i + 1) is combined at ith node to construct the tree.
As you can see in the above figure, we can query in this tree in an interval [L,R) with left index(L) included and right (R) excluded.

We will implement all of these multiplication and addition operations using bitwise operators.

Let us know have a look at the complete implementation:

filter_none

edit
close

play_arrow

link
brightness_4
code

#include <bits/stdc++.h>
using namespace std;
  
// limit for array size
const int N = 100000; 
  
int n; // array size
  
// Max size of tree
int tree[2 * N];
  
// function to build the tree
void build( int arr[]) 
    // insert leaf nodes in tree
    for (int i=0; i<n; i++)    
        tree[n+i] = arr[i];
      
    // build the tree by calculating parents
    for (int i = n - 1; i > 0; --i)     
        tree[i] = tree[i<<1] + tree[i<<1 | 1];    
}
  
// function to update a tree node
void updateTreeNode(int p, int value) 
    // set value at position p
    tree[p+n] = value;
    p = p+n;
      
    // move upward and update parents
    for (int i=p; i > 1; i >>= 1)
        tree[i>>1] = tree[i] + tree[i^1];
}
  
// function to get sum on interval [l, r)
int query(int l, int r) 
    int res = 0;
      
    // loop to find the sum in the range
    for (l += n, r += n; l < r; l >>= 1, r >>= 1)
    {
        if (l&1) 
            res += tree[l++];
      
        if (r&1) 
            res += tree[--r];
    }
      
    return res;
}
  
// driver program to test the above function 
int main() 
{
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
  
    // n is global
    n = sizeof(a)/sizeof(a[0]);
      
    // build tree 
    build(a);
      
    // print the sum in range(1,2) index-based
    cout << query(1, 3)<<endl;
      
    // modify element at 2nd index
    updateTreeNode(2, 1);
      
    // print the sum in range(1,2) index-based
    cout << query(1, 3)<<endl;
  
    return 0;
}
chevron_right

filter_none

edit
close

play_arrow

link
brightness_4
code

import java.io.*;
  
public class GFG {
      
    // limit for array size
    static int N = 100000
      
    static int n; // array size
      
    // Max size of tree
    static int []tree = new int[2 * N];
      
    // function to build the tree
    static void build( int []arr) 
    
          
        // insert leaf nodes in tree
        for (int i = 0; i < n; i++) 
            tree[n + i] = arr[i];
          
        // build the tree by calculating
        // parents
        for (int i = n - 1; i > 0; --i) 
            tree[i] = tree[i << 1] +
                      tree[i << 1 | 1]; 
    }
      
    // function to update a tree node
    static void updateTreeNode(int p, int value) 
    
          
        // set value at position p
        tree[p + n] = value;
        p = p + n;
          
        // move upward and update parents
        for (int i = p; i > 1; i >>= 1)
            tree[i >> 1] = tree[i] + tree[i^1];
    }
      
    // function to get sum on
    // interval [l, r)
    static int query(int l, int r) 
    
        int res = 0;
          
        // loop to find the sum in the range
        for (l += n, r += n; l < r;
                             l >>= 1, r >>= 1)
        {
            if ((l & 1) > 0
                res += tree[l++];
          
            if ((r & 1) > 0
                res += tree[--r];
        }
          
        return res;
    }
      
    // driver program to test the
    // above function 
    static public void main (String[] args)
    {
        int []a = {1, 2, 3, 4, 5, 6, 7, 8,
                                9, 10, 11, 12};
      
        // n is global
        n = a.length;
          
        // build tree 
        build(a);
          
        // print the sum in range(1,2)
        // index-based
        System.out.println(query(1, 3));
          
        // modify element at 2nd index
        updateTreeNode(2, 1);
          
        // print the sum in range(1,2)
        // index-based
        System.out.println(query(1, 3)); 
    }
}
  
// This code is contributed by vt_m.
chevron_right

filter_none

edit
close

play_arrow

link
brightness_4
code

using System;
  
public class GFG {
      
    // limit for array size
    static int N = 100000; 
      
    static int n; // array size
      
    // Max size of tree
    static int []tree = new int[2 * N];
      
    // function to build the tree
    static void build( int []arr) 
    
          
        // insert leaf nodes in tree
        for (int i = 0; i < n; i++) 
            tree[n + i] = arr[i];
          
        // build the tree by calculating
        // parents
        for (int i = n - 1; i > 0; --i) 
            tree[i] = tree[i << 1] +
                       tree[i << 1 | 1]; 
    }
      
    // function to update a tree node
    static void updateTreeNode(int p, int value) 
    
        // set value at position p
        tree[p + n] = value;
        p = p + n;
          
        // move upward and update parents
        for (int i = p; i > 1; i >>= 1)
            tree[i >> 1] = tree[i] + tree[i^1];
    }
      
    // function to get sum on
    // interval [l, r)
    static int query(int l, int r) 
    
        int res = 0;
          
        // loop to find the sum in the range
        for (l += n, r += n; l < r;
                             l >>= 1, r >>= 1)
        {
            if ((l & 1) > 0) 
                res += tree[l++];
          
            if ((r & 1) > 0) 
                res += tree[--r];
        }
          
        return res;
    }
      
    // driver program to test the
    // above function 
    static public void Main ()
    {
        int []a = {1, 2, 3, 4, 5, 6, 7, 8,
                            9, 10, 11, 12};
      
        // n is global
        n = a.Length;
          
        // build tree 
        build(a);
          
        // print the sum in range(1,2)
        // index-based
        Console.WriteLine(query(1, 3));
          
        // modify element at 2nd index
        updateTreeNode(2, 1);
          
        // print the sum in range(1,2)
        // index-based
        Console.WriteLine(query(1, 3)); 
    }
}
  
// This code is contributed by vt_m.
chevron_right


Output:
5
3

Yes! This is all. Complete implementation of segment tree including the query and update functions in such a less number of lines of code than the previous recursive one. Let us now understand about how each of the function is working:

  1. The picture makes it clear that the leaf nodes are stored at i+n, so we can clearly insert all leaf nodes directly.
  2. The next step is to build the tree and it takes O(n) time. The parent has always it’s index less then its children so we just process all the nodes in decreasing order calculating the value of parent node. If the code inside the build function to calculate parents seems confusing then you can see this code, it is equivalent to that inside the build function.
    tree[i]=tree[2*i]+tree[2*i+1]
    
  3. Updating a value at any position is also simple and the time taken will be proportional to the height of the tree. We only update values in the parents of the given node which is being changed. so for getting the parent , we just go up to the parent node , which is p/2 or p>>1, for node p. p^1 turns (2*i) to (2*i + 1) and vice versa to get the second child of p.
  4. Computing the sum also works in O(log(n)) time .if we work through an interval of [3,11), we need to calculate only for nodes 19,26,12 and 5 in that order.

The idea behind the query function is that whether we should include an element in the sum or we should include its parent. Let’s look at the image once again for proper understanding. Consider that L is the left border of an interval and R is the right border of the interval [L,R). It is clear from the image that if L is odd then it means that it is the right child of it’s parent and our interval includes only L and not it’s parent. So we will simply include this node to sum and move to the parent of it’s next node by doing L = (L+1)/2. Now, if L is even then it is the left child of it’s parent and interval includes it’s parent also unless the right borders interferes. Similar conditions is applied to the right border also for faster computation. We will stop this iteration once the left and right borders meet.

The theoretical time complexities of both previous implementation and this implementation is same but practically this is found to be much more efficient as there are no recursive calls. We simply iterate over the elements that we need. Also this is very easy to implement.

Time Complexities:

References:
http://codeforces.com/blog/entry/18051

This article is contributed by Striver. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.




Improved By : vt_m



Article Tags :
Practice Tags :