树状数组
问题引入
树状数组是一种实现起来比较简单的高级数据结构。
我们知道,对于一个数组a[i],其前缀和s[i]表示a数组里面前i个元素之和,而求区间l到r的元素之和可以用s[r] - s[l-1]来求。
现在有个单点修改,区间查询的问题,也就是修改原始数组a中某个元素,然后查询某段区间内元素之和。
暴力做法修改a中某个元素的时间复杂度是O(1),查询区间和复杂度是O(n);如果将前缀和数组利用起来,那么查询区间和操作的复杂度固然可以降为O(1),但是单次修改操作后更新前缀和数组的复杂度却是O(n)的。
现在我们需要实现修改和查询操作复杂度均是O(logn)的一个算法。
lowbit运算
二进制位运算可以很简单的实现一些功能,比如x & (x - 1)就可以将x的二进制表示中最后一个1去掉,而lowbit运算则可以只保留一个整数的最后一个1,lowbit(x) = -x & x是怎么实现这个功能的呢?(如果不想了解其中原理,可以跳过后面三段,减轻思维负担。)
首先,二进制的编码方式分为原码、补码和反码,正数的原码与补码、反码相同,负数的反码等于原码除符号位外按位取反,负数的补码等于原码除符号位外按位取反后加上1,而计算机中存储的就是整数的补码形式。
比如x = 0,1010,开头的0表示符号位,-x的原码就是1,1010,反码是1,0101,补码就是1,0110,
那么x & -x = 0,0010,与x的0,1010相比只保留了最后一个1.更加清楚的解释就是开始x最后一个1后面跟着若干个0,比如说1000对x按位取反后最后一个1变成了0,后面的数都是1,也就是0111,再加上个1就会连续进位成为1000,和x最开始的样子保持一致,而x的最后一个1之前的元素都取反了与x相与自然都变成了0.这样就保留了x的最后一个1。
int lowbit(int x){
return -x & x;
}
树状数组的定义
现在我们知道了lowbit(x)可以提取x的最后一个1,那么要如何在对数时间复杂度内实现单点修改和区间查询操作呢?我们先来分析下使用前缀和数组进行单点修改的复杂度为什么是O(n)的,s[i]存储着前i个元素的和,换句话说,s[i]管理着前i个元素,而a[i]被后面n - i个元素管理着。如果我们修改了a[i],那么从s[i]一直到s[n]这n - i + 1个元素的前缀和都改变了,归根结底就是因为s[n]存储着n个元素的和,一旦修改,牵一发而动全身。如果我们定义一个新的前缀和数组tr[n]来存储n前面logn个元素的和呢?那么相当于tr[i]只管理着logi个元素,只有这logi个元素其中的一个改变了,tr[i]才会改变,修改的复杂度就会大大降低了。
现在我们定义tr[n]为第n个元素及其之前的lowbit(n) - 1个元素之和,比如n = 6时,6 = (0110),lowbit(6) = (10) = 2,tr[6] = a[6] + a[5]。
如图所示,图中的C数组就是我们说的tr数组,我们将tr[i]管理的元素视为i的孩子节点,比如tr[6]是求第五个和第六个元素的和,那么tr[5]就是tr[6]的孩子节点。为什么tr[i]要管理着前面的lowbit(i)个元素呢?这是由二进制分组决定的,比如7 = 0111 = 0001 + 0010 + 0100 = 1 + 2 + 4,我们将前七个元素分为三组,第一组7,第二个6 5,第三组4 3 2 1,可以看出,每组的个数恰好是lowbit的值,lowbit(7) = 1,剩下6个元素,lowbit(6) = 2,剩下4个元素,lowbit(4) = 4。也就是:
tr[7] = a[7]
tr[6] = a[6] + a[5]
tr[4] = a[4] + a[3] + a[2] + a[1]
我们求前7个元素之和s7 = tr[7] + tr[6] + tr[4],求前x个元素之和时,x中有几个1,lowbit运算就会执行几次,前x个元素也就会分成多少组,而x中1的个数必然不超过x的字长,也就是区间求和操作最多对logn个元素进行求和,实现了求和的O(logn)的复杂度。
那么修改操作的复杂度呢?我们知道对于节点x而言,其前面的lowbit(x) - 1个元素都是x的孩子节点,它们的改变都会引起tr[x]的改变,所以我们修改了x,对于下标小于x的元素而言,是没有影响的,因为tr[i]只会管理前面的元素,修改x,首先改变的就是a[x],继而改变了tr[x],然后一路沿着x的父节点往上,影响着x的祖先节点的值,那么如何求x的父节点呢?也就是管理着x的节点。
比如tr[12],12 = 1100,lowbit(12) = 4,也就是12管理着12,11,10,9四个节点,对于其中的任一个孩子节点比如10 = 1010而言lowbit(10) = 2,10 + 2 = 12,10的父节点就是12,再比如9 = 1001,lowbit(9) = 1,9 + 1 = 10也就是说,9的父节点是10。lowbit(9) = 1,所以9只管理着自己,然后10管理着10,9,12管理着12,11,10,9,再之后就是16管理着前16个元素。我们得出的结论是,节点x的父节点是x + lowbit(x)。
树状数组的实现
如果不想理解细节,只考虑实现,只需要理解三点:<