理解树状数组

树状数组又名二分索引术,主要包含两种基本操作

1.Update(int i,int val)更新节点及其所有父节点及祖先节点的值,表示对第i点的值增加val。时间复杂度O(logn)

2.Sum(int i)表示对前i个点进行求和操作.时间复杂度O(logn),n表示节点总数,logn即log2n。

树状数组是通过数组来实现的一种轻量级的数据结构,性价比较高。

主要实现

定义数组C[i],A[i]。C[i]=A[i-2^k+1]+A[i-2^k+2]+......+A[i],这里k表示i在二进制表示下末尾数字0的个数。

一种较为直观的判断方法可以直接找出i的因子中2的最高次方即k同时2^k就是A[i]的个数,例如8是1000,因子有2的3次方,所以c[8]=A[1]+A[2]+......+A[8]

在这里i=8,一定是从A[x]开始一直加到A[8],8=2^3,因此判断有8个数相加,一直加到A[8]为止,所以一定是从A[1]开始加的。再如6=3*2^1,只有两个数相加,一定从A[5]开始加

见下图

根据刚才对C[i]的定义可知,当i为奇数时C[i]=A[i],当i为偶数时需要进行计算以求得i-2^k+1

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

可以直观的看到若改变某个A[i]的值,A[i]的父节点及祖先节点的值也要相应的改变

在函数实现之前先来介绍一种运算,以函数的方式来实现

int Lowbit(int x)

{

  return x&(-x);

}

这个函数求的就是2^k。

具体运算是通过补码方式来实现的,不懂补码概念的自行去脑补先

以6为例,6的原、反、补码都是0110,-6的原码是1110(假定第一位都是符号位),反码是1001,补码是1010.两个补码进行&运算得到的结果是2

再来看看函数的具体实现

1.更新操作Update(int i,int val)

假设要实现A[i]=A[i]+val的操作,这里我们不直接对A[i]直接操作,而是改变C[i]及C[i]的所有祖先节点的值

例如改变C[1]的值需要改变C[2]、C[4]、C[8]的值

void Update(int i,int val)
{
  while(i<=n)
  {
    C[i]+=val;
    i+=Lowbit(i);
  }
}

提供一种非递归的写法

void add(int i,int val)
{
  for(i;i<=n; i+=lowbit(i))
  {
    C[i] += val;
  }
}

2.求和操作Sum(int i)

求A1~Ai的和

sum(i) = sum{ A[j] | 1 <= j <= i } = A[1] + A[2] + … + A[i]
= A[1] + A[2] + A[i-2^k] + A[i-2^k+1] + … + A[i]
= A[1] + A[2] + A[i-2^k] + C[i]
= sum(i - 2^k) + C[i]
= sum( i – lowbit(i) ) + C[i]


int Sum(int i)
{
  int sum=0;
  while(i>0)
  {
    sum+=C[i];
    i-=Lowbit(i);
  }
  return sum;
}

同样提供一种非递归写法

int Sum(int i)
{
  int sum=0;
  for(i; i ; i -= lowbit(i))
  { 
    sum+=C[i];
  }
  return sum;
}

原博请见:http://www.open-open.com/lib/view/open1450603621287.html

题目推荐的话后续再更新吧,我也是刚入门>_<

原文地址:https://www.cnblogs.com/pter/p/5698127.html