树状数组

来源:互联网 发布:网络时间校准网址 编辑:程序博客网 时间:2024/06/05 08:57

树状数组的作用

普通数组的对某项的修改时间复杂度为O(1),求前n项的累加和的时间复杂度为O(n)。而树状数组的修改和求前n项累加和的时间复杂度都为O(lgn),这对于一个较大数组求前n项累加和有很大便利性。

树状数组的组织方式

利用下面两幅图可以较清楚的了解树状数组是如何进行组织的。左边叫做A图,右边的叫做B图。


数组a是一个假想的普通数组,我们实际操作和维护的是数组c,大家可以看到数组c是不是很像一棵树?由图可以看出在数组c中,c[8]表示a[1]...a[8]的和,而c[6]表示的却是a[5]和a[6]的和。为什么会出现这种情况呢?主要是这样会使操作更加简单,规则统一。我们看下B图就会明白了,对于c[8]表示的是a[1]..a[8]的和可以看做是左半边a[1]..a[4]和右半边a[5]...a[8]的和。左边则是确定的c[4],右边同样被一分为二,直至不可分。利用二分的思想这样把普通数组组织起来就是树状数组了。

那么它又是怎样做到按照上述规则一分为二的呢?这里我们要讲到一个lowbit的函数,它的作用是将一个二进制数k的除低位以外的1置零,如lowbit(1010)=0010,lowbit(01101)=0001。实现的方式也非常简单就是 k&-k,我们知道一个数的负数就是将这个数取反加1,然后再与原数按位与,这样就可以把除低位以外的1全部置零了。如-10的二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了!

上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,比如c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,因为lowbit(0110)=0010,可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用。

既然关系建立好了,看看如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,因为后面求和要用到。而维护也很简单,比如更改了a[0011],我们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,但是你可能会问,他们之间有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操作,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,所以0100是不能变换到0101的,因为没有新的高位1产生,这个变换过程恰好是可以借助我们的lowbit进行的,k +=lowbit(k)。

上面介绍完了树状数组的组织方式,再来介绍一下如何更新维护树状数组的代码实现:


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


int add(int pos,int num){while(pos<=N){c[pos]+=num;pos+=lowbit(pos);}}

求和函数

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

树状数组说白了是按照二分对数组进行分组;维护和查询都是O(lgn)的复杂度,复杂度取决于最坏的情况,也是O(lgn);lowbit这里只是一个技巧,关键在于明白c数组的组织方式。


0 0
原创粉丝点击