Fenwick Trees, also known as Binary Indexed Trees (BIT), are an elegant and efficient data structure used to perform range queries and updates on an array. Originally designed by Peter Fenwick in 1994, these trees have found applications in various fields, ranging from computational geometry to competitive programming. In this blog post, we’ll explore the fundamentals of Fenwick Trees, understand their construction, and delve into practical examples.
Understanding Fenwick Trees
At its core, a Fenwick Tree is designed to provide a fast and memory-efficient solution for cumulative frequency queries on an array. It excels at answering questions like “What is the sum of elements from index 1 to i?” efficiently.
Basic Concepts
1. Binary Indexed Tree Structure:
- A Fenwick Tree is represented as an array
BIT[]
, where each elementBIT[i]
stores the cumulative sum of a specific range.
2. Construction:
- The construction of a Fenwick Tree involves updating each element based on its contribution to different ranges.
3. Query Operation:
- Fenwick Trees efficiently answer range sum queries using a recursive and bitwise approach.
4. Update Operation:
- Updating an element in a Fenwick Tree involves modifying all the affected ranges efficiently.
Constructing a Fenwick Tree
Let’s walk through the step-by-step construction of a Fenwick Tree for an array arr[]
.
- Initialize the Fenwick Tree:
BIT = [0] * (len(arr) + 1)
- Update Function:
def update(index, value): while index <= len(arr): BIT[index] += value index += index & -index
- Construction:
def construct_fenwick_tree(arr): for i in range(1, len(arr) + 1): update(i, arr[i - 1])
- Query Function:
def query(index): result = 0 while index > 0: result += BIT[index] index -= index & -index return result
Practical Example
Let’s consider an array arr = [3, 2, -1, 6, 5, 4, -3, 3, 7, 2, 3]
and construct a Fenwick Tree for it.
# Constructing the Fenwick Tree
arr = [3, 2, -1, 6, 5, 4, -3, 3, 7, 2, 3]
BIT = [0] * (len(arr) + 1)
def update(index, value):
while index <= len(arr):
BIT[index] += value
index += index & -index
def construct_fenwick_tree(arr):
for i in range(1, len(arr) + 1):
update(i, arr[i - 1])
# Querying the sum of elements from index 1 to 6
result = query(6) - query(0)
print(result) # Output: 19
In this example, we construct a Fenwick Tree for the given array and then query the sum of elements from index 1 to 6 efficiently.
Conclusion
Fenwick Trees offer a powerful and versatile solution for range queries and updates. Their simplicity and efficiency make them valuable in scenarios where cumulative frequency operations are frequent. As you explore and apply Fenwick Trees, you’ll uncover their ability to optimize various algorithms and computations. Happy coding!