![]() |
VOOZH | about |
Binary Indexed Tree (BIT), also known as a Fenwick Tree, is used in scenarios where we need to efficiently perform multiple operations on a fixed-size array. These operations typically include:
Let us consider the following problem to understand Binary Indexed Tree.
We have an array, and we would like to:
A simple solution involves iterating through the array (O(n) time) to calculate the sum, with updating a value being O(1). Alternatively, an auxiliary array can store cumulative sums, enabling O(1) range sum calculation but O(n) updates. This is suitable for many queries and few updates.
One efficient solution is Segment Trees, performing both operations in O(log n) time. Another alternative is Binary Indexed Trees, also achieving O(log n) time complexity. Compared to Segment Trees, Binary Indexed Trees require less space and are easier to implement.
We build the Binary Indexed Tree by inserting each element of the array one by one using the update() function. For every index i, we add arr[i] to the BIT, and this value gets propagated to all the indices that are responsible for storing ranges including i. Since each update operation takes O(log N) time (due to movement using the rightmost set bit), performing it for all N elements results in an overall time complexity of O(N log N).
Note: The Binary Indexed Tree can also be constructed in O(N) time using an optimized approach, which is discussed in the Competitive Programming section below.
getSum(x): Returns the sum of the sub-array [0,x].
- Initialize the output sum as 0, the current index as x+1.
- While the current index is greater than 0, add BIT[index] to sum and go the parent of BIT[index].
- The parent can be obtained by removing the last set bit from the current index i.e. index = index - (index & (-index)).
- Return sum.
Here are some key observations:
update( x , delta ): Updates the BIT by increasing the value at index i (and all its responsible ancestors) by delta.
- Initialize the current index as x+1.
- While the current index is smaller than or equal to n, add delta to BIT[index] and go to the next element of BIT[index].
- The next element can be obtained by incrementing the last set bit of the current index i.e. index = index + (index & (-index)).
The following is a representation of the update() and getSum() operation in a BIT, illustrating how the bitwise representation changes as we traverse up or down the structure.
The update function ensures that all BIT nodes containing arr[i] within their ranges are updated. This is achieved by iterating through these nodes in the BIT, repeatedly adding the decimal value of the last set bit of the current index.
A Binary Indexed Tree works using the binary (base-2) form of numbers to store sums in a smart way. Each index in the BIT keeps the sum of a group of elements, and the size of this group is decided by the rightmost set bit of that index (found using i & (-i)).
For example, to find the sum of the first 12 elements, we can split it based on its binary form 1100, which means 12 = 8 + 4. So instead of adding all elements one by one, we combine the sum of the first 8 elements and the next 4 elements. The Binary Indexed Tree stores these partial sums in advance, so we can quickly combine them when needed. This way of breaking numbers into powers of 2 is what makes the Binary Indexed Tree efficient and easy to traverse.
Sum of elements in arr[0..5] is: 12 Sum of elements in arr[0..5] after update is: 18
Time Complexity: O(N x logN) because we call the update() function for each of the N elements, and each update takes O(log N) time as it moves through multiple indices using the rightmost set bit.
Auxiliary Space: O(N) as we use an additional BIT[] array of size N + 1 to store the tree.
Note: If we use the optimized construction method (discussed in the Competitive Programming section), we can build the Binary Indexed Tree in O(N) time instead of O(N log N).
We build the Binary Indexed Tree in O(N) time by avoiding repeated updates. Instead of propagating each element multiple times, we directly transfer the accumulated contribution of each index to its parent. For every index i, we first store its value in F[i], and then add this value to its parent index i + (i & (-i)). Since F[i] already represents the sum of a specific range, pushing it once to its parent ensures all required contributions are handled efficiently without redundancy.
The concept of a Binary Indexed Tree can be extended from a 1-D array to higher dimensions like 2-D or even 3-D arrays. Instead of storing prefix sums for a single range, we now store sums over a submatrix (or sub-region).
In a 2-D Binary Indexed Tree, each cell stores the sum of a rectangular region of the matrix. The idea is similar to the 1-D case: we break the matrix into smaller blocks based on binary representation and store partial sums so that queries and updates can be done efficiently.
For example, to find the sum of elements in a submatrix, we use a prefix-based approach:
This follows the formula: sum(x1, y1, x2, y2) = sum(x2,y2) - sum(x1-1,y2) - sum(x2,y1-1) + sum(x1-1,y1-1)
Similarly, this idea can be extended to 3-D arrays, where each index stores the sum of a 3D sub-region, and the same prefix-based logic is applied.
A Binary Indexed Tree is widely used in problems that involve frequent updates and prefix-based queries. Some common use cases include: