8.14 Fenwick Trees
Not every range-query problem requires the full power of a Segment Tree.
8.14 Fenwick Trees
Not every range-query problem requires the full power of a Segment Tree.
Many practical workloads involve only two operations:
- Update a single value.
- Query a prefix sum.
Examples include:
- Running totals
- Frequency counts
- Event counters
- Rankings
- Histogram maintenance
- Subtree sums after Euler Tour flattening
For these cases, a Fenwick Tree provides an elegant alternative. It uses less memory, requires less code, and often performs better in practice because of its compact representation.
The Fenwick Tree, also known as a Binary Indexed Tree (BIT), is one of the most useful data structures for cumulative-frequency problems.
Problem
You need efficient updates and sum queries on an array.
Operations:
add(index, value)
sum(left, right)
A naive implementation scans the affected range.
With many updates and queries, performance degrades quickly.
Solution
Store partial sums in a Binary Indexed Tree.
Consider:
Index: 1 2 3 4 5 6 7 8
Value: 5 2 7 1 4 3 8 6
A Fenwick Tree stores selected cumulative ranges.
Instead of storing every interval explicitly, it uses binary properties of indices to determine which ranges each entry represents.
Implementation:
package main
type Fenwick struct {
tree []int
}
func NewFenwick(n int) *Fenwick {
return &Fenwick{
tree: make([]int, n+1),
}
}
The structure uses one-based indexing.
This simplifies the bit manipulations that make the data structure work.
Discussion
The most important operation in a Fenwick Tree is:
index & -index
This expression extracts the least significant set bit.
Examples:
| Index | Binary | index & -index |
|---|---|---|
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 1 |
| 4 | 0100 | 4 |
| 6 | 0110 | 2 |
| 8 | 1000 | 8 |
This value determines the interval size represented by a node.
For example:
| Position | Covers |
|---|---|
| 1 | [1,1] |
| 2 | [1,2] |
| 4 | [1,4] |
| 8 | [1,8] |
| 6 | [5,6] |
The structure forms an implicit tree without storing explicit child pointers.
Point Updates
To add a value:
func (f *Fenwick) Add(
index int,
delta int,
) {
for index < len(f.tree) {
f.tree[index] += delta
index += index & -index
}
}
Example:
bit.Add(3, 7)
The update affects all ranges containing position 3.
The bit manipulation automatically identifies those ranges.
For index 3:
3
→ 4
→ 8
→ 16
...
Each affected node receives the update.
Prefix Sums
To compute:
sum(1,index)
move upward through the implicit structure.
func (f *Fenwick) PrefixSum(
index int,
) int {
result := 0
for index > 0 {
result += f.tree[index]
index -= index & -index
}
return result
}
Example:
bit.PrefixSum(7)
Traversal:
7
→ 6
→ 4
→ 0
The collected intervals exactly cover:
[1,7]
without overlap.
Range Sums
Once prefix sums exist, range sums become simple.
func (f *Fenwick) RangeSum(
left int,
right int,
) int {
return f.PrefixSum(right) -
f.PrefixSum(left-1)
}
Example:
sum(3,7)
becomes:
prefix(7)
-
prefix(2)
This transformation is one of the most important ideas in cumulative-sum algorithms.
Building a Fenwick Tree
The simplest construction inserts values individually.
bit := NewFenwick(n)
for i, value := range values {
bit.Add(i+1, value)
}
Complexity:
O(n log n)
A more advanced linear-time construction exists, but the incremental version is usually sufficient and easier to understand.
Visualizing the Structure
Suppose:
Index: 1 2 3 4 5 6 7 8
The intervals look like:
1 -> [1]
2 -> [1,2]
3 -> [3]
4 -> [1,4]
5 -> [5]
6 -> [5,6]
7 -> [7]
8 -> [1,8]
Notice how powers of two represent increasingly larger ranges.
This organization allows logarithmic traversal.
Why the Bit Trick Works
Consider:
index = 12
1100₂
The least significant set bit:
0100₂
equals:
4
Subtracting it:
12 - 4 = 8
moves to the next ancestor interval.
Adding it:
12 + 4 = 16
moves to the next larger covering interval.
The binary representation naturally encodes the tree structure.
No explicit pointers are required.
Frequency Counting
Fenwick Trees are frequently used for frequency tables.
Suppose:
Value
-----
3
1
5
3
2
Store counts:
bit.Add(value, 1)
Now:
How many values ≤ X?
becomes:
bit.PrefixSum(X)
This technique appears in:
- Ranking systems
- Order statistics
- Inversion counting
- Streaming analytics
Inversion Counting
Consider:
3 1 2
Inversions:
(3,1)
(3,2)
Answer:
2
Process values from left to right.
For each element:
Previously seen values
greater than current
can be computed using Fenwick queries.
The resulting algorithm runs in:
O(n log n)
instead of:
O(n²)
This is one of the classic Fenwick Tree applications.
Euler Tour and Subtree Sums
Fenwick Trees pair naturally with Euler Tours.
Suppose:
Subtree(node)
corresponds to:
[start,end]
in Euler order.
Subtree sum:
bit.RangeSum(start, end)
Point update:
bit.Add(position, delta)
The original tree disappears.
The problem becomes a sequence problem.
This combination appears frequently in competitive programming and large-scale hierarchy systems.
Fenwick Trees vs Prefix Sums
Prefix sums support:
Range query
in:
O(1)
but updates require:
O(n)
recomputation.
Fenwick Trees support:
Query: O(log n)
Update: O(log n)
This tradeoff is often worthwhile when updates occur frequently.
Fenwick Trees vs Segment Trees
Both structures support logarithmic operations.
However, they target different workloads.
| Feature | Fenwick Tree | Segment Tree |
|---|---|---|
| Prefix sums | Excellent | Excellent |
| Range sums | Excellent | Excellent |
| Point updates | Excellent | Excellent |
| Range minimum | Difficult | Natural |
| Range maximum | Difficult | Natural |
| Custom aggregates | Limited | Flexible |
| Memory | O(n) | O(n) |
| Constant factors | Smaller | Larger |
| Implementation | Simpler | More complex |
Fenwick Trees are specialized.
Segment Trees are more general.
When sums are sufficient, Fenwick Trees are often preferable.
Two Fenwick Trees
A surprising extension supports range updates.
Instead of storing:
point changes
store:
difference changes
Using two Fenwick Trees, it becomes possible to perform:
Range update
Range query
in logarithmic time.
The technique relies on the same prefix-sum principles discussed earlier in Chapter 2.
This illustrates how multiple algorithmic ideas often combine into more powerful structures.
Complexity Analysis
Let:
n = number of elements
Operations:
| Operation | Complexity |
|---|---|
| Add | O(log n) |
| Prefix sum | O(log n) |
| Range sum | O(log n) |
Memory:
| Structure | Complexity |
|---|---|
| Fenwick Tree | O(n) |
The logarithmic factor comes from repeatedly removing the least significant set bit.
Each operation visits at most:
O(log n)
indices.
Common Mistakes
A common mistake is using zero-based indexing.
Fenwick Trees are usually implemented with:
1-based indexing
because the bit operations depend on it.
Another mistake is forgetting:
index += index & -index
during updates.
Using subtraction instead of addition moves in the wrong direction.
A third mistake is attempting to compute minimum or maximum values directly. Fenwick Trees naturally support invertible cumulative operations such as addition. Arbitrary aggregates generally require Segment Trees.
Finally, many bugs arise from incorrect interval formulas:
sum(left,right)
=
prefix(right)
-
prefix(left-1)
The subtraction of left-1 is essential.
Takeaway
Fenwick Trees provide an elegant solution for dynamic cumulative-sum problems. By exploiting binary properties of indices, they support updates and prefix queries in logarithmic time while using only linear memory. Although less flexible than Segment Trees, they are simpler, faster, and often the best choice when the primary operation is summation. Combined with Euler Tours, Fenwick Trees become a powerful tool for subtree aggregation and dynamic hierarchy queries.