Detailed explanation of tree array

foreword

Tree array or Binary Indexed Tree (Binary Indexed Tree), also named Fenwick tree after its inventor. Its original intention is to solve the calculation problem of cumulative frequency in data compression, and now it is mostly used to efficiently calculate the prefix sum and interval sum of the sequence . It can get arbitrary prefix sums in O(logn) time . At the same time, it supports the modification of dynamic single-point values ​​in O(logn) time . Space complexity O(n) .


1. Summary of tree array

A tree array is a data structure with log(n) query and modification complexity . It is mainly used for single-point modification of arrays && interval summation . Another one with similar functions is the line segment tree .

The specific differences and connections are as follows:

1. The two are at the same level in complexity , but the constant of the tree array is obviously better than that of the line segment tree , and its programming complexity is much smaller than that of the line segment tree .

2. The function of the tree array is completely covered by the line segment tree . Any problem that can be solved by using the tree array can be solved by using the line segment tree , but the problem that the line segment tree can solve may not be solved by the tree array .

3. The outstanding feature of the tree array is its extreme simplicity of programming . The core operation of the tree array can be completed in a few short steps by using lowbit technology , and its code efficiency is much higher than that of the line segment tree .


Second, the application of tree array

1. Single point modification + interval query

code example

int lowbit(int x)
{
  return x & (-x);//表示求数组下标二进制的非0最低位所表示的值
}

//查找1~x的和
int find_sum(int x)
{
  int ans = 0;
  while(x)
  {
    ans += c[x];//从右往左累加求和
    x -= lowbit(x);
  }
  return x;
}

//单点修改
void gexi(int x,int v)
{
  a[x] += v;
  while(x <= n)
  {
    c[x] += v;
    x += lowbit(x);//由叶子节点向上更新树状数组C,从左往右更新
  }
}

Realization principle

The three most common functions in templates:

① Take the value represented by the lowest bit of the binary subscript of the array that is not 0;

②Single-point update;

③ Interval query.

A tree array, as the name implies, is a tree array. We first introduce a binary tree, and the leaf nodes represent A[1]~A[8].

Now transform it:

Now define the top node C array of each column (in fact, the C array is a tree array), as shown in the figure:

Understand the key points of tree arrays

C[i] represents the sum of the weights of the leaf nodes of the subtree, as shown in the figure:

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];

The first is an interval query (sum):

Use the C[i] array to find the sum of the first i items in the A array , and give two chestnuts:

①i=7

The sum of the previous 7 items: sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7];

而C[4]=A[1]+A[2]+A[3]+A[4];C[6]=A[5]+A[6];C[7]=A[7];

可以得到:sum[7]=C[4]+C[6]+C[7]。

数组下标写成二进制:sum[(111)]=C[(100)]+C[(110)]+C[(111)];

②i=5

前5项和:sum[5]=A[1]+A[2]+A[3]+A[4]+A[5];

而C[4]=A[1]+A[2]+A[3]+A[4];C[5]=A[5];

可以得到:sum[5]=C[4]+C[5];

数组下标写成二进制:sum[(101)]=C[(100)]+C[(101)];

细细观察二进制,树状数组追其根本就是二进制的应用,结合代码演示一下代码过程:

//查找1~x的和
int find_sum(int x)
{
  int ans = 0;
  while(x)
  {
    ans += c[x];
    x -= lowbit(x);
  }
  return x;
}

代码推演

对于i=7进行演示:

7(111)  ans+=C[7]

lowbit(7)=001 7-lowbit(7)=6(110) ans+=C[6]

lowbit(6)=010 6-lowbit(6)=4(100) ans+=C[4]

lowbit(4)=100 4-lowbit(4)=0(000) break;

对于i=5进行演示:

5(101)  ans+=C[5]

lowbit(5)=001 5-lowbit(5)=4(100) ans+=C[4]

lowbit(4)=100 4-lowbit(4)=0(000) break;

然后单点更新:

当我们修改A数组中某个值时,应当如何更新C数组呢?回想一下,区间查询的过程,再看一下上文中列出的过程。这里声明一下:单点更新实际上是不修改A数组的,而是修改树状数组C,向上更新区间长度为lowbit(i)所代表的节点的值。

//单点修改
void gexi(int x,int v)
{
  a[x] += v;
  while(x <= n)
  {
    c[x] += v;
    x += lowbit(x);
  }

如图:当在A[1]加上值val,即更新A[1]时,需要向上更新C[1],C[2],C[4],C[8],这个时候只需将这4个节点每个节点的值加上val即可。这里为了方便大家理解,人为添加了个A数组表示每个叶子节点的值,事实上A数组并不用修改,实际运用中也可不设置A数组,单点更新只需修改树状数组C即可。下标写成二进制:C[(001)],C[(010)],C[(100)],C[(1000)];

lowbit(1)=001 1+lowbit(1)=2(010) C[2]+=val;

lowbit(2)=010 2+lowbit(2)=4(100) C[4]+=val;

lowbit(4)=100 4+lowbit(4)=8(1000) C[8]+=val;

由于c[1] c[2] c[4] c[8] 都包含有A[1],所以在更新A[1]时实际上就是更新每一个包含A[1]的节点。

总结

树状数组的重点就是利用二进制的变化动态地更新树状数组

树状数组的每一个节点并不是代表原数组的值,而是包含了原数组多个节点的值。

所以在更新A[1]时需要将所有包含A[1]的C[i]都加上val这也就利用到了二进制的神奇之处。

如果是更新A[i]的值,则每一次对C[i] 中的 i 向上更新,即每次i+=lowbit(i),这样就能C[i] 以及C[i] 的所有父节点都加上val。

反之求区间和也是和更新节点值差不多,只不过每次 i-=lowbit(i)。

Guess you like

Origin blog.csdn.net/weq2011/article/details/128835460