【算法】树状数组
树状数组(Binary Indexed Tree / Fenwich Tree)
1 算法特性
- 以下讨论中,数组下标从 1 开始而不是 0 ;以二进制表示整数时,LSB 在右侧,比特下标从 0 开始(例如一个字节表示为 \(b_7b_6 \ldots b_0\) ).
- 对于一个元素序列 \(a_1, a_2, \ldots, a_n\) ,考虑以下两种操作:
- 操作一:读写任意元素 \(a_k, k \in [1, n]\).
- 操作二:计算元素序列之和 \(\displaystyle\sum_{i = j}^{k}a_i = a_j + a_{j+1} + \cdots + a_{k}\).
- 如果使用元素数组 \(\mbox{Array}\) ,即 \(\mbox{Array}[i]\) 用于存储 \(a_i\) ,那么操作一的开销为 \(O(1)\) ,操作二的开销为 \(O(n)\).
- 如果使用前 \(n\) 项和数组 \(\mbox{Sum}\) ,即 \(\mbox{Sum}[n]\) 用于存储 \(\displaystyle\sum_{i=1}^{n}a_i=a_1+a_2+\cdots+a_n\) ,那么操作一的开销为 \(O(n)\) ,操作二的开销为 \(O(1)\).
- 考虑 \(m\) 次任意操作,最坏情况下两种数据结构的开销都是 \(O(m * n)\).
- 今天讨论的树状数组 \(\mbox{Tree}\) 可以将最坏情况下 \(m\) 次任意操作的开销降低为 \(O(m\log\,n)\) ,即对于操作一和操作二,开销均为 \(O(\log\,n)\).
2 算法描述
2.1 求和
- 对于数组下标 \(index\) ,现在考虑通过对应的数组元素 \(\mbox{Tree}[index]\) ,求出序列 \(\{a_n\}\) 的前 \(index\) 项的和,即求 \(\mbox{Sum}[index]\).
- 我们考虑下标的二进制表示: \(index = 2^{e_1} + 2^{e_2} + \cdots + 2^{e_k}\) ,其中 \(e_1 > e_2 > \cdots > e_k\).
- 我们可以将 \(\mbox{Sum}[index]\) 拆分成 \(k\) 个互不重叠的组,分别包含 \(2^{e_1}, 2^{e_2}, \ldots, 2^{e_k}\) 个元素,分别求和后再相加.
- 例如, \(index = 13 = 8 + 4 + 1 = 2^3 + 2^2 + 2^0\) ,我们可以将 \(\mbox{Sum}[13]\) 拆分成三个组,分别含有 \(8=2^3,4=2^2,1=2^0\) 个元素.
- 我们规定:\(\mbox{Tree}[index]\) 保存的是上述划分中,元素数目为 \(2^{e_k}\) 的那个组中所有元素的和.
- 考虑到下标的这种特殊意义,那么通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) 可以通过递归方式进行:
- 将计算结果 \(\mbox{sum}\) 初始化为零: \(\mbox{sum} \leftarrow 0\).
- 将 \(\mbox{Tree}[index]\) 加入到计算结果中: \(\mbox{sum} \leftarrow \mbox{sum} + \mbox{Tree}[index]\).
- 将 \(index\) 的二进制表示中从左到右最后一个 \(1\) 改为 \(0\) ,得到新的数组下标 \({index}' = 2^{e_1} + 2^{e_2} + \cdots + 2^{e_{k - 1}}\).
- 若 \({index}'\) 的值为零则算法终止,否则 \(index \leftarrow {index}'\) ,回到步骤 2.
- 算法的执行过程中执行了若干次步骤 2,依次将 \(2^{e_k}, 2^{e_{k-1}},\ldots,2^{e_1}\) 个元素加入到计算结果中,最终计算结果将包含 \(index\) 个元素之和.
- 显然对于读写元素操作和序列求和操作,开销均为 \(O(\log\,n)\).
- 到这里问题已经解决了一半,另一半是,确保前 \(index\) 项分成的 \(k\) 个组互不重叠.
- 按照之前的规定,能够通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) ,并能够通过 \(\mbox{Tree}[{index}']\) 求 \(\mbox{Sum}[{index}']\)
- 那么 \(\mbox{Sum}[index] = \mbox{Tree}[index] + \mbox{Sum}[{index}']\).
- 那么 \(\mbox{Tree}[index] = \mbox{Sum}[index] - \mbox{Sum}[{index}']\).
- 也就是说 \(\mbox{Tree}[index]\) 中存储的是下标范围在 \([index-2^r+1, index]\) 范围内的所有元素之和,其中 \(r\) 是 \(index\) 的二进制表示中从左到右最后一个 1 的位置.
- 例如,一个包含 16 个元素的数状数组的具体情况如下:
树状数组下标 下标二进制表示 元素下标范围 图示 1 0001 \([\phantom{0}1, \phantom{0}1] = [\phantom{0}1-2^0+1, \phantom{0}1]\) \(\qquad \underline{a_1} \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 2 0010 \([\phantom{0}1, \phantom{0}2] = [\phantom{0}2-2^1+1, \phantom{0}2]\) \(\qquad \underline{a_1 \quad a_2} \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 3 0011 \([\phantom{0}3, \phantom{0}3] = [\phantom{0}3-2^0+1, \phantom{0}3]\) \(\qquad a_1 \quad a_2 \quad \underline{a_3} \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 4 0100 \([\phantom{0}1, \phantom{0}4] = [\phantom{0}4-2^2+1, \phantom{0}4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 5 0101 \([\phantom{0}5, \phantom{0}5] = [\phantom{0}5-2^0+1, \phantom{0}5]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5} \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 6 0110 \([\phantom{0}5, \phantom{0}6] = [\phantom{0}6-2^1+1, \phantom{0}6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 7 0111 \([\phantom{0}7, \phantom{0}7] = [\phantom{0}7-2^0+1, \phantom{0}7]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad \underline{a_7} \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 9 1001 \([\phantom{0}9, \phantom{0}9] = [\phantom{0}9-2^0+1, \phantom{0}9]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9} \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 10 1010 \([\phantom{0}9, 10] = [10-2^1+1, 10]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10}} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 11 1011 \([11, 11] = [11-2^0+1, 11]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad \underline{a_{11}} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\) 13 1101 \([13, 13] = [13-2^0+1, 13]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13}} \quad a_{14} \quad a_{15}\) 14 1110 \([13, 14] = [14-2^1+1, 14]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13} \quad a_{14}} \quad a_{15}\) 15 1111 \([15, 15] = [15-2^0+1, 15]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad \underline{a_{15}}\) - 例如,通过 \(\mbox{Tree}[6]\) 求 \(\mbox{Sum}[6]\) :
树状数组下标 下标二进制表示 元素下标范围 图示 6 0110 \([5, 6] = [6-2^1+1, 6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 4 0100 \([1, 4] = [4-2^2+1, 4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) - 例如,通过 \(\mbox{Tree}[13]\) 求 \(\mbox{Sum}[13]\) :
树状数组下标 下标二进制表示 元素下标范围 图示 13 1101 \([13, 13] = [13-2^0+1, 13]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13}} \quad a_{14} \quad a_{15}\) 12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\) 8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) - 例如,通过 \(\mbox{Tree}[15]\) 求 \(\mbox{Sum}[15]\) :
树状数组下标 下标二进制表示 元素下标范围 图示 15 1111 \([15, 15] = [15-2^0+1, 15]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad \underline{a_{15}}\) 14 1110 \([13, 14] = [14-2^1+1, 14]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13} \quad a_{14}} \quad a_{15}\) 12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\) 8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) - 求和需要依次访问数组中的一些元素,将各个数组下标看作节点,将下标的变换看作边,那么将得到一个树形结构,这是“树状数组”名字的由来:

- 还有一个问题,那就是怎样高效地从数组下标的二进制表示中去掉最后一个 1.
- 若 \(index\) 非零,则其二进制表示可以写为 \(p1q\) ,其中 \(p\) 和 \(q\) 都为二进制串,并且 \(q\) 全部由 0 组成.
- 对该二进制串取反得到 \(\bar{p}0\bar{q}\) ,由于 \(q\) 全部由 0 组成,所以 \(\bar{q}\) 全部由 1 组成.
- 在 \(\bar{p}0\bar{q}\) 上加 1 得到 \(\bar{p}1q\) ,将该串与原串 \(p1q\) 进行按位与运算得到 \(\bar{p}1q \wedge p1q = p'1q\) ,其中 \(p'\) 全部由 0 组成并且与 \(p\) 位数相同.
- \(p'1q\) 中只包含一个 1,并且就是 \(index\) 的二进制表示中最后一个 1 ,现在可以通过减法将其去掉.
- 取反然后加一,这个操作其实就是求补码,所以通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) 的过程的代码片段可以这样写:
int sum(int tree [], int index) { int result = 0; while ( index > 0 ) { result += tree[index]; index -= ( index & -index ); } return result; }
2.2 更新元素
- 前面说过 \(\mbox{Tree}[index]\) 存储的是若干个元素的和,当更新序列元素的时候,所有求和范围包含该序列元素的数组元素都必须更新.
- 还是以前面包含 15 个元素的树状数组作为例子,当要更新 \(a_1\) 时,需要更新的数组元素包括:
树状数组下标 下标二进制表示 元素下标范围 图示 1 0001 \([1, 1] = [1-2^0+1, 1]\) \(\qquad \underline{a_1} \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 2 0010 \([1, 2] = [2-2^1+1, 2]\) \(\qquad \underline{a_1 \quad a_2} \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 4 0100 \([1, 4] = [4-2^2+1, 4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 8 1000 \([1, 8] = [8-2^3+1, 8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) - 当要更新 \(a_5\) 时,需要更新的数组元素包括:
树状数组下标 下标二进制表示 元素下标范围 图示 5 0101 \([5, 5] = [5-2^0+1, 5]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5} \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 6 0110 \([5, 6] = [6-2^1+1, 6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) 8 1000 \([1, 8] = [8-2^3+1, 8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\) - 我们发现需要更新的数组元素下标具有规律性:相邻两次更新中数组下标之差为 2 的幂,换句话说这个差值的二进制表示中只含有一个 1,并且其位置和较小的那个数组下标中最后一个 1 的位置相同.
- 这启发我们用类似之前求和的方法来更新元素,代码片段可以这样写:
void update(int tree [], int size, int index, int delta) { while ( index <= size ) { tree[index] += delta; index += ( index & -index ); } }
2.3 读取元素的值
- 当空间足够的时候,可以额外使用一个数组记录各个元素的值,这样读取元素取值的开销为 \(O(1)\).
- 当然我们也可以直接通过树状数组求指定下标序列元素的值.
- 一个很容易想到的策略是,分别求出前 \(i, i - 1\) 项之和,再做减法得到下标为 \(i\) 的序列元素: \(a_i = \mbox{Sum}[i] - \mbox{Sum}[i - 1]\).
- 回忆之前对“树状数组”名字的解释,每次求和都是沿着树中某条路径向上访问直到树根,那么两次求和所对应的两条路径重合的部分我们可以不必进行求和.
- 进一步想,两条路径在什么情况下会重合,为了回答这个问题,我们回到下标的二进制表示上来.
- 假设下标 \(index\) 的二进制表示为 \(p1q\) ,其中 \(p, q\) 都是二进制串,并且 \(q\) 全部由 0 组成.
- 那么 \(index - 1\) 的二进制表示为 \(p0\bar{q}\) ,其中 \(\bar{q}\) 全部由 1 组成.
- 由于下标的二进制表示和路径上的点时一一对应的,下标通过将最后一个 1 变成 0 发生变化,那么两个二进制表示的最长公共前缀就对应了公共路径.
- 上面两个二进制表示的公共部分是 \(p1q \wedge p0\bar{q} = p0q\) ,其实“与运算”不能求任意两个二进制串的最长公共前缀,但对于 \(index\) 和 \(index - 1\) 这是合适的.
- \(p0q\) 还可以通过 \(p1q\) 去掉最后一个 1 得到,回忆之前的迭代式 \(\mbox{Sum}[index] = \mbox{Tree}[index] + \mbox{Sum}[{index}']\) ,其中的 \(index'\) 的二进制表示就是 \(p0q\).
- 换句话说,在求 \(\mbox{Sum}[index]\) 和 \(\mbox{Sum}[index - 1]\) 的过程中都会通过迭代进入求 \(\mbox{Sum}[index']\) 的过程,这两个重复的求和过程我们可以设法省略.
- 再换句话说,由于 \(\mbox{Tree}\) 中的元素对应一组序列元素之和,求下标为 \(index\) 的序列元素其实就是从 \(\mbox{Tree}[index]\) 中减去所有下标不是 \(index\) 的元素.
- 读取序列元素值的代码片段可以这样写:
int read(int tree [], int index) { int idx = index - ( index & -index ); int sum = tree[index]; index--; while ( index != idx ) { sum -= tree[index]; index -= ( index & -index ); } return sum; }
- 话说上面的代码中
idx还可以通过index & ( index - 1 )得到. - 这种改进不能降低时间复杂度的阶,例如当要访问的元素下标的二进制表示中之含有一个 1 的时候.
2.4 查找元素
- 有时候我们会有这样的需求,找到某个元素,使其前 \(n\) 项和为给定的值.
- 利用 \(\mbox{Sum}\) 数组和二分算法,单次查找的开销为 \(O(\log\,n)\) ,但更新数据的开销为 \(O(n)\) ,所以只适合静态数据.
- 利用 \(\mbox{Tree}\) 数组,可以使得查找和更新数据的开销都为 \(O(\log\,n)\).
- 我们用定长二进制串表示下标,二进制串的长度由最大下标决定.
- 然后我们从高到低依次确定二进制串的每一位,由于前 \(n\) 项和跟 \(n\) 是正相关的,因此按照这个顺序可以正确逼近目标下标.
- 注意,如果序列元素中存在负值,那么前面提到的所有与二分法有关的方法都不适用,因为前 \(n\) 项和不再是单调的了.
- 代码片段:
int tree[MAX_VALUE + 1]; int find(int tree [], int sum) { int index = 0; int bit_mask = BIT_MASK; // The constant BIT_MASK is the highest bit of MAX_VALUE. while ( bit_mask != 0 ) { int new_index = index + bitmask; if ( ( new_index <= MAX_SIZE ) && ( sum >= tree[new_index] ) ) { index = new_index; sum -= tree[new_index]; } bit_mask >>= 1; } return ( bitMask == 0 ? index : -1 ); }
2.5 多维数状数组
- 数状数组可以很容易地扩展到多维.
- 例如,更新二维树状数组的元素:
int tree[MAX_X + 1][MAX_Y + 1]; void update(int x, int y, int delta) { int i, j; for ( i = x; i <= MAX_X; i += ( i & -i ) ) { for ( j = y; j <= MAX_Y; j += ( j & -j ) ) { tree[i][j] += delta; } } }
- 例如,求矩形区域 \((1, 1), (x, y)\) 内所有元素之和:
int tree[MAX_X][MAX_Y]; int sum(int x, int y) { int result = 0; int i, j; for ( i = x; i > 0; i -= ( i & -i ) ) { for ( j = y; j > 0; j -= ( j & -j ) ) { result += tree[i][j]; } } return result; }
- 由于每一维的空间可能很大,所以通常多维树状数组在空间上不可行.
posted on 2013-06-27 00:08 SinceBelieve 阅读(258) 评论(0) 收藏 举报

浙公网安备 33010602011771号