【树状数组】

一篇讲解的很通俗易懂的博客点击打开链接

第一:什么是树状数组?

        树状数组是一个查询和修改的复杂度都为o(logn)的一种数据结构。对于一般的求和算法来说,它的优点在于当修改一个数时,它的复杂度会从o(n)减小到o(logn),这就是它的效率所在。

第二:图解树状数组c[]:

现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]


如图所示:

(1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。

(2)C[i]为A[i]对应的那一列的最高的节点。

现在告诉你:序列C[]就是树状数组。

那么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];

以上只是枚举了所有的情况,那么推广到一般情况,得到一个C[i]的抽象定义:

因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C[i]=C[i]的所有叶子的和。

现在不得不引出关于二进制的一个规律:

先仔细看下图:


将十进制化成二进制,然后观察这些二进制数最右边1的位置:

1 --> 00000001

2 --> 00000010

3 --> 00000011

4 --> 00000100

5 --> 00000101

6 --> 00000110

7 --> 00000111

8 --> 00001000

在满二叉树中,

以1结尾的那些结点(C[1],C[3],C[5],C[7]),其叶子数有1个,所以这些结点C[i]代表区间范围为1的元素和;

以10结尾的那些结点(C[2],C[6]),其叶子数为2个,所以这些结点C[i]代表区间范围为2的元素和;

以100结尾的那些结点(C[4]),其叶子数为4个,所以这些结点C[i]代表区间范围为4的元素和;

以1000结尾的那些结点(C[8]),其叶子数为8个,所以这些结点C[i]代表区间范围为8的元素和。

扩展到一般情况:

i的二进制中的从右往左数有连续的x个“0”,那么拥有2^x个叶子,为序列A[]中的第i-2^x+1到第i个元素的和。

终于,我们得到了一个C[i]的具体定义:

C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。

第三 利用树状数组求前i个元素的和s[i]:

理解了C[i]后,前i个元素的和S[i]就很容易实现。

从C[i]的定义出发:

C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。

我们可以知道:C[i]是肯定包括A[i]的,那么:

S[i]=C[i]+C[i-2^x]+…

也许上面这个公式太抽象了,因为有省略号,我们拿一个具体的实例来看:

S[7]=C[7]+C[6]+C[4]

因为C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]

(1)i=7,求得x=0,那么我们求得了A[7];

(2)i=i-2^x=6,求得x=1,那么求得了A[6]+A[5];

(3)i=i-2^x=4,求得x=2,那么求得了A[4]+A[3]+A[2]+A[1]。

现在直接告诉你结论:2^x=i&(-i)

树状数组的求和函数:

int Query(int i)   //求从1到i的和

{

    int sum=0;

    while(i>0)

   {

        sum=sum+c[i];

        i=i-lowbit(i);

    }

return sum;

}

第四 更新c[]:


如上图:

假如A[3]=3,接着A[3]+=1,那么哪些C[]需要改变呢?

答案从图中就可以得出:C[3],C[4],C[8]。因为这些值和A[3]是有联系的,他们用树的关系描述就是:C[3],C[4],C[8]是A[3]的祖先。

那么怎么知道那些C[]需要变化呢?

我们来看“A”这个结点。这个“A”结点非常的重要,因为他体现了一个关系:A的叶子数为C[3]的2倍。因为“A”的左子树和右子树的叶子数是相同的。 因为2^x代表的就是叶子数,所以C[3]的父亲是A,A的父亲是C[i+2^0],即C[3]改变,那么C[3+2^0]也改变。

我们再来看看“B”这个结点。B结点的叶子数为2倍的C[6]的叶子数。所以B和C[6+2^1]在同一列,所以C[6]改变,C[6+2^1]也改变。

推广到一般情况就是:

如果A[i]发生改变,那么C[i]发生改变,C[i]的父亲C[i+2^x]也发生改变。

更新c[]的函数:

void Update(int i,int x)   //将a[i]位置上的数更新为x

{

     while(i<=n)

     {

        c[i]=c[i]+x;

        i=i+lowbit(i);

      }

}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值