二叉索引树(Fenwick Tree)
字数 1309 2025-11-22 09:48:39

二叉索引树(Fenwick Tree)

二叉索引树(又称树状数组)是一种高效处理动态前缀和查询与单点更新的数据结构,其时间复杂度为O(log n),空间复杂度为O(n)。下面我们分步骤讲解它的核心原理和操作。

一、问题背景
假设有一个数组arr,需要频繁执行两种操作:

  1. 单点更新:将arr[i]增加某个值
  2. 前缀和查询:求arr[0]到arr[i]的和
    若直接使用数组,单点更新O(1),但前缀和查询需要O(n)。Fenwick树通过巧妙的二进制索引设计,将两种操作都优化到O(log n)。

二、核心思想——低位技术(Lowbit)
定义lowbit(x)为x的二进制表示中最低位的1所对应的值,例如:

  • lowbit(6)=2(6的二进制110,最低位1表示2)
  • lowbit(8)=8(二进制1000)
    数学上lowbit(x)=x & -x(利用补码性质)

三、树状数组的存储结构

  1. 定义树状数组tree,大小与原数组arr相同(索引从1开始更易理解)
  2. tree[i]存储的是原数组arr中区间[i-lowbit(i)+1, i]的和
    • 例如i=6(二进制110),lowbit=2,则tree[6]=arr[5]+arr[6]
    • i=8(二进制1000),lowbit=8,则tree[8]=arr[1]+arr[2]+...+arr[8]

四、前缀和查询(getSum)
求前i项和sum(i)的步骤:

  1. 初始化sum=0
  2. 从i开始,每次将tree[i]加入sum
  3. 将i减去lowbit(i):i = i - lowbit(i)
  4. 重复步骤2-3直到i=0

示例:求前7项和(i=7)

  • 7的二进制111,lowbit=1:加tree[7](仅arr[7])
  • 7-1=6,lowbit=2:加tree[6](arr[5..6])
  • 6-2=4,lowbit=4:加tree[4](arr[1..4])
  • 4-4=0:结束。sum=tree[7]+tree[6]+tree[4]

五、单点更新(update)
更新arr[i]增加delta时:

  1. 从i开始,每次将tree[i]增加delta
  2. 将i加上lowbit(i):i = i + lowbit(i)
  3. 重复直到i超出数组范围

示例:更新arr[5](i=5)

  • 5的二进制101,lowbit=1:更新tree[5]
  • 5+1=6,lowbit=2:更新tree[6]
  • 6+2=8,lowbit=8:更新tree[8]
  • 继续直到边界

六、初始化构建

  1. 初始化tree全0
  2. 对每个arr[i]执行update(i, arr[i]),时间复杂度O(n log n)
  3. 优化方法:可直接用O(n)建树,通过子节点累加至父节点

七、与线段树对比

  • 优点:代码简洁、常数小、节省空间
  • 缺点:仅支持前缀和操作(但可通过变形支持区间更新)
  • 线段树功能更通用但实现复杂

通过这种基于二进制索引的设计,Fenwick树以极小的开销实现了动态前缀和的高效维护,是处理频繁更新和查询场景的经典解决方案。

二叉索引树(Fenwick Tree) 二叉索引树(又称树状数组)是一种高效处理动态前缀和查询与单点更新的数据结构,其时间复杂度为O(log n),空间复杂度为O(n)。下面我们分步骤讲解它的核心原理和操作。 一、问题背景 假设有一个数组arr,需要频繁执行两种操作: 单点更新:将arr[ i ]增加某个值 前缀和查询:求arr[ 0]到arr[ i ]的和 若直接使用数组,单点更新O(1),但前缀和查询需要O(n)。Fenwick树通过巧妙的二进制索引设计,将两种操作都优化到O(log n)。 二、核心思想——低位技术(Lowbit) 定义lowbit(x)为x的二进制表示中最低位的1所对应的值,例如: lowbit(6)=2(6的二进制110,最低位1表示2) lowbit(8)=8(二进制1000) 数学上lowbit(x)=x & -x(利用补码性质) 三、树状数组的存储结构 定义树状数组tree,大小与原数组arr相同(索引从1开始更易理解) tree[ i]存储的是原数组arr中区间[ i-lowbit(i)+1, i ]的和 例如i=6(二进制110),lowbit=2,则tree[ 6]=arr[ 5]+arr[ 6 ] i=8(二进制1000),lowbit=8,则tree[ 8]=arr[ 1]+arr[ 2]+...+arr[ 8 ] 四、前缀和查询(getSum) 求前i项和sum(i)的步骤: 初始化sum=0 从i开始,每次将tree[ i ]加入sum 将i减去lowbit(i):i = i - lowbit(i) 重复步骤2-3直到i=0 示例:求前7项和(i=7) 7的二进制111,lowbit=1:加tree[ 7](仅arr[ 7 ]) 7-1=6,lowbit=2:加tree[ 6](arr[ 5..6 ]) 6-2=4,lowbit=4:加tree[ 4](arr[ 1..4 ]) 4-4=0:结束。sum=tree[ 7]+tree[ 6]+tree[ 4 ] 五、单点更新(update) 更新arr[ i ]增加delta时: 从i开始,每次将tree[ i ]增加delta 将i加上lowbit(i):i = i + lowbit(i) 重复直到i超出数组范围 示例:更新arr[ 5 ](i=5) 5的二进制101,lowbit=1:更新tree[ 5 ] 5+1=6,lowbit=2:更新tree[ 6 ] 6+2=8,lowbit=8:更新tree[ 8 ] 继续直到边界 六、初始化构建 初始化tree全0 对每个arr[ i]执行update(i, arr[ i ]),时间复杂度O(n log n) 优化方法:可直接用O(n)建树,通过子节点累加至父节点 七、与线段树对比 优点:代码简洁、常数小、节省空间 缺点:仅支持前缀和操作(但可通过变形支持区间更新) 线段树功能更通用但实现复杂 通过这种基于二进制索引的设计,Fenwick树以极小的开销实现了动态前缀和的高效维护,是处理频繁更新和查询场景的经典解决方案。