现在的位置: 首页 > 综合 > 正文

树状数组—-算法高级专题

2014年04月05日 ⁄ 综合 ⁄ 共 3328字 ⁄ 字号 评论关闭

lowbit (i):i&-i 或者i=i&(i^i-1)

9  and  -1

 1、要点:一个数i对应二进制表示未尾0的个数为k,那么他管辖的范围为:从i到左边i-2(k)+12(k)个元素。如:i:6  110
k=1 6
管辖的范围为:

C[5]=a[5]+a[6]; 2(1) 2个。

2、求前N个元素的和,N的二进制的后一个1去。如:N=7;111à110(6)->100(4)->0

i=i-lowbit(i)

C[7]=a[4]+a[6]+a[7]

C[8]= 1 a[8]     1000->0

C[i]=c[i]+c[i-2(k)]

  K表示i当前未尾0的个数

-àc=7 k=0; c=7-2(0)=7-1=6

  C[7]=c[7]

--k表示未尾0的个数

   C=6   k=1  c=6-2(1)=4

 C[7]=c[7]+c[6]

--c=4,k表示未尾0的个数

C=4.k=2; c=4-2(2)=0

C[7]=c[7]+c[6]+c[4]

C=13à1101->第一种思想:

C[13]=1101c[1100=12]=c[1000=8]=c[0]

C[13]=c[13]+c[12]+c[8]

3、如果要修改a[i],应该要修改:

i=i+2(k)  C[i].一直到根结点。3
11 ->4->8->16

如:i=15  a[15]  1111 c[i]=15+2(0)=15+1=16

4、动态求和:如果要求a数组中某段元素的和,则只需要统计c数组即可。

 1sum=sum+c[i]

  2i=i-lowbit(i)

重复直到i等于0

i=7 要求 a[1]~a[7]的和

sum=0+c[7]  i=7-2(0)=6 sum=c[7]+c[6]+c[4]  i=6-2(1)=4   4=4-2(2)=0

a[2]a[7]可以先求a[1]a[7]-c[2]

树 状 数 组

 

 

1i=i+lowbit(i)向上走,用于更新a数组 ->c[i]

//解释i=i+lowbit(i)表示把i未尾10的过程。

2i=i-lowbit(i)用于求a[1]a[i]的和,可以通过求c[i]的和来得.

//解释i=i-lowbit(i)表示把i的最后一个1减去。

1、概述

树状数组(binary indexed tree),是一种设计新颖的数组结构,它能够高效地获取数组中连续n个数的和。概括说,树状数组通常用于解决以下问题:数组{a}中的元素可能不断地被修改,怎样才能快速地获取连续几个数的和?

2、树状数组基本操作

传统数组(n个元素)的元素修改和连续元素求和的复杂度分别为O(1)O(n)。树状数组通过将线性结构转换成伪树状结构(线性结构只能逐个扫描元素,而树状结构可以实现跳跃式扫描),使得修改和求和复杂度均为O(lgn),大大提高了整体效率。

给定序列(数列)A,我们设一个数组C满足

C[i] = A[i–2^k+ 1] + … + A[i]

其中,ki在二进制下末尾0的个数,i1开始算!

则我们称C为树状数组。

下面的问题是,给定i,如何求2^k?

答案很简单:2^k=i&(i^(i-1)) ,也就是i&(-i)

下面进行解释:

i=6为例(注意:a_x表示数字ax进制表示形式):

(i)_10 = (0110)_2

(i-1)_10=(0101)_2

i xor (i-1) =(0011)_2

i and (i xor (i-1))  =(0010)_2

2^k = 2

C[6] = C[6-2+1]+…+A[6]=A[5]+A[6]

数组C的具体含义如下图所示:

当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,

i=i-2(i)  

i=i+2(i)表示把未尾10的过程如:i=4;

a[4] à 10000  c[i]  i=i+lowbit(i)(2k) àlowbit(i)=i&i^(i-1)=i&(-i)=2(k)

a[5]à   i=5
101  i=5+1=6   110 ->i=6+2=8   1000->i=

i=13 2(0)=14  +2(1)=16

 

a[7]被修改:i=7 111  i=i+2(0)=7+1=8  1000    i=i+2(3)=16

 

 

这个操作的复杂度在最坏情况下就是树的高度即O(logn)。另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,因此,求和操作的复杂度也是O(logn)

树状数组能快速求任意区间的和:A[i] + A[i+1] + … + A[j],设sum(k) = A[1]+A[2]+…+A[k],则A[i]
+ A[i+1] + … + A[j] = sum(j)-sum(i-1)

下面给出树状数组的C语言实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//2^k

  int lowbit(int t)

  {       return t & ( t ^ ( t - 1 ) );  }

  //求前n项和

  int sum(int end)

  {      int sum = 0;

     while(end > 0)

    {

       sum += in[end];

       end -= lowbit(end);

    }

    return sum;

  //增加某个元素的大小

  void plus(int pos, int num)

  {

     while(pos <= n)

    {  

     in[pos] += num;

       pos += lowbit(pos);

    }

  }

3、扩展——二维树状数组

一维树状数组很容易扩展到二维,二维树状数组如下所示:

C[x][y] = sum(A[i][j])

其中,x-lowbit[x]+1 <= i<=xy-lowbit[y]+1 <= j <=y

2    二维树状数组:

一个由数字构成的大矩阵,能进行两种操作

1) 对矩阵里的某个数加上一个整数(可正可负)

2) 查询某个子矩阵里所有数字的和

要求对每次查询,输出结果

5、总结

树状数组最初是在设计压缩算法时发现的(见参考资料1),现在也会经常用语维护子序列和。它与线段树(具体见:数据结构之线段树)比较在思想上类似,比线段树节省空间且编程复杂度低,但使用范围比线段树小(如查询每个区间最小值问题)。

 

 

C++源程序L

#include<iostream>
#include<fstream>
using namespace std;
ifstream in("树状数组.in");
ofstream out("树状数组.out");

#define max 16
int n,a[max],c[max],m;
int lowbit(int i)
{
    return (i&-i);
}
void genxin(int i,int x)
{
while(i<=n)
{
c[i]=c[i]+x;//等于前面已有的元素加上现在读入的数 
i=i+lowbit(i);//i未尾补0 
}
}
int getsum(int i)
{
 int sum=0;
 while(i>0)
 {
 sum=sum+c[i];//累和
 i=i-lowbit(i);//把i未尾的1减去           
 }  
 return sum;
}
void init()
{
     in>>n;
for(int i=1;i<=n;i++)
  {
        in>>a[i];
        genxin(i,a[i]);
  }  
}

void print()
{
     cout<<"原数组为:"; 
     for(int i=1;i<=n;i++)
     cout<<a[i]<<"  ";
     cout<<endl<<" 树状数组为: ";
     for(int i=1;i<=n;i++)
     cout<<c[i]<<"  ";
     cout<<endl;
}
int main()
{
    init();
    print();
    cout<<"输入m求和"<<endl;
    cin>>m;
    cout<<getsum(m)<<endl<<endl;
    system("pause");
    }

抱歉!评论已关闭.