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

数据结构 树状数组

时间:2015-08-04 11:11:09      阅读:148      评论:0      收藏:0      [点我收藏+]

标签:数据结构

树状数组的作用是求区间和和做点更新。
和线段树相比,线段树和树状数组时间复杂度都是nlogn,但是在空间利用上,我们知道,线段树接近满二叉,需要的空间是4倍N,但是我们树状数组的空间和N相同,而且在代码上有明显的优势。
这里我们先看两幅图:
技术分享
树状数组就是上面的C[],表示的是下面这些数的和。
技术分享

这里我们可以看到,所有的奇数位置都会表示它本身,如1,3,5,7……它们只包含自己
所有的2的倍数的位置都会除了本身都会包含它前面一个数,如2,4,6,8……除了他们本身,还包含了前一个数的和。
所有4的倍数都会再包含再之前的两个数,如4,8……不仅包含了本身和它前面一个数,还包含了再前面两个数的和。
所有8的倍数都会再包含再之前的4个数如8……
……
所有能被2^n整除的数都表示从该点前2^n个数的和。

通过上面这规律,如果我们要求1~n的和,只需要把它拆成二进制数的组合就行了。
比如求1~45的和,就等于c32+c40+c44+c45,这里因为c32表示1~32的和(能被32整除),c40表示33到40的和(能被8整除),c44表示41到44的和(能被4整除),c45表示45到45的和。
所以我们用树状数组的做法可以达到时间上是nlogn,空间上是n的效率。

那么我们如何实现呢?假如我们要寻找1~n的和。
首先我们得构建这个树状数组。
我们还是从左到右一步一步来。
假如n=8,我们的8个数分别是1 8 7 6 5 2 4 3
我们加第一个点,c1=1,那么反向推,c2会包含c1值,所以c2=c2+c1,c4会包含c2的值,所以c4=c4+c2,同理c8=c8+c4。所以我们第一次建完树是这样的。
技术分享

第二次我们加点2,那么显然2,4,8处的点会被更新。
技术分享

第三次我们加入点3,只有3,4,8会被更新。
技术分享

第四次我们加点4,只有4和8要更新。
技术分享

同理,加点5更新5,6,8;加点6更新6,8;加点7更新7,8;加点8更新8。

手动模拟建树我们会了,如何用代码实现呢,或许你有一大堆想法,但可能都不及下面这么简单:

int lowbit(int x)
{
    return x&(-x);
}

void modify(int x,int n,int val)
{
    for(int i=x;i<=n;i=i+lowbit(i))
    {
        c[i]=c[i]+val;
    }
}

第二个函数大家应该都看得懂,相隔相应位置都加上该点的值嘛,但是这相隔位置是怎么个算的,逻辑运算符大家可能会很陌生,这里举个例子:
大家都知道5的二进制是0000 0101 ,那么-5的二进制是1111 1011,(二进制数取负数是先按位取反,再在最后一位+1),现在5&(-5)的二进制就是0000 0001,因为&操作符只有1&1=1,其他情况都是0。

这里大家可以发现,所有奇数的二进制末尾都是1,我们不管前面,按位取反后最后是0,再加1就末尾是1,其他位和原来相反,只有末尾和原来相同是1,所以得到的结果就是1。也就是我们更新了奇数点后接着要更新该点+1的位置。

假如我们的值是2的倍数(不包含2^2,2^3等),那么末尾是10,同理不管前面,取反变01,加1变10,前面和原来的数相反,末尾两位相同,所以得到的结果是2,也就是我们对2的倍数要继续更新它后面两个位置的点。比如2跳到4,6跳到8。

同理,对于4的倍数,末尾是100,取反+1后就还是100,结果是4,所以4的位置要更新完要跳到8。12的位置要跳到16。

同理,对于2^n的倍数,末尾是1000……(n个0),取反+1是本身,结果就是2^n,所以该点要跳到该点+2^n的点。
直到下一个要更新的点不在区间。

这样我们就完成了树状数组的建立。

对于查询,我们可以按建树的逆向思维来做,大家可以把上面45那个例子带入下面的代码手动模拟,求得的是1到x的和。

int getsum(int x)
{
    int sum=0;
    for(int i=x;i>=1;i=i-lowbit(i))
    {
        sum=sum+c[i];
    }
    return sum;
}

最后我们求区间x-y和的时候,只需要用1~y的和减去1~(x-1)的和就行了。

下面给个测试代码供大家细读。

#include <iostream>
#include"stdio.h"
const int N = 105;
int c[N];
using namespace std;

int lowbit(int x)
{
    return x&(-x);
}

void modify(int x,int n,int val)
{
    for(int i=x;i<=n;i=i+lowbit(i))
    {
        c[i]=c[i]+val;
    }
}

int getsum(int x)
{
    int sum=0;
    for(int i=x;i>=1;i=i-lowbit(i))
    {
        sum=sum+c[i];
    }
    return sum;
}

int main()
{
    printf("请输入10个数\n");
    for(int i=1;i<=10;i++)
    {
        int t;
        scanf("%d",&t);
        modify(i,10,t);
    }
    printf("树状数组的值分别为:\n");
    for(int i=1;i<=10;i++)
    {
        printf("%d ",c[i]);
    }
    printf("\n从1到i的和分别是\n");
    for(int i=1;i<=10;i++)
    {
        printf("%d ",getsum(i));
    }
}

模拟运行结果如下:
技术分享

版权声明:本文为博主原创文章,未经博主允许不得转载。

数据结构 树状数组

标签:数据结构

原文地址:http://blog.csdn.net/qq_27508477/article/details/47272515

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