树状数组详解

问题引入

为了做到对区间的快速查询,可能你会想到前缀和来优化这个查询,这样区间查询的话是O(1)的复杂度。但如果发生了单点更新,在之后的所有前缀和都要更新,修改的时间复杂度是O(n),并不能解决问题。

线段树

为了避免每次区间查询都要遍历每个元素,我们可以把数字两两求和,并存到另一个数组,这样时间就能节省一半,我们以此类推,就形成了一个 树 的结构

这就是线段树,即便需要求和的数字有很多,我们也可以通过这些额外数组快速求出。

但是同时我们会发现线段树有一些数据是我们不需要的,比如求前2、或者前3个数的和时,我们都用不上 第二层(从下往上)的 第二个元素 5;而在求前4、或者前5个数的和时,我们用第三层(从下往上)的 第一个元素 19更好。

在线段树里类似于这种不需要的数据还有很多,所有层的第偶数个数字都是没用的,去掉也不影响计算

我们观察剩下的数据,会发现刚好有 n 个,我们将其放到一个数组中,这个数组就叫 树状数组

树状数组

树状数组和线段树的区别

线段树比树状数组的扩展性要强,树状数组可以看成是线段树的删减版。线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决。

树状数组的应用

  • 单点修改,区间查询;
  • 区间修改,单点查询;
  • 区间修改,区间查询
  • ......

lowbit函数

如何计算一个非负整数n在二进制下的最低为1及其后面的0构成的数:

例如:44= (101100) (二进制);最低为1和后面的0构成的数是 100 (二进制) = 4

按位与(&)可得到

  

lowbit(x) = x&(-x)

树状数组结构分析

上面是树状数组的结构图,t[x] 保存 以 x 为根的子数中叶子节点值的和,原数组为a[ ]

我们可以发现:

  • t[x] 节点覆盖的长度就是 lowbit(x)
  • t[x] 节点的父节点为 t[x+ lowbit(x)]  
  • 整棵树的深度为 (\log n)+1

单点修改

树状数组从下往上每一层都需要修改

public static void singleAdd(int i,int number){
        for(int x=i;x<=n;x+=lowbit(x)){
            treeVis[x]+=number;
        }
    }

区间查询

前缀和

 可以发现 向左上找上一个节点 只需要将下标 -= lowbit(这个节点的下标)

这样可根据前缀和求出任意区间和

 //求 i 到 j 的区间和
    public static int search(int i,int j){
        int ans=0;
        //求前 j个元素的和
        for(int x=j;x>0;x-=lowbit(x))ans+=treeVis[x];
        //减去前 i-1个元素的和
        for (int x=i-1;x>0;x-=lowbit(x))ans-=treeVis[x];
        return ans;
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值