一、什么是树状数组?
二、树状数组的两个基本操作
三、树状数组的特点
- 树状数组用于求解给定数组中任意区间的累计频率(前缀和、区间和)问题,支持单点更新,不支持增加和删除元素。
- 树状数组的本质是一个数组,根据元素的索引建立起“逻辑上的”树形结构,元素间便有了联系,所以也叫二叉索引树。这里的“索引”可以理解为树形结构就是一种索引结构,也可以理解为,树状数组是基于元素的索引建立关系的。
四、代码实现
/**
*
* <p>树状数组或二叉索引树(英语:Binary Indexed Tree),以其发明者命名为Fenwick树。
* 其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,
* 现多用于高效计算数列的前缀和, 区间和。</p>
*
* <p>虽然BIT的名字是“二叉索引树”,实际上我们并没有使用一个“树”的结构去存储BIT对象,
* 而是将其放在一个数组中,所以这种结构被称为“树状数组”。
* 许多树结构也使用了这样的形式,比如二叉堆。</p>
*
* @author edu.lagou.com
*/
public class BinaryIndexedTree {
int[] BIT;
int len;
/**
* 初始化BIT。
* 需要先调用{@link #update(int, int)}填充数据之后,
* 才能使用{@link #getSum(int)}方法
*
* @param length 数组长度
*/
public BinaryIndexedTree(int length) {
len = length;
// 将BIT数据存储到索引[1, len]上
BIT = new int[len + 1];
}
/**
* 初始化BIT。
* 默认操作:将给定数组元素的前缀和填充到BIT中。
* 可根据需要重写{@link #init(int[])}方法来改变此行为。
*
* @param nums 给定数组
*/
public BinaryIndexedTree(int[] nums) {
this(nums.length);
init(nums);
}
/**
* 记录给定数组的前缀和。
* 子类根据实际业务重写此方法。
*/
public void init(int[] arr) {
for (int i = 0; i < arr.length; i++) {
update(i, arr[i]);
}
}
public int update(int i, int k) {
// 将数据存储到加1位置上
i = i + 1;
// 更新节点个数
int c = 0;
while (i <= len) {
BIT[i] += k;
i += lowBit(i);
c++;
}
return c;
}
public int getSum(int i) {
// 从i加1位置上取数据
i = i + 1;
int res = 0;
while (i > 0) {
res += BIT[i];
// 定位下一个关键节点
i -= lowBit(i);
}
return res;
}
/**
* 获取给定整数的最低位 1 所代表的值。
* 比如:
* 整数1:返回二进制表示(0000 0001)中最后的 1 的十进制值 1。
* 整数10:返回二进制表示(0000 1010)中最后的 1 的十进制值 2。
* 运算过程如下:
* 1 & -1 = 1
* 0000 0001
* & 1111 1111
* = 0000 0001
*
* 10 & -10 = 2
* 0000 1010
* & 1111 0110
* = 0000 0010
* @param x
* @return
*/
public int lowBit(int x) {
// 等价于:x & (~x + 1)
return x & (-x);
}
}