Fenwick Tree | Binary Indexed Trees
Learn via video course
Overview
Binary Indexed Tree is a data structure that is used to efficiently calculate the prefix sum of a series (array) of numbers. It is also known as the Fenwick tree as "Peter Fenwick" introduced it to the world through his paper
"A new data structure for cumulative frequency tables" first published in 1994.
Scope of the Article
- Idea and representation of the Binary Indexed Tree have been discussed in detail.
- Code of Fenwick Tree in three languages C, C++, and Java have been explained along with a bit-manipulation technique to optimize the process.
Introduction to Fenwick Tree
When answering to multiple prefix sum (sum of array elements from 0 to an index queries of an array, the first idea that comes to our mind is to use a prefix_sum array.
For Example:
Here if we want to calculate , we do not need to calculate it instead we can simply return _prefix\_sum[4]_ as it already contains the sum of those values.
Also, if we want to calculate the sum of a segment of the array,
For Example: then instead of calculating we can return prefix_sum[4] prefix_sum[1].
That means we can answer all such queries in constant time, but if we also need to update array elements frequently then, using prefix_sum array would be of no use because in case of updation we may need to re-construct the whole prefix_sum array.
In such cases, Binary Indexed Tree comes into the picture. A binary indexed tree popularly known as the Fenwick tree is a data structure that maintains the cumulative frequencies of the array elements at each of its nodes.
One of the best and simple use cases can be calculating the prefix sum of an array in which values are mutable (i.e. values can be changed) logarithmic time complexity.
Basic Idea of Fenwick Tree
If we want to calculate prefix_sum of array of size then we will use another array of size ( is to maintain 1-based indexing for simplicity).
The basic idea behind Binary Indexed Tree is that we can store the sum of a particular range of elements at an index of the newly formed array. Which will be used later to calculate prefix_sum in the fewer number of steps.
The idea that "every integer can be represented as the sum of powers of 2" is used extensively in the formation of the Fenwick tree. Using this fact we can calculate the _prefix\_sum_ of an array in logarithmic time complexity.
Representation of Fenwick Tree
The BIT (Binary Indexed Tree) is represented as an array (say ). Where each index of stores the sum of a range of elements of the original array (say $a$).
Let be an index of the array , then will contain the sum of elements KaTeX parse error: $ within math mode where is the position of the least significant set-bit in the binary representation of .
Let us understand by an Example: Let's say we want to know the range of which sum is stored in , we know that binary representation of is , we can see that the position of least significant set-bit from right, is .
That means will contain the sum of the range .
The above image represents the range of elements for which the sum is stored for any index i in the bit array of size 16.
For Example: index 12 in bit holds the sum of the values of the range present in a and index 8 in bit holds the sum of the values of the range in a. So to find the prefix_sum[12] we can simply find instead of calculating sum one by one.
Operations of Fenwick Tree
The two main operations supported by Fewick tree are -
- getSum(x) - This operation returns the sum of first elements of the array .
- update(x,val) - This operation updates the value of at index
We can also find the sum of any arbitrary range using the below-given function which uses getSum(x) to find the same.
getSumRange(left, right): This operation returns the sum of array elements within a range . It can be easily calculated using _getSum(x)_ operation by returning getSum(right+1)-getSum(left)
How does Fenwick Tree Works?
Before getting into the actual working of the Binary Indexed Tree (Fenwick Tree), let's first see we can find the least significant set-bit of any integer, say . This is a very crucial step because it plays a key role in implementing the Binary Indexed Trees.
Finding the least significant set-bit of
It is understood that for any , we can express it in the binary form , where is any arbitrary binary string consisting of both and and is a binary string of all because is the last set-bit present in the binary representation of . For example, the binary representation of is , if we write this in the form of then, will correspond to and will correspond to .
We have studied that in binary form represented as complement of and complement of is equal to . Now since , Since had contained all so will contain all . Adding 1 to will make it . Let's observe this phenomenon with the example of 20.
Representing it of the form will give and .
We can clearly see now contains all , and it is of the form with and .
Hence it is of the form , now if take of and we will get something of the form where and are binary strings with all a number with only one set bit because and was already zero.
To remove the least significant set-bit from we can subtract the result obtained of from .
For Example: and which is the same number that could be obtained by removing the least significant set-bit of 20.
As discussed in the previous sections we will not be representing the Fenwick tree in a "tree like structure" instead we will represent it as an array of size for any given array of size to maintain 1-based indexing. Let bit be the array that represents the fenwick tree of size . Then we can have the following operations on bit.
getSum(x) operation:
getSum(x) operation returns sum of array elements from index to index . Steps invloved to calculate it are -
- Initialize answer, ans with 0.
- Increase x by 1 (To maintain 1-based indexing).
- While x is greater than 0
- Return ans
update(x, val) operation
update(x, val) operation updates the value of array at index to . After updating, we also need to update array at those indices which get impacted by the element at index . , Steps involved in that are -
- Find the change happening at the index x, .
- Update the value at index x .
- While
When to Use Fenwick Tree?
Fenwick tree is mostly used when we have been given an array (say ) and we need to answer multiple getSum queries finding the sum of first elements many times or/and we need to find the sum of a range of elements many times, and also we need to perform multiple update operations updating the value of at any particular index .
Construction of Fenwick Tree
Let's say we have been given an array , of size . So we will create another array of size (say ) all of its entries will be initialized with 0. Then we will use the update function for each index of array . Its pseudocode will be like -
For Example: Let Then array will correspond to - where represents sum of range
Implementation
To implement the Fenwick tree (BIT) we will have the following global variables to maintain simplicity in the code.
- n- It denotes the size of the array on which we want to perform certain operations which are discussed in the previous sections.
- bit- It is the actual binary indexed tree that is represented in array format. Its size will be n+1 to maintain 1-based indexing.
The functions which are needed to implement functionalities of the binary indexed trees are :
- FindSum- It takes one argument (say ind) and we need to return the sum of values of the given array (say a) in the range [0, ind].
- SumOfRange- It takes two arguments (say left and right) and we will return the sum of values of a in the range [left, right]
- Update- It takes two arguments (sat ind and val) and we need to change the value of a at index ind to val and correspondingly we also need to update the bit.
Pseudocode of Fenwick Tree Implementation
Fenwick Tree Implementation in C/C++
Fenwick Tree Implementation in Java
Output: Sum of range 2-6 is 24. Sum of range 5-9 is 33.
Applications of Fenwick Tree
Fenwick Tree is mainly used to optimize the process of finding the prefix sum of the arrays such that we can have the prefix sum of an array index in logarithmic time complexity. However, there are some popular questions that can be efficiently solved using Fenwick trees:
-
Mutable Range Sum Queries: Q queries can be answered in time which is way better than run-time of brute force approach.
-
Count Inversions in an Array: The number of inversions can easily be found in time even without sorting the array.
-
Count smaller numbers after self: Many practical uses can be seen in the real world which can be computed efficiently using BIT.
Conclusion
- Binary Indexed Tree (Fenwick Tree) is an efficient data structure that can be used to quickly calculate the prefix sum of an array.
- If the values present in the array are mutable values can be changed using the Fenwick tree is of great use.
- Subtracting from , is the efficient way to remove the least significant set-bit from .
- Time Complexity to calculate prefix sum or range sum of the array of size is and extra space is required to store the array.