Fenwick Tree or BITree is a very popular data structure mostly used for solving range query questions. The specialty of Fenwick Tree is that it can calculate the values of any function f in the given range [ 1 : r ] i.e.

f ( arr[1] , arr[l+1] , . . arr[r-1] , arr[r] )

If f is a reversible function then we can calculate this for any range [ l : r ]

To understand the BITree we will look into one of its most basic questions and consider f as a function that calculates the sum of the elements.

Question:

You are given an array arr[1…n]. You would be asked some queries which can be of two types :

Given an index x, find the sum of elements in the array from index 1 to index x ( inclusive ). Given an index x and a value val, add the value to arr[x].

Solution:

A trival solution would be to have the update operation in O(1) and to calculate the sum of elements, just run a loop in O(N). Or you can have a prefix array that will store the prefix sum, using this you can answer the queries in O(1) but the update will take O(N). To solve this problem we will see how we can use the BITree data structure which supports both the update and the query operation in O(log(N)), where N is the size of the array.

Intuition:

The basic idea behind the BITree is that any number can be represented as the sum of powers of 2. In the same manner, we can express a range ( 1 to x ) as the sum of other successive sub-ranges ( which are stored in sizes of powers of 2 ). It would be more clear once you read the following paragraph and see the diagram.

Consider the array ( note that the array is considered to be 1 based ):

How BITree works is, at any index idx of the tree the value stored would be the sum of values from idx -2^r + 1 to idx ( inclusive ). Here r represents the position of the least significant bit of idx. The idx 0 is kept zero in BITree.

Ex: consider index 6, it's binary representation would be 110. The least significant set bit is at pos 1. The so BIT[6] store the information about the range 6 - 2¹ + 1 = 5 to 6. Similarly, for all the indexes we can calculate and construct the following table which would tell what values are stored in these indexes:

The cumulative sums that are stored in the BITree

From the given diagram it is pretty much clear how the data is stored in the tree. Now suppose we have to find the sum for range 1 to 7. From the diagram, this range sum can be expressed as BIT[7] + BIT[6] + BIT[4]. If we represent these numbers in binary we can see that 7 is 111, 6 is 110 and 4 is 100. Similarly, the sum for range 1 to 9 would be BIT[9] + BIT[8]. 9 and 8 in binary form would are 1001 and 1000 respectively. We are removing the rightmost set bit continuously and adding those values to get the final answer.

You may be wondering why is it called tree. The reason is that the larger segment is considered as the parent of the smaller one ( the ones completely inside it). To move to the parent of a segment we do idx + (idx & -idx) [ Adding the LSB repeatedly]. We can do the reverse and go to the children.

Implementation:

Note: The implementation of the BITree is done in C++. Most of the functions don’t take BIT or arr as an argument, consider that both of them are declared globally as an array, the size of BIT is N+1 and array is N. This is not a good practice to declare them as global and then use them, the correct way would be to make a class or struct for Fenwick Tree. The array and queries are 0-index based. The proper complete implementation can be found here.

Before diving into the implementation part, a little knowledge of bit manipulation is necessary, more precisely the how to get the rightmost set bit.

Any number can be considered of the form: (pre)1(zeros), where the pre part is the bits to the left of the rightmost set bit. Example: 10001100, the number can be represented as (10001) pre 1 (00) zeros.

The negative of any number is stored as ~(num) + 1. The ~ represents an inversion of all zeros to ones and ones to zeros. So the negation of (pre)1(zeros) would become (negation of pre)0(ones). After adding 1 to it, it becomes (neg of pre)1(zeros). Now if you and this and the original number, only the rightmost set bit will remain and others will become 0.

the number stored in 2’s complement form becomes the following :

10001100 = ( 01110011 + 1) = 01110100

anding them results in

10001100 & 01110100 = 00000100

We are now ready to code the BITree.

Range Sum:

As already discussed, keep removing the LSB and add the values at that index in the Tree to get the sum.

If the given input is 5, then BIT[5] (iteration 1)+ BIT[4] (iteration 2) will be returned as the sum. The loop will remove the rightmost set bit until the idx doesn’t have any more set bits. Hence the time complexity would be the number of bits, which is log(N). Hence the sum can be calculated in O(log(N)).

Point Update:

To perform the update on the tree, just keep on adding the value to all the segments where this point lies. The operation is just the reverse of the sum operation:

Here N stands for the maximum size of the Tree or the asked index, i.e the size of the array. Consider the update of idx 5. The value of idx will change from 5 to 6, then to 8, then to 16 ( it will stop here because of the size of our array is only 10 ).

Here the idx is being updated until the idx becomes greater than N. The least significant bit is added, so the time complexity of the update operation is O(log(N)).

Building :

Function to build the tree, we simply consider that the array is initially empty ( all zeroes ) and all the values are being added one by one.

The time complexity of building the tree is O( N * log N )

Answer for a given range:

The answer the queries for any given range [ l : r ] we can simply use

sum[ r ] - sum[ l - 1 ]

You can now probably guess why it was specifically mentioned that f needs to be an invertible function to have range queries possible. Due to this reason, it is not possible to construct a single BITree that would give you range minimum queries for a given range. Range Minimum Queries can be implemented using two different BITrees , the implementation is a bit difficult and it is advisable to use segment trees in these cases.

Extending it to a 2D array:

BITrees can be used to get the cumulative sum of elements for a given matrix. The cumulative sum here refers to sum of all the elements arr[i][j] such that 1 ≤ i ≤ x and 1 ≤ j ≤ y. Below is the code to get the sum of elements in the matrix [ 1: 1] to [ x : y ]

2D Range Sum:

2D Point Update:

The complexity of both would be O(log(N)*log(M)). Where N represents the number of rows and M represents the number of columns.

Variations in BITree:

In the above example, we saw how to do point update and range query. Other than this we can also have point queries with range updates or range updates with range queries. The former is easy to implement but the later needs two BITrees to solve the problem. It is sufficient to know up till here and you can skip the following part and directly go to the conclusion. It is always advisable to use segment trees if range updates are there or more complex information is needed to combine a segment or if the function is non-invertible.

1. Range Update and Point Query

Here the update of adding val to range l to r is done as add +val to l, and add -val to r+1. Take cumulative sum 1 to N, you will notice that the updated value will be in the picture only when you are in the range l to r. We use this observation to answer Range Update and Point Query question. The code given below should be fairly obvious by now.

2. Range Query and Range Update

To perform range updates, we need to have some observations first. Suppose you add val from index between l to r. Initially, the entire array was zero. So after this update operation, the sum from 1 to some idx could be written as

It can be re-written as:

We use two BITrees. The BIT1 has the update of +x on l and -x on r+1 ( the same way as range update and point query). But here we have some extra term, to remove that extra term we use BIT2, this has the update of +val*(l-1) on l and -val*(r) on r+1.

Conclusion

The Fenwick tree requires O(n) memory and O(log(N)) for range update and query. Fenwick tree mainly used for range query and point update, but depending on the evaluation function it could be modified to range updates. It is very easy to implement and can be extended for n dimensions. Fenwick tree is faster than the Segment tree but is not as powerful as the Segment tree. It can only give cumulative values, for getting a range query, the inverse operation should exist or the implementation would become tedious.

Source

There are some good problems to solve at the end of link 1.

If you wanna learn about segment trees :)