树状数组

引入问题

给出一个长度为n的数组,完成以下两种操作

  • 将第x个数加上k
  • 输出区间[x,y]内每个数的和

使用暴力算法

  • 单点修改:O(1)
  • 区间查询:O(n)

对于大数据来说,这样的复杂度是不能接受的

树状数组处理:

  • 单点修改:O(logn)
  • 区间查询:O(logn)

可以应对非常大规模的数据


前置知识——lowbit()操作

lowbit()操作:非负整数n在二进制表示下最低为1及其后面的0构成的数值
例如:lowbit(44) = lowbit( (101100)2) =(100)2 =4

101100的lowbit值过程

  • 按位取反010011
  • 然后加一010100
  • 然后与原来的数相与:000100

由于二进制在计算机中是用补码存储的,因此(非负整数)N按位取反加一就是-N,故lowbit(N) = N&(~N+1) = N&(-N);


树状数组——思想及实现

区间查询 ——> 前缀和 ——> 树结构维护(logn)

因为求得是区间和,所以很容易想到用前缀和相减的方法,如果使用树结构来维护,那么就可以将复杂度降低到logn
在这里插入图片描述
该树状数组特性

  • 每个节点t[x]保存以x为根的子树中的叶节点(即原数组的每个元素)值的和
  • t[x]节点覆盖的长度就是lowbit(x)
  • t[x]节点的父节点为t[x+lowbit(x)]
  • 整棵树的深度为log2n+1

add(x,k)操作,即将a[x]加上k
- 需要处理每一层的一个树状数组的数据
- 最坏时间复杂度log2n
在这里插入图片描述
ask(x)操作,即查询a[1]~x[7]的和,即前缀和

  • 最坏时间复杂度log2n
  • 向左上找上一个节点,只需要将下标-=lowbit(index)即可
  • 如果需要求区间和,则计算出两个前缀和,然后相减即可
    在这里插入图片描述

示例

可以通过前缀和来初始化树状数组

//树状数组
#include <iostream>
using namespace std;
const int N=10;
int arr[11]={0,1,4,2,5,10,3,4,0,1,0};   //0号位置舍弃不用
int treeArr[11];    //0号位置舍弃不用
int prefix[11]; //记录前缀和,用于树状数组初始化

int lowbit(int x){
    return x&(-x);
}

void init(){
    prefix[1]=arr[1];
    for(int i=2;i<=N;i++){
        prefix[i]=arr[i]+prefix[i-1];
    }
    for(int i=1;i<=N;i++){
        //treeArr[i]是以i为根的树的所有叶节点的和
        treeArr[i]=prefix[i]-prefix[i-lowbit(i)];
    }
}

//对a[i]增加x
void add(int i,int x){
    while(i<=N){
        treeArr[i]+=x;
        i+=lowbit(i);
    }
}
//查询1到x之间的区间和即前缀和
int ask(int x){
    if(x==0)
        return 0;
    int sum=0;
    while(x>0){     //注意这里一定要是>0
        sum+=treeArr[x];
        x-=lowbit(x);
    }
    return sum;
}

//计算坐标a到b之间的区间和
int interval(int a,int b){
    return ask(b)-ask(a-1);
}


int main(){
    init();
    while(1){
        cout<<"0:add 1:interval"<<endl;
        int opt;
        cin>>opt;
        switch(opt){
            case 0:
                cout<<"enter index and addValue:"<<endl;
                int index,value;
                cin>>index>>value;
                add(index,value);
                break;
            case 1:
                cout<<"enter left and right boundary:"<<endl;
                int l,r;
                cin>>l>>r;
                cout<<interval(l,r)<<endl;
                break;
            default:
                return 0;
        }
    }
    return 0;
}

总结

树状数组是动态维护前缀和的工具,最基本的用途是进行区间和查询和单点修改(均是logn的时间复杂度),此外还可以进行区间修改、单点查询;区间修改、区间查询等。

本文根据该视频的讲解总结而来,地址如下https://www.bilibili.com/video/BV1pE41197Qj?from=search&seid=13604527415584361816

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值