理解树状数组
树状数组又名二分索引术,主要包含两种基本操作
1.Update(int i,int val)更新节点及其所有父节点及祖先节点的值,表示对第i点的值增加val。时间复杂度O(logn)
2.Sum(int i)表示对前i个点进行求和操作.时间复杂度O(logn),n表示节点总数,logn即log2n。
树状数组是通过数组来实现的一种轻量级的数据结构,性价比较高。
主要实现
定义数组C[i],A[i]。C[i]=A[i-2^k+1]+A[i-2^k+2]+......+A[i],这里k表示i在二进制表示下末尾数字0的个数。
一种较为直观的判断方法可以直接找出i的因子中2的最高次方即k同时2^k就是A[i]的个数,例如8是1000,因子有2的3次方,所以c[8]=A[1]+A[2]+......+A[8]
在这里i=8,一定是从A[x]开始一直加到A[8],8=2^3,因此判断有8个数相加,一直加到A[8]为止,所以一定是从A[1]开始加的。再如6=3*2^1,只有两个数相加,一定从A[5]开始加
见下图
根据刚才对C[i]的定义可知,当i为奇数时C[i]=A[i],当i为偶数时需要进行计算以求得i-2^k+1
C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7
C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
可以直观的看到若改变某个A[i]的值,A[i]的父节点及祖先节点的值也要相应的改变
在函数实现之前先来介绍一种运算,以函数的方式来实现
int Lowbit(int x)
{
return x&(-x);
}
这个函数求的就是2^k。
具体运算是通过补码方式来实现的,不懂补码概念的自行去脑补先
以6为例,6的原、反、补码都是0110,-6的原码是1110(假定第一位都是符号位),反码是1001,补码是1010.两个补码进行&运算得到的结果是2
再来看看函数的具体实现
1.更新操作Update(int i,int val)
假设要实现A[i]=A[i]+val的操作,这里我们不直接对A[i]直接操作,而是改变C[i]及C[i]的所有祖先节点的值
例如改变C[1]的值需要改变C[2]、C[4]、C[8]的值
void Update(int i,int val)
{
while(i<=n)
{
C[i]+=val;
i+=Lowbit(i);
}
}
提供一种非递归的写法
void add(int i,int val)
{
for(i;i<=n; i+=lowbit(i))
{
C[i] += val;
}
}
2.求和操作Sum(int i)
求A1~Ai的和
sum(i) = sum{ A[j] | 1 <= j <= i } = A[1] + A[2] + … + A[i]
= A[1] + A[2] + A[i-2^k] + A[i-2^k+1] + … + A[i]
= A[1] + A[2] + A[i-2^k] + C[i]
= sum(i - 2^k) + C[i]
= sum( i – lowbit(i) ) + C[i]
int Sum(int i)
{
int sum=0;
while(i>0)
{
sum+=C[i];
i-=Lowbit(i);
}
return sum;
}
同样提供一种非递归写法
int Sum(int i)
{
int sum=0;
for(i; i ; i -= lowbit(i))
{
sum+=C[i];
}
return sum;
}
原博请见:http://www.open-open.com/lib/view/open1450603621287.html
题目推荐的话后续再更新吧,我也是刚入门>_<