数据结构之树状数组(Binary Indexed Tree, BIT)

张开发
2026/4/12 20:05:43 15 分钟阅读

分享文章

数据结构之树状数组(Binary Indexed Tree, BIT)
树状数组Binary Indexed Tree, BIT详解1. 引言树状数组Binary Indexed Tree, BIT是一种高效的数据结构由Peter M. Fenwick在1994年提出。它主要用于解决前缀和查询和点更新问题能够在O(log n)的时间复杂度内完成这两种操作。相比线段树树状数组实现更简单代码量更少但在功能上稍显局限。2. 基本概念2.1 核心思想树状数组的核心思想是将数组元素按照二进制位进行分组每个节点存储特定范围内元素的和。这种结构使得前缀和查询和点更新操作都能在O(log n)时间内完成。2.2 数据结构树状数组通常使用一个辅助数组tree[]来存储其中tree[i]存储原数组中从i - lowbit(i) 1到i的元素和。lowbit(i) i (-i)获取i的二进制表示中最低位的1及其后面的0组成的数3. 核心操作3.1 前缀和查询要查询数组前i个元素的和可以使用以下方法defquery(i):result0whilei0:resulttree[i]i-i-i# 移除最低位的1returnresult3.2 点更新要更新数组中第i个元素的值可以使用以下方法defupdate(i,delta):whilein:# n为数组大小tree[i]delta ii-i# 跳到下一个需要更新的节点4. 实现示例4.1 Python实现classBinaryIndexedTree:def__init__(self,nums):self.nlen(nums)self.tree[0]*(self.n1)foriinrange(self.n):self._update(i1,nums[i])def_update(self,i,delta):whileiself.n:self.tree[i]delta ii-idefupdate(self,index,val):deltaval-self.sumRange(index,index)self._update(index1,delta)defquery(self,i):result0whilei0:resultself.tree[i]i-i-ireturnresultdefsumRange(self,left,right):returnself.query(right1)-self.query(left)4.2 C实现classBinaryIndexedTree{private:vectorinttree;intn;public:BinaryIndexedTree(vectorintnums){nnums.size();tree.resize(n1,0);for(inti0;in;i){update(i,nums[i]);}}voidupdate(intindex,intval){intdeltaval-sumRange(index,index);for(intiindex1;in;ii-i){tree[i]delta;}}intquery(inti){intresult0;for(;i0;i-i-i){resulttree[i];}returnresult;}intsumRange(intleft,intright){returnquery(right1)-query(left);}};5. 时间复杂度分析操作时间复杂度空间复杂度构造O(n log n)O(n)点更新O(log n)-前缀和查询O(log n)-6. 应用场景6.1 典型应用频率统计统计数组中前i个元素的出现次数区间和查询快速计算任意子数组的和逆序对统计在排序算法中统计逆序对数量动态前缀和支持动态更新的前缀和计算7. 与其他数据结构的比较7.1 与线段树的比较特性树状数组线段树实现复杂度简单复杂代码量少多功能仅前缀和区间查询、区间更新空间复杂度O(n)O(n)构造时间O(n log n)O(n)7.2 与前缀和数组的比较特性前缀和数组树状数组更新时间O(n)O(log n)查询时间O(1)O(log n)适用场景静态数据动态数据8. 扩展应用8.1 二维树状数组对于二维前缀和问题可以使用二维树状数组classBinaryIndexedTree:def__init__(self,matrix):ifnotmatrixornotmatrix[0]:returnself.m,self.nlen(matrix),len(matrix[0])self.tree[[0]*(self.n1)for_inrange(self.m1)]foriinrange(self.m):forjinrange(self.n):self._update(i1,j1,matrix[i][j])def_update(self,row,col,delta):irowwhileiself.m:jcolwhilejself.n:self.tree[i][j]delta jj-j ii-idefquery(self,row,col):result0irowwhilei0:jcolwhilej0:resultself.tree[i][j]j-j-j i-i-ireturnresult8.2 离散化应用对于值域较大的数据可以先进行离散化处理再使用树状数组defdiscretization(arr):uniquesorted(set(arr))return{v:i1fori,vinenumerate(unique)}9. 性能优化技巧9.1 循环展开在关键路径上可以适当展开循环以减少分支预测失败intquery(inti){intresulttree[i];i-i-i;if(i0){resulttree[i];i-i-i;if(i0){resulttree[i];i-i-i;// 可以继续展开...}}returnresult;}9.2 内存布局优化确保数据在内存中连续存储以提高缓存命中率。10. 常见误区索引从1开始树状数组的实现通常从1开始索引而不是0边界处理注意数组越界问题特别是更新和查询时的边界条件负数处理lowbit操作对负数同样有效但需要注意符号位大数处理对于大数运算注意整数溢出问题11. 实际案例分析11.1 逆序对统计defcount_inversions(arr):# 离散化sorted_uniquesorted(set(arr))rank{v:i1fori,vinenumerate(sorted_unique)}bitBinaryIndexedTree([0]*len(sorted_unique))inversions0foriinrange(len(arr)-1,-1,-1):rrank[arr[i]]inversionsbit.query(r-1)bit.update(r,1)returninversions11.2 动态频率统计classFrequencyTracker:def__init__(self,nums):self.bitBinaryIndexedTree(nums)self.freq{}defupdate(self,index,val):old_valself.freq.get(index,0)self.bit.update(index,val-old_val)self.freq[index]valdefquery(self,left,right):returnself.bit.sumRange(left,right)12. 总结树状数组是一种优雅而高效的数据结构特别适用于需要频繁进行前缀和查询和点更新操作的场景。它的实现简单性能优秀是算法竞赛和实际应用中的重要工具。虽然功能上不如线段树全面但在特定场景下具有不可替代的优势。通过理解其底层原理和掌握正确的实现技巧开发者可以有效地利用树状数组解决各种复杂问题。

更多文章