树状数组略解

今天比赛的时候好多树状数组的题,这里总结一下树状数组的用处。
首先不得不说树状数组的思想简洁而又深刻,短短几行代码,诠释了什么叫“大道至简”,我想算法的魅力或许就在于此。

今天比赛的时候,看似简单的题总是超时,当时就敏锐的想到用树状数组解决,然而由于不太熟悉,自己又在本子上推了一遍,最后还是有几道题没来得及看,现在赶紧回来总结一下树状数组。(纯手打,不容易!)

先讲讲树状数组的用处,毕竟有了需求,才有学习的动力。
对普通数组进行M次修改或求和,时间复杂度为O(M*N),N为修改或求和需要扫描的区间大小。而对于树状数组,时间复杂度则为O(M*lgN)。加了一个lg,学过数学的我们应该都知道差距有多大。

在讲实现之前,我们需要先理解一个函数lowbit(x),这是一个自定义的函数,函数名是约定成俗的,作用就是返回x的二进制表示中最后一位1的权值
代码实现

int lowbit(int x)//位运算,利用计算机补码特性
{
    return x&-x;
}

写个长注释,假设x=10
10的二进制:1010,我们知道,一个数前加符号,就是用这个数的二进制取反加一。
-10的二进制:0101+1=0110
然后位运算符&(按位与),1010&0110=10,十进制表示就是2
所以lowbit(10)=2
这个函数很重要,所以先在这里交代清楚原理。

下面上图,讲讲实现树状数组(图来自百度百科)
这里写图片描述
图中有两个数组,数据都接收到底层数组a中,而数组c则是树状数组(看形状是不是很像个树)。 从图中可以直观的看到
c[1]=a[1];
c[2]=a[1]+a[2];
c[3]=a[3];
c[4]=a[1]+a[2]+a[3]+a[4];
c[5]=a[5];
c[6]=a[5]+a[6];
c[7]=a[7];
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
假设结点为n,那么结点n所管辖的区间为2的lowbit(n)次方
即:c[n]=a[n-(2^lowbit(n))+1]+…..+a[n];
这样通过lowbit函数,把底层数组a和树状数组c联系了起来。

求数组a的前n位和
代码

int sum(int n)
{  
    int sum=0;  
    while(n>0)  
    {  
        sum+=c[n];  
        n=n-lowbit(n);
    }  
    return sum;  
}

假设n=8,lowbit(8)=8,while循环只持续了一次,得到的结果c[8]也与实际吻合
(发现没有,求前8位和,实际上只循环运算了1次!)
假设n=6,lowbit(6)=2,获取c[6]=a[5]+a[6]后,n=4;lowbit(4)=4;获取c[4]=a[1]+a[2]+a[3]+a[4];最终获取的就是a[1]+..+a[6];
(求前6位和,实际只循环运算了2次!)
还理解不了?那简单,把代码背下来,记住这个函数的返回值就是前n位和。

下面是修改,将数组a的第k位增加(或减少)num
代码

void add(int k,int num)  
{  
    while(k<=n)//n是树状数组的大小 
    {  
        c[k]+=num;
        k=k+lowbit(k);//由于每次跳lowbit(k)位,所以时间复杂度大大降低,为什么跳lowbit(k)位操作,因为你求和的时候也是跳lowbit(k)位求和啊。 
    }  
}  

说了那么多,把我自己都讲糊涂了,本来我是直接套用函数简单粗暴,这样一写,我发现树状数组其实讲的就是二进制,

后面我打算再更新离散化树状数组和二维树状数组,待我睡一觉想想怎么写。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值