数据结构:树状数组

树状数组

一、树状数组的定义

树状数组(Binary Indexed Tree,简称 BIT)是一种高效的数组结构,专门用于处理单点更新前缀和查询操作,也可扩展处理区间查询、区间更新等问题。

它的核心优势是实现简单、空间复杂度低,相较于线段树,树状数组代码更简洁,常数更小,适合处理一维的前缀和相关问题。

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

二、树状数组的核心原理

1. 二进制分解

树状数组的设计基于数字的二进制分解。对于一个正整数 x,其最低位的 1 对应的数值记为 lowbit(x),例如:

  • lowbit(6)(二进制 110)= 2(二进制 10)
  • lowbit(8)(二进制 1000)= 8(二进制 1000)

lowbit(x) 的计算方法:lowbit(x) = x & -x(利用补码特性,负数的二进制是原码取反加 1)。

2. 数组结构

树状数组用一个数组 tree 存储信息,tree[i] 表示原数组中从 i - lowbit(i) + 1i 这个区间的和(以区间和为例)。

  • 叶子节点对应原数组的单个元素;
  • 非叶子节点对应原数组的一段连续区间,区间长度由 lowbit(i) 决定。

三、树状数组的核心操作

1. 单点更新(Point Update)

将原数组中第 idx 位置的元素增加 val(也可扩展为修改为指定值),需要更新树状数组中所有包含 idx 的区间节点。

  • 操作逻辑:从 idx 出发,不断加上 lowbit(idx),直到超出数组长度,依次更新对应位置的 tree 值。

2. 前缀和查询(Prefix Sum Query)

查询原数组中从第 1 位到第 idx 位的前缀和(树状数组通常从 1 开始索引,避免处理 0 的 lowbit 问题)。

  • 操作逻辑:从 idx 出发,不断减去 lowbit(idx),直到为 0,累加对应位置的 tree 值。

3. 区间查询(Range Sum Query)

查询原数组中 [l, r] 区间的和,可通过前缀和推导:query(r) - query(l - 1)

四、时间复杂度

  • 单点更新:O(log n),每次更新最多遍历 log n 个节点;
  • 前缀和查询:O(log n),每次查询最多遍历 log n 个节点;
  • 区间查询:基于两次前缀和查询,时间复杂度仍为 O(log n)

五、树状数组的实现示例(以区间和为例)

python 复制代码
class BinaryIndexedTree:
    def __init__(self, data):
        # 原数组(从1开始索引)
        self.n = len(data)
        self.tree = [0] * (self.n + 1)
        # 初始化树状数组
        for i in range(1, self.n + 1):
            self.update(i, data[i - 1])
    
    def lowbit(self, x):
        # 计算最低位的1对应的数值
        return x & -x
    
    def update(self, idx, val):
        # 单点更新:将idx位置增加val(idx从1开始)
        while idx <= self.n:
            self.tree[idx] += val
            idx += self.lowbit(idx)
    
    def query(self, idx):
        # 查询前缀和:[1, idx](idx从1开始)
        res = 0
        while idx > 0:
            res += self.tree[idx]
            idx -= self.lowbit(idx)
        return res
    
    def range_query(self, l, r):
        # 查询区间和:[l, r](l、r从1开始)
        return self.query(r) - self.query(l - 1)

使用示例

python 复制代码
# 原数组(索引0开始)
data = [1, 2, 3, 4, 5]
# 初始化树状数组(内部自动转为1开始索引)
bit = BinaryIndexedTree(data)

# 查询前缀和:[1, 3](对应原数组[0,2])
print(bit.query(3))  # 输出6(1+2+3)

# 查询区间和:[2,4](对应原数组[1,3])
print(bit.range_query(2, 4))  # 输出9(2+3+4)

# 单点更新:将原数组索引3(树状数组索引4)的值加2(原4→6)
bit.update(4, 2)

# 再次查询区间和:[2,4]
print(bit.range_query(2, 4))  # 输出11(2+3+6)

六、树状数组的扩展应用

1. 区间更新 + 单点查询

通过"差分思想"实现:

  • 区间 [l, r]val:执行 update(l, val)update(r+1, -val)
  • 单点查询 idx:查询前缀和 query(idx)(差分的前缀和即为原数组值)。

2. 区间更新 + 区间查询

需要维护两个树状数组,结合差分公式推导,可实现区间加、区间和查询。

3. 其他统计场景

除了区间和,树状数组还可处理:

  • 区间最大值/最小值(需特殊处理,不如线段树灵活);
  • 逆序对统计(将数组离散化后,从后往前查询比当前数小的元素个数);
  • 频率统计(统计某个数值范围内的元素个数)。

七、树状数组与线段树的对比

特性 树状数组 线段树
实现难度 简单,代码量少 较复杂,代码量多
空间复杂度 O(n) O(4n)
单点更新 O(log n),常数小 O(log n),常数稍大
区间查询 仅擅长前缀和衍生的查询 支持任意区间统计(和、最值等)
区间更新 需差分扩展,场景有限 结合懒标记,支持任意区间更新

八、适用场景

  1. 一维数组的单点更新 + 前缀和/区间和查询(树状数组的最优场景);
  2. 逆序对、频率统计等基于前缀和的统计问题;
  3. 数据量较大,对代码简洁性和常数效率要求高的场景。
相关推荐
CoderYanger2 小时前
C.滑动窗口-求子数组个数-越短越合法——LCP 68. 美观的花束
java·开发语言·数据结构·算法·leetcode
EXtreme352 小时前
【C语言/数据结构】零基础打造控制台游戏:贪吃蛇实战教程----链表与Win32 API的完美结合!
c语言·数据结构·链表·贪吃蛇·宽字符·win32 api·控制台编程
AI科技星3 小时前
伟大的跨越:从超距作用到时空运动——牛顿与张祥前引力场方程的终极对比
开发语言·数据结构·经验分享·线性代数·算法
刘 大 望3 小时前
JVM(Java虚拟机)
java·开发语言·jvm·数据结构·后端·java-ee
阿拉伯柠檬4 小时前
实现一个异步操作线程池
开发语言·数据结构·c++·面试
小年糕是糕手4 小时前
【C++】内存管理(下)
java·c语言·开发语言·数据结构·c++·算法
CoderYanger4 小时前
第 479 场周赛Q2——3770. 可表示为连续质数和的最大质数
java·数据结构·算法·leetcode·职场和发展
草莓熊Lotso5 小时前
哈希表的两种灵魂:深入探索开放定址与链地址法的核心机密
linux·运维·数据结构·c++·人工智能·算法·哈希算法
自然常数e5 小时前
深入理解指针(5)
c语言·数据结构·visual studio