树状数组原理和实现
树状数组是一种维护数组前缀和、区间和的数据结构,思想和跳跃表有点类似。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。和Trie树的构造方式有类似之处。
建立索引
树状数组的一个节点索引的原始数据数量,与该节点编号在二进制下最低为的1有关
- 1,3,5,7… 二进制下以1结尾,仅索引1个数据(自身)
- 2,6,10,14…二进制下以10结尾,索引2个数据(自身,它前面的那个数)
- 4,12… 二进制下以100结尾,索引4个数据(自身,前面3个数)
二进制分解与lowbit
- 任意正整数可以唯一分解为若干个不重复的2次幂之和
- 7 = 22 + 21 + 20, 12 = 22 + 22
- lowbit(x) 定义为x二进制下最低为的1和后面0组成的数值(或者说x二进制分解下的最小次幂)
- lowbit(7) = lowbit(1112) = 12 = 20 = 1
- lowbit(12) = lowbit(11002) = 1002 = 22 = 4
- 树状数组c的节点c[x]存储x前面的lowbit(x)个数据(包括x)的和
- c[7] = a[7]
- c[12] = c[12] + c[11] + c[10] + c[9]
- 添加代码
void add(int x, int y) { // x y
for (; x <= N; x += x & -x) c[x] += y;
}
- 查询代码 时间复杂度O(logN)
int query(int x) { // x
int ans = 0;
for (; x > 0; x -= x & -x) ans += c[x];
return ans; }
各种树形数据结构的对比
数据结构 | 用途 | n次操作的时间复杂度 |
---|---|---|
并查集 | 关系维护、图(连通性)、利用路径压缩等 | O(n α(n)) |
Trie树 | 维护字典(多个字符串的存储和查询) | O(total length of string) |
二叉堆 | 最大、最小值的维护与查询 | O(n logn) |
树状数组 | 区间信息的维护与查询(需要区间减法性质) | O(n logn) |
线段树 | 区间信息的维护与查询 | O(n logn) |
平衡二叉搜索树 | 实现有序集合/映射区间的信息维护与查询(更加灵活) | O(n logn) |

解题
- 区域和检索 - 数组可修改
private int[] data;
private int n;
private int[] newNums;
public NumArray(int[] nums) {
//初始化
this.n = nums.length;
this.data = new int[this.n + 1];
this.newNums = new int[this.n + 1];
for (int i = 1; i <= nums.length; i++) {
this.newNums[i] = nums[i - 1];
this.add(i,newNums[i]);
}
// println();
}
public void update(int index, int val) {
index++;
int value = val - this.newNums[index];
this.add(index,value);
this.newNums[index] = val;
// println();
}
public int sumRange(int left, int right) {
int leftValue = query(++left - 1);
int rightValue = query(++right);
return rightValue - leftValue;
}
public void println(){
for (int i = 1; i < data.length; i++) {
System.out.println("i="+i+" val="+data[i]);
}
for (int i = 1; i < data.length; i++) {
System.out.println("in="+i+" newValue="+newNums[i]);
}
}
// 查询前缀和
public int query(int x) {
int sum = 0;
while(x > 0) {
sum += data[x];
x -= lowbit(x);
}
return sum;
}
// 添加
public void add(int x,int value){
while(x <= n ){
data[x] += value;
x += lowbit(x);
}
}
// lowbit算法
private int lowbit(int x){
return x & -x;
}
- 线段树解法
class NumArray {
private Node[] data;
public NumArray(int[] nums) {
data = new Node[4 * nums.length];
build(1,0,nums.length - 1,nums);
// for (int i = 0; i < data.length; i++) {
// System.out.println("i="+ i + " val = "+ data[i]);
// }
}
public void update(int index, int val) {
// for (int i = 0; i < data.length; i++) {
// System.out.println("i="+ i + " val = "+ data[i]);
// }
change(1,index,val);
// for (int i = 0; i < data.length; i++) {
// System.out.println("i="+ i + " val = "+ data[i]);
// }
}
public int sumRange(int left, int right) {
return query(1,left,right);
}
public void build(int index,int l, int r, int[] nums){
data[index] = new Node();
data[index].left = l;
data[index].right = r;
if (l == r) {
data[index].sum = nums[l];
// System.out.println("index="+index+" nums="+nums[l]);
return;
}
int mid = (l + r) >> 1;
// System.out.println(" 111index="+index+" nums="+nums[l]+" ;l="+l+" r="+r);
build(index * 2 ,l,mid,nums);
build(index * 2 + 1, mid + 1,r,nums);
// 回溯的时候累加更新
data[index].sum = data[index * 2].sum + data[index * 2 +1].sum;
}
void change(int curr, int index, int val) {
if (data[curr].left == data[curr].right) {
data[curr].sum = val;
return;
}
// updataTree(curr);
int mid = (data[curr].left + data[curr].right) >> 1;
if (index <= mid) change(curr * 2, index, val);
else change(curr * 2 + 1, index, val);
// System.out.println("data[curr * 2].sum="+data[curr * 2].sum+" data[curr * 2 + 1].sum="+data[curr * 2 + 1].sum);
data[curr].sum = data[curr * 2].sum + data[curr * 2 + 1].sum;
}
public int query(int index,int l, int r) {
// 如果l,r包含index节点的left,right直接返回
// System.out.println("query="+index+" data[index]="+data[index]);
// if (data[index] == null) return 0;
if (data[index].left >= l && data[index].right <= r) return data[index].sum;
// 不包含
int mid = (data[index].left + data[index].right) >> 1;
int ans = 0;
// updataTree(index);
if (l <= mid) ans += query(index * 2, l, r);
if (r > mid) ans += query(index * 2 + 1, l, r);
return ans;
}
public void updataTree(int curr){
if (data[curr].mark != 0) {
data[curr * 2].sum += data[curr * 2].mark * (data[curr * 2].right - data[curr * 2].left + 1);
data[curr * 2].mark += data[curr].mark;
data[curr * 2 + 1].sum += data[curr * 2 + 1].mark * (data[curr * 2 + 1].right - data[curr * 2 + 1].left + 1);
data[curr * 2 + 1].mark += data[curr].mark;
data[curr].mark = 0;
}
}
}
class Node{
int index;
int left;
int right;
int sum = 0;
int mark;
public String toString(){
return "index="+index+" left="+left+" right="+right+" sum="+sum;
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* obj.update(index,val);
* int param_2 = obj.sumRange(left,right);
*/
位运算
- 位1的个数
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
for (int i = 0; i < 32; i++) {
// 右移多少位然后 和1 进行与运算
if (((n >> i) & 1) > 0) count++;
}
return count;
}
}
- 2 的幂
/*lowbit 等于原数就是2的幂*/
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n == (n & -n));
}
}
- 颠倒二进制位
public class Solution {
// you need treat n as an unsigned value
/**
00000010100101000001111010011100
1. i = 2时,右移2位后得到1
2. 此时res为00000000000000000000000000000000
3. 1<< (31 - i) 即 1 << 29
4. 00000000000000000000000000000000
^ 00100000000000000000000000000000
00100000000000000000000000000000
5. res = 00100000000000000000000000000000;
*/
public int reverseBits(int n) {
int res = 0;
for (int i = 0; i < 32; i++) {
// 每一位是1还是0
int a = n >>i & 1;
// 如果是1直接写入翻转后的位置
if (a == 1) {
// 把res的第31 - i位修改为1
res = res ^ (1 << (31-i));
}
}
return res;
}
}