树状数组

树状数组,听起来是一个多么平凡的数据结构名称,顾名思义就是开一个树一样的数组。但是经过这几天的学习我发现树状数组——普通而奇妙。

引入:

现在有这么一个问题:给你一个数列a,让你求出某区间(i, j)内元素的和,你会怎么做??如果你没学过树状数组或者线段树这样的数据结构的话,你可能会天真地回答当然是在那多区间扫一遍,把元素累加到一个变量(如:sum。。叫法当然随心所欲)里,不就妥妥地搞定了吗~


这样当然没问题,但是!如果随时要改变元素的值,随时向不同区间询问区间和,而且区间的长度很大很大呢??那种扫一遍,逐个改变,逐个累加的算法岂不是很慢很慢。所以,树状数组这个神奇的就够就华丽丽的出现了。


                                                                         

大家观察上面的图片,有没有发现什么?其实这就是树状数组的模型。如果你还没发现其中的奥秘——


c[1] = a[1];

c[2] = a[1] + a[2] ;

c[3] = c[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];

. . . . . .


大家应该可以发现——这里出现了一个神奇而有趣的性质:


对于每一个节点x,它管辖的范围就是它前面的2^k(k代表x在二进制下末尾0的个数)个元素.

就可以得出:c[i] = a[i - 2 ^k + 1] + ... + a[i](i 从1开始算)。注意!c[i]储存的是[a - 2^k, a) 这段区间的 元素和!

这样就达到了我们区间求和的目的, 而且时间的复杂度由最朴素的O(n) 降低到了O(logn)。则我们称这个c数组叫做树状数组。


下面问题来了,给出i,如何求得2  ^ k呢?


最奇妙的地方来了:2 ^ k = x & (x ^ (x - 1))  或 = x &  (-x);

举例:当i = 6 时, 6在二进制下 = (0110);

                                 (6 - 1) = 5 在二进制下 = (0101);

                                 (0110) ^ (0101) = (1100);

                                 (0011) & (0110) = (0010);

所以,i = 6时,其管辖范围2 ^ k = 2 ^ 1 = 2;

int Lowbit(int x)
{
    return (x & (x ^ (x - 1)));
}

接下来,我们该考虑改变元素的值和求和的问题了——

1)求和——

     step1 : int sum, sum = 0; - > step2;

     step2 : 假如 i <= 0, 直接返回sum; 否则 sum += c[i];  - > step3;

     step3 : i -= Lowbit(i); - > step2;

   可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

int Sum(int end)
{
    int sum = 0;
    for (i = end; i > 0; i -= Lowbit(end))
        sum += c[i];
    return sum;
}

2)改变元素——

   修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x)。

     step1:当i > n时,算法结束,否则转第二步。

     step2: c[i] = c[i] + x, i = i + Lowbit(i)转第一步。

   i = i + Lowbit( i ) 这个过程实际上也只是一个把末尾1补为0的过程。

   对于数组求和来说树状数组简直太快了!

   

void change(int pos, int x)
{
     for (int i = pos; i <= n; i += Lowbit(pos))
          c[pos] += x;
}


      

     





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值