zoukankan      html  css  js  c++  java
  • 树状数组的原理和实现

    树状数组的原理和实现

    概念

    树状数组或者二叉索引树也称作Binary Indexed Tree,又叫做Fenwick树;它的查询和修改的时间复杂度都是log(n),空间复杂度则为O(n),这是因为树状数组通过将线性结构转化成树状结构,从而进行跳跃式扫描。通常使用在高效的计算数列的前缀和,区间和。

    其中a数组就是原数组,c数组则是树状数组,可以发现

    C1 = A1
    C2 = A1+A2
    C3 = A3
    C4 = A1+A2+A3+A4
    C5 = A5
    C6 = A5+A6
    C7 = A7
    C8 = A1+A2+A3+A4+A5+A6+A7+A8

    原理

    lowbit

    它通过公式来得出k,其中k就是该值从末尾开始0的个数。然后将其得出的结果加上x自身就可以得出当前节点的父亲节点的位置或者是x减去其结果就可以得出上一个父亲节点的位置。比如当前是6,二进制就是0110,k为2,那么6+2=8,而C(8)则是C(6)的父亲节点的位置;相反,6-2=4,则是C(6)的上一个父亲节点的位置。

    def LOWBIT(x):
        return x & (-x)
    

    注意:LOWBIT无法处理0的情况,因为它的结果也是0,那么最终就是一个死循环

    单点修改

    当我们要对最底层的值进行更新时,那么它相应的父亲节点存储的和也需要进行更新,所以修改的代码如下:

    def MODIFY(x, delta):
        if x < 1:
            return
        while x <= n:
            fenwick[x] += delta
            x += LOWBIT(x)
    

    查询

    而查询的时候,则需要向前进行统计

    def QUERY(x):
        result = 0
        while right > 0:
            result += fenwick[x]
            x -= LOWBIT(x)
        return result
    

    例如

    15=(1111)2,通过lowbit分解,它可以变成4个数的和:(1111)2=(1)2+(10)2+(100)2+(1000)2,然后我们分析这个倒着跳的过程。减去15的最小的2的幂次2^0得到14。减去14的最小的2的幂次2^1得到12。减去12的最小的2的幂次2^2得到8。减去8的最小的2的幂次2^3得到0。

    所以C(15) = C(14) + C(12) + C(8) + C(0),由图也可以得知,其结果是正确的。

    除此之外,树状数组能够快速的求任意区间的和,设sum(k) = A[1] + A[2] + ... + A[k],则A[i] + A[i+1] + ... + A[j] = sum(j) - sum(i-1)。

    练习

    输入n个位置,然后先按照y的顺序,如果相等则按照x的顺序,最终求该坐标左下的星星的数目,网址,根据题目要求,y已经满足要求,那么只需要考虑x即可,那么我们就可以使用树状数组来计算前面的星星的数目。

    #include<stdio.h>
    #include<string.h>
    int c[32000+10];
    int a[15000+10];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void updata(int x,int d)
    {
        while(x<=32001)
        {
            c[x]=c[x]+d;
            x=x+lowbit(x);
        }
    }
    int getsum(int x)
    {
        int res = 0;
        while(x>0)
        {
            res=res+c[x];
            x=x-lowbit(x);
        }
        return res;
    }
    int main()
    {
        int n;
        int i,x,y;
        while(scanf("%d",&n)!=EOF)
        {
            memset(c,0,sizeof(c));
            memset(a,0,sizeof(a));
            for(i=0; i<n; i++)
            {
                //因为y是升序,所以横坐标小于x的,(想了很久)所有点都符合,这是解这道题的关键。
                scanf("%d%d",&x,&y);    //下标可能从0开始,所以要x+1
                a[getsum(x+1)]++;       //求出横坐标小于x的所有stars个数,并记录到a中
                updata(x+1,1);          //更新区间
            }
            for(i=0; i<n; i++)
            {
                printf("%d
    ",a[i]);
            }
        }
        return 0;
    }
    

    优缺点

    树状数组的优点:

    1. 代码短小,实现简单;
    2. 容易扩展到高纬度的数据;

    缺点:

    1. 只能用于求和,不能求最大/小值;
    2. 不能动态插入;
    3. 数据多时,空间压力大;
  • 相关阅读:
    51nod_1445 变色DNA 最短路模板 奇妙思维
    51nod_1459 最短路 dijkstra 特调参数
    UVA_10653 公主与王子 #刘汝佳DP题刷完计划
    HOJ 13819 Height map
    51nod_1255字典序最小的子序列
    电梯设计需求调研报告
    梦断代码读后感
    求一循环数组的最大子数组的和
    求二维数组中最大子数组的和
    四则运算
  • 原文地址:https://www.cnblogs.com/George1994/p/7710886.html
Copyright © 2011-2022 走看看