树状数组简介
树状数组(Binary Index Tree, BIT)可以以 O ( l o g n ) O(logn) O(logn)的时间复杂度实现单点修改、区间查询等功能。
一、关键要点
1. lowbit操作
用于获取某数的最低位的1,计算公式为:
x
&
(
x
+
1
)
x \& (~x+1)
x&( x+1) 或
x
&
(
−
x
)
x \& (-x)
x&(−x)
- 设 x x x为20,则其二进制表示为10100
- 则 x + 1 ~x+1 x+1为01011+1 --> 01100
- 可见, x x x和 x + 1 ~x+1 x+1最低位的1左边每一位都不同(包括符号位),而最低位的1以及其右边每一位都相同。
- 两者相与,则只有 x x x最低位的1会保留下来
lowbit 操作在树状数组中及其重要
2. 数组中元素的实质
设原数组为 n u m s nums nums,树状数组为 t r e e tree tree,为了方便取值,使两个数组的下标均从1开始。则:
- 若 i i i的二进制表示为 ∗ ∗ ∗ 100 ***100 ∗∗∗100
- 则 t r e e [ i ] tree[i] tree[i]是nums中第 ∗ ∗ ∗ 100 ***100 ∗∗∗100, ∗ ∗ ∗ 001 ***001 ∗∗∗001, ∗ ∗ ∗ 010 ***010 ∗∗∗010, ∗ ∗ ∗ 011 ***011 ∗∗∗011 个元素的和
换一种方式描述
- 若取出 i i i的最低位1的结果为 a a a
- 则 t r e e [ i ] tree[i] tree[i]= n u m s [ i ] + n u m s [ i − 1 ] + n u m s [ i − 2 ] + . . . + n u m s [ i − a + 1 ] nums[i]+nums[i-1]+nums[i-2]+...+nums[i-a+1] nums[i]+nums[i−1]+nums[i−2]+...+nums[i−a+1]
3. 树状数组的结构
二、Java实现
public class BinaryIndexTree {
private int[] tree;
private int[] nums;
public BinaryIndexTree(int[] nums) {
this.nums = nums;
// 数组长度+1,避免采用0下标
this.tree = new int[nums.length+1];
for(int i = 0; i<nums.length; i++){
this.insert(i,nums[i]);
}
}
/**
* @param index:
* @param val:
* @return: void
* @author: lihen
* @date: 2022/5/18 15:55
* @description: 向空数组中加入数据,生成树状数组
*/
private void insert(int index, int val){
// nums索引转为tree索引
index++;
for(int i = index; i<tree.length; i+=lowBit(i)){
this.tree[i]+=val;
}
}
/**
* @param index:
* @param val:
* @return: void
* @author: lihen
* @date: 2022/5/18 15:55
* @description: 更新数组中某位置的数
*/
public void update(int index, int val) {
try {
checkIndex(index);
// nums索引转为tree索引
index++;
// 更新树状数组和原数组
for(int i = index; i<tree.length; i+=lowBit(i)){
this.tree[i] = this.tree[i] + val - nums[index-1];
}
nums[index-1] = val;
} catch (Exception e) {
System.out.println("下标越界,修改失败");
}
}
/**
* @param left:
* @param right:
* @return: int
* @author: lihen
* @date: 2022/5/18 15:56
* @description: 获取某个索引位置到另一位置的数之和
*/
public int sumRange(int left, int right) {
try {
checkIndex(left,right);
if(right<left)return -1;
// 分别查询0到left-1和0到right的元素和,再进行相减
return query(right)-query(left-1);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* @param index:
* @return: int
* @author: lihen
* @date: 2022/5/18 15:59
* @description: 统计原数组中0到index的元素的和
*/
public int query(int index){
// nums索引转为tree索引
index++;
int res = 0;
for(int i = index; i>0; i-=lowBit(i)){
res+=this.tree[i];
}
return res;
}
/**
* @param indexes:
* @return: void
* @author: lihen
* @date: 2022/5/18 16:06
* @description: 检查是否出现越界错误
*/
private void checkIndex(int ... indexes) throws Exception {
for (int index : indexes) {
if(index<0||index>=nums.length)throw new Exception();
}
}
/**
* @param x:
* @return: int
* @author: lihen
* @date: 2022/5/18 15:07
* @description: 取最低位的1
*/
int lowBit(int x){
return x&(-x);
}
}
总结
树状数组是一种十分优雅的数据结构,自己现在对它的了解还不够深,之后还要多接触相关题目,把树状数组练会练精。