树状数组
是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],那么查询a[1]+...+a[n]的时间是
log(n)级别的。所以如果要解决“数组中的元素不断被修改,怎么才能快速地获取数组中连续m个数的和”这个问题的话,用树状数组就再好不过了
首先,什么是树状数组呢?树状数组就是用另外一个数组再保存当前数组的值,设树状数组为C[n],那么有C[i] = a[i - 2^k + 1] + ... + a[i](其中k为i的二进制末尾0的个数)。在这里,2^k代表2的k次方。如果n=8,则c的值如下:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
算2^k有一个快捷的办法,代码如下,这个就不用多解释了: int lowbit(int x){ return x&(x^(x–1)); }
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
用图来表示的话,就如下图,是不是有点像一棵树啊?
在算法方面:
![]()
![]()
算2^k有一个快捷的办法,代码如下,这个就不用多解释了: int lowbit(int x){ return x&(x^(x–1)); }
其次是求和的过程:
当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:
step1: 令sum = 0,转第二步; step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步; step3: 令n = n – lowbit(n),转第二步。 可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。
当然,如果你要求的是第i个数到第j个数的和的话,调用sum(j) - sum(i - 1)就行了
i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。
最后贴一下代码吧,由于算法在上面已经讲得比较详细,所以在这里就不多加注释了
#include <stdio.h>
#define MAXN 11
int c[MAXN] = {0};
int a[MAXN] = {0};
int lowbit(int n)//确定2的k次方
{
return n & (n ^ (n - 1));
}
void built(int n) //建树的过程
{
for (int i = 1; i <= n; ++i)
{
for (int j = i - lowbit(i) + 1; j <= i; ++j)
{
c[i] += a[j];
}
}
}
void add(int num, int pos, int n)
{
if (pos > n)
{
return;
}
c[pos] += num;
pos += lowbit(pos);
add(num, pos, n);
}
int sum(int n)
{
int total = 0;
while (n > 0)
{
total += c[n];
n -= lowbit(n);
}
return total;
}
int main()
{
for (int i = 1; i < 11; ++i)
{
scanf("%d", &a[i]);
}
built(10);
while (true)
{
printf("please input the instruct:1 for add, 2 for sum\n");
int instruct = 0;
scanf("%d",&instruct);
if (1 == instruct) //修改数组
{
printf("please input the pos and the number to add\n");
int pos = 0;
int num = 0;
scanf("%d %d", &pos, &num);
add(num, pos, 10);
}
else //求和
{
printf("please input the n\n");
int n = 0;
scanf("%d", &n);
printf("%d\n", sum(n));
}
}
return 0;
}