树状数组略解

来源:互联网 发布:手机淘宝众筹在哪进入 编辑:程序博客网 时间:2024/05/24 05:03

今天比赛的时候好多树状数组的题,这里总结一下树状数组的用处。 
首先不得不说树状数组的思想简洁而又深刻,短短几行代码,诠释了什么叫“大道至简”,我想算法的魅力或许就在于此。

今天比赛的时候,看似简单的题总是超时,当时就敏锐的想到用树状数组解决,然而由于不太熟悉,自己又在本子上推了一遍,最后还是有几道题没来得及看,现在赶紧回来总结一下树状数组。(纯手打,不容易!)

先讲讲树状数组的用处,毕竟有了需求,才有学习的动力。 
对普通数组进行M次修改或求和,时间复杂度为O(M*N),N为修改或求和需要扫描的区间大小。而对于树状数组,时间复杂度则为O(M*lgN)。加了一个lg,学过数学的我们应该都知道差距有多大。

在讲实现之前,我们需要先理解一个函数lowbit(x),这是一个自定义的函数,函数名是约定成俗的,作用就是返回x的二进制表示中最后一位1的权值 
代码实现

int lowbit(int x)//位运算,利用计算机补码特性{    return x&-x;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

写个长注释,假设x=10 
10的二进制:1010,我们知道,一个数前加符号,就是用这个数的二进制取反加一。 
-10的二进制:0101+1=0110 
然后位运算符&(按位与),1010&0110=10,十进制表示就是2 
所以lowbit(10)=2 
这个函数很重要,所以先在这里交代清楚原理。

下面上图,讲讲实现树状数组(图来自百度百科) 
这里写图片描述 
图中有两个数组,数据都接收到底层数组a中,而数组c则是树状数组(看形状是不是很像个树)。 从图中可以直观的看到 
c[1]=a[1]; 
c[2]=a[1]+a[2]; 
c[3]=a[3]; 
c[4]=a[1]+a[2]+a[3]+a[4]; 
c[5]=a[5]; 
c[6]=a[5]+a[6]; 
c[7]=a[7]; 
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]; 
假设结点为n,那么结点n所管辖的区间为2的lowbit(n)次方 
即:c[n]=a[n-(2^lowbit(n))+1]+…..+a[n]; 
这样通过lowbit函数,把底层数组a和树状数组c联系了起来。

求数组a的前n位和 
代码

int sum(int n){      int sum=0;      while(n>0)      {          sum+=c[n];          n=n-lowbit(n);    }      return sum;  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

假设n=8,lowbit(8)=8,while循环只持续了一次,得到的结果c[8]也与实际吻合 
(发现没有,求前8位和,实际上只循环运算了1次!) 
假设n=6,lowbit(6)=2,获取c[6]=a[5]+a[6]后,n=4;lowbit(4)=4;获取c[4]=a[1]+a[2]+a[3]+a[4];最终获取的就是a[1]+..+a[6]; 
(求前6位和,实际只循环运算了2次!) 
还理解不了?那简单,把代码背下来,记住这个函数的返回值就是前n位和。

下面是修改,将数组a的第k位增加(或减少)num 
代码

void add(int k,int num)  {      while(k<=n)//n是树状数组的大小     {          c[k]+=num;        k=k+lowbit(k);//由于每次跳lowbit(k)位,所以时间复杂度大大降低,为什么跳lowbit(k)位操作,因为你求和的时候也是跳lowbit(k)位求和啊。     }  }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

说了那么多,把我自己都讲糊涂了,本来我是直接套用函数简单粗暴,这样一写,我发现树状数组其实讲的就是二进制,


原创粉丝点击