图解树状数组

来源:互联网 发布:linux编译android源码 编辑:程序博客网 时间:2024/06/06 01:56

        希望能帮助大家理解树状数组。

        树状数组的好处我就不说了,为了方便理解树状数组,在这里我们先假设一个具体的普通数组A{0,1,2,3.。。。。}。普通数组的每个元素就让他和下标相等好了。然后再给出相对应的树状数组C,0的位置就不用了,开头一部分如下图:




        先看图片有什么规律,C1存的就是A1,C2存的是C1加上A2,C3存的只是C3,C4存了C2+C3+A4等等。可以先不理解为什么要这样存。要看懂树状数组我们重点看他的二进制下标,再看一下他的二进制下标有什么规律,最底下一排下标最右边都是1,第二排(C2、C6等)下标右边都是一个0,第三排(C4、C12)下标右边都有两个0等等,还有(C1->C2->C4->C8->C16...)这样沿着最上面往右走,这些下标的二进制都只有一个0,,而1的位置越来越靠左;现在你可能不知道这些规律有什么意义,但在后面会帮助你理解。

        如果你要修改其中一个元素需要怎么办呢?比如修改第六个元素(开头说了0位置我们不看,所以就是下标为6的位置),由图我们可以看到肯定需要先修改C6,然后修改C8,然后修改C16,这样沿着线一直修改到最后,很多元素都要修改,那么问题来了,我们修改完C6之后怎么样才能知道下一个需要修改哪里呢?在这里我们可以想到整个修改操作可以用一个函数来完成,传进去两个参数,要修改的第一个位置,以及加上多少,在这里第一个参数就是6,下面贴上代码:


int lowbit(int k){return k&(-k);}void add(int k,int num)  {      while(k<=n)      {          C[k]+=num;          k+=lowbit(k);     }  }

        只看add函数,先不管lowbit函数,传进去位置k之后,只要k没超过边界,就在位置k上进行修改,然后k移动一下,这比较好理解,可是关键k要怎么移动?可以看到lowbit函数就是用来做这个操作的。

        lowbit就是低比特位的意思,你给他一个数(这里看二进制),他会把最低位的1给你返回,比如传进去10110(随便编的),他不管前面多少1,最右边的1在从右往左第二个位置,那么他就给你返回10,不管前面有几个0几个1。也就是说k&-k就可以把一个数二进制最右边的1给取出来,现在说一下它的过程,比如一个数是1101101,那么他的相反数(负数)的二进制,是原数每一位取反,即0010010,然后加一,即0010011,然后做&运算,便只剩下了最右边的1。此处关键在于那个+1,可以多变换几次细细体会。

        所以k加上lowbit(k)就等于在k的最低位1上加1,也就是把最右边的1拿出来,往左放一位。可是为什么在k的最低位上加1就是下一个位置呢?看一下图就很明显了。随便找一个比如C5,下标是00101,把最右边的1往左移一下,就是00110,也就是C6的位置,正确,00110再把最右边的1往左移,此时需要进位,也就变成了01000,就是C8的位置,此时只有一个1,再把它往左移一下变成了10000,即C16....可以发现这刚好就是沿着元素变化会影响到的方向在移动,随便找一个点都是如此。所以修改一个元素只要不停的在下标上加lowbit(下标)就好了。到此为止就是建立数组的过程。(每次修改一个元素就要进行一组循环会不会太浪费时间了?其实可以很容易看出每次移动1就相当于乘了2,n个元素从第一个开始修改也才进行lg n次循环




        那么怎么用呢。这里我们用求前k个元素的和来举例子。先看图,如果k=6,求前六项的和应该怎么求,依然看图,应该是C6+C4,因为C4包含了前四项,C6包括了第5项和第6项,那么我们怎么才能知道是哪几项相加呢?

上代码:

int read(int k)//1~k的区间和  {      int sum=0;      while(k)      {          sum+=C[k];          k-=lowbit(k);      }      return sum;  }  

        代码依然是很少。可以看到从需要求的位置开始累加,每次减去一个lowbit(k),直到k为0。我们已经知道lowbit返回的是最低位的1,那么减去最低位的1,就是每次把最右边1变成0,直到没有1,即数字为0了。这个过程是怎样的呢,从图上看,C6,00110去掉最右边的1是00100,即C4,再去掉最右边的1,因为00100只有一个1,去掉之后就变成0,退出循环,刚刚好。可以再看一个复杂一点的,求前15项和,看图,C15为01111,去掉最右边1为01110(C14),再去掉最右边1为01100(C12),再去掉最右边1为01000(C8),再去掉1为0,退出循环,可以求得和为C15+C14+C12+C8,正确。




原创粉丝点击