【算法】树状数组原理和实现(纪录片)

10 篇文章 0 订阅

在这里插入图片描述

树状数组原理和实现

树状数组是一种维护数组前缀和、区间和的数据结构,思想和跳跃表有点类似。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。和Trie树的构造方式有类似之处。

在这里插入图片描述

建立索引

树状数组的一个节点索引的原始数据数量,与该节点编号在二进制下最低为的1有关

  1. 1,3,5,7… 二进制下以1结尾,仅索引1个数据(自身)
  2. 2,6,10,14…二进制下以10结尾,索引2个数据(自身,它前面的那个数)
  3. 4,12… 二进制下以100结尾,索引4个数据(自身,前面3个数)
二进制分解与lowbit
  1. 任意正整数可以唯一分解为若干个不重复的2次幂之和
  • 7 = 22 + 21 + 20, 12 = 22 + 22
  1. lowbit(x) 定义为x二进制下最低为的1和后面0组成的数值(或者说x二进制分解下的最小次幂)
  • lowbit(7) = lowbit(1112) = 12 = 20 = 1
  • lowbit(12) = lowbit(11002) = 1002 = 22 = 4
  1. 树状数组c的节点c[x]存储x前面的lowbit(x)个数据(包括x)的和
  • c[7] = a[7]
  • c[12] = c[12] + c[11] + c[10] + c[9]
  1. 添加代码
void add(int x, int y) { // x y
for (; x <= N; x += x & -x) c[x] += y;
}
  1. 查询代码 时间复杂度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)


解题

  1. 区域和检索 - 数组可修改
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;
    }
  1. 线段树解法
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. 位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;
    }
}
  1. 2 的幂
/*lowbit 等于原数就是2的幂*/
class Solution {
    public boolean isPowerOfTwo(int n) {
        return n > 0 && (n == (n & -n));
    }
}
  1. 颠倒二进制位
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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值