码迷,mamicode.com
首页 > 编程语言 > 详细

C++实用数据结构:二叉索引树

时间:2016-06-30 21:18:24      阅读:161      评论:0      收藏:0      [点我收藏+]

标签:

看下面这个问题(动态连续和查询):

有一个数组A(长度为n),要求进行两种操作:
add(i,x):让Ai增大x;
query(a,b):询问Aa+Aa+1+...+Ab的和;
若进行模拟,则每次query操作的最坏的时间复杂度为O(n),在n较大时速度较慢。用前缀和也不能提高效率(每次add操作最坏为O(n))。有一种数据结构,可以在O(n)时间里初始化,用O(logn)的速度执行add操作或查询前缀和,从而执行query操作。
首先,我们来介绍“lowbit”。对于一个数x,lowbit(x)是x的二进制里最右面的1所代表的数字,例如lowbit(20)=4,因为20的二进制为10100,最右面的1代表4。怎样计算lowbit呢?很简单,lowbit(x)=x&-x。这是因为负数是用补码保存的,-x就相当于(~x)+1。例(用8位计算):
 20=(00010100)
-20=(11101100)
现在我们来介绍二叉索引树。二叉索引树在程序中也是用数组来保存的。
技术分享(画得难看请见谅)
图中,深灰色方块代表树中的结点(结点0为虚拟结点),灰色线段代表树中的边。这些边并不需要显式保存;对于一个节点x,若它是父结点的左子结点,则父结点编号为x-lowbit(x),否则为x+lowbit(x)。图中的每个结点左侧都有一个白色长条(包括它自己),如结点4的长条为1~4,节点3的长条为3~3。不难发现,结点x的长条为(x-lowbit(x)+1)~x。
然后,我们用一个数组S储存每个结点的白色长条内的所有数的和。例如,S4=A1+A2+A3+A4。这样,就可以使用一个辅助的前缀和数组,在O(n)时间内从左到右初始化S数组。代码如下:
 
 
技术分享
 1 int n;
 2 int S[maxn],A[mxan],S1[maxn];
 3 int begin()
 4 {
 5     memset(S1,0,sizeof(S1));
 6     for(int i=1;i<=n;i++)
 7     {
 8         S1[i]=S1[i-1]+A[i];
 9         S[i]=S1[i]-S1[i-lowbit(i)];
10     }
11 }
View Code

 

最后让我们来实现add操作和查询操作。对于某个add操作的结点i,它的修改会影响到那些结点呢?很显然,在它下面的结点(lowbit比它小)不会受到影响,在它左边的结点也不会受到影响,所以只需要考虑在它右边且lowbit比它大的第一个结点,这个结点的白色长条一定覆盖x。很显然,这个结点的编号为i+lowbit(i)。
接下来,x结点不需要考虑了,让我们来考虑i+lowbit(i)结点。与上面一样,只需要考虑在它右边且lowbit比它大的第一个结点,所以只需要让i+=lowbit(i)。代码如下:
技术分享
1 void add(int i,int x)
2 {
3     //若需要其他操作,可以加一句:
4     //A[i]+=x;
5     while(i<=n)
6     {
7         S[i]+=x;i+=lowbit(i);
8     }
9 }
View Code

 

查询前缀和的方法与之类似,只是i+=lowbit(i);改成了i-=lowbit(i);这里不再介绍证明方法,与上面类似。代码如下:

技术分享
 1 int query2(int i)
 2 {
 3     int sum=0;
 4     while(i!=0)
 5     {
 6         sum+=S[i];i-=lowbit(i);
 7     }
 8     return sum;
 9 }
10 int query(int l,int r)
11 {
12     return query2(r)-query2(l-1);
13 }
View Code

 

 

最后,若有建议请提出。

 

C++实用数据结构:二叉索引树

标签:

原文地址:http://www.cnblogs.com/y-clever/p/5631028.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!