定义
根据维基百科的定义:
A Fenwick tree or binary indexed tree is a data structure that can efficiently update elements and calculate prefix sums in a table of numbers.
也就是说,所谓树状数组,或称Binary Indexed Tree, Fenwick Tree,是一种用于高效处理对一个存储数字的列表进行更新及求前缀和的数据结构。
举例来说,树状数组所能解决的典型问题就是存在一个长度为n
的数组,我们如何高效进行如下操作:
update(idx, delta)
:将num
加到位置idx
的数字上。prefixSum(idx)
:求从数组第一个位置到第idx
(含idx
)个位置所有数字的和。rangeSum(from_idx, to_idx)
:求从数组第from_idx
个位置到第to_idx
个位置的所有数字的和
对于上述问题,除去每次求和都对原数组相关数字暴力相加求和的解法外,另一种较简单解法为使用O(n)
时间构造一个_前缀和数组(cumulative sum)_,即该数组中的第i
个位置保存原数组中前i
个元素的和,则对于上述每一个操作,我们有:
update(idx, delta)
:更新操作需要更新cumulative sum数组中每一个受此更新影响的前缀和,即从idx
其到最后一个位置的前缀和。该操作为O(n)
时间复杂度。prefixSum(idx)
:直接返回cumulativeSum[idx + 1]
即可。该操作为O(1)
时间复杂度。rangeSum(from_idx, to_idx)
:直接返回cumulativeSum[to_idx + 1] - cumulativeSum[from_idx]
即可。该操作为O(1)
操作。
可以看出,该简单解法的求和操作非常高效,而单个更新操作为线性时间。如果所需的更新操作的数量远少于求和操作的话,该解法非常合适。反之,如果更新操作较多,我们就需要思考优化的方法。
那么使用树状数组解决该问题的目的就是为了在保证求和操作依然高效的前提下优化update(idx, delta)
操作的时间复杂度。
填坑法构造Binary Indexed Tree
所谓的Binary Indexed Tree,首先需要明确它其实并不是一棵树。Binary Indexed Tree事实上是将根据数字的二进制表示来对数组中的元素进行逻辑上的分层存储。
Binary Indexed Tree求和的基本思想在于,给定需要求和的位置i
,例如13,我们可以利用其二进制表示法来进行分段(或者说分层)求和:13 = 2^3 + 2^2 + 2^0
,则prefixSum(13) = RANGE(1, 8) + RANGE(9, 12) + RANGE(13, 13)
(注意此处的RANGE(x, y)
表示数组中第x
个位置到第y
个位置的所有数字求和)。如下面例子中所示:
arr = [1, 7, 3, 0, 5, 8, 3, 2, 6, 2, 1, 1, 4, 5]
prefixSum(13) = RANGE(1, 8) + RANGE(9, 12) + RANGE(13, 13)
= 29 + 10 + 4 = 43
那么如果我们将上述的range sum提前计算好的话,prefixSum(13)
可以直接由它们相加得到。那么我们所需要解决的问题就是,根据何种规则来计算和存储这样的二进制表示后所需的range sum呢?规则如下图中所示。
图中第一行为原数组,第二到第四行为依次按层填坑的过程。我们需要从左到右,从上到下依次将相应的值填入对应的位置中。最后一行中即为最终所形成的树状数组。
以图中第二行,也就是构造树状数组第一层的过程为例,我们首先需要填充的是数组中第一个数字开始,长度为__2的指数__个数字的区间内的数字的累加和。所以图中分别填充了从第一个数字开始,长度为2^0, 2^1, 2^2, 2^3
的区间的区间和。到此为止这一步就结束了。因为2^4
超过了我们原数组的长度范围。
下一步我们构造数组的第二层。与上一层类似,我们依然填充余下的空白中从第空白处一个位置算起长度为__2的指数__的区间的区间和。例如3-3
空白,我们只需填充从位置3开始,长度为1的区间的和。再如9-14
空白,我们需要填