作者:Grey
要解決的問題
數組在不變的情況下,前綴和數組可以用來加速生成i ~ j
位置的累加和信息, 假設前綴和數組為preSum
,那么i...j
的累加和
sum[i...j] = preSum[j] - preSum[i-1]
但是如果數組要單點修改,則以上的情況不適用,樹狀數組(index tree)就是解決這個問題,時間復雜度可以達到O(logN)。
注:本文所有涉及到的數組操作均從下標1開始計算,下標0位置棄而不用
用輔助數組保存累加信息
申請一個輔助數組help
, 大小和原始數組arr
一樣,保存原始數組的累加信息,但是要基於如下規則來保存。
遍歷的位置i如果是奇數,則只管自己這個位置的內容,即:arr[i] = help[i]
。
遍歷的位置i如果是偶數,則按如下規律:
0010
管0001 ~ 0010
的累加和,
00100
管 00001 ~ 00100
的累加和,
0110
管 0101 ~ 0110
的累加和
...
即某個偶數位置接管的區間是:把這個偶數位置二進制最右邊的1抹掉后再加1得出的位置一直到這個位置本身。
比如:
1010111000
這個位置,把最右邊的1抹掉后,值為1010110000
,再加1,值為1010110001
,所以1010111000
這個位置接管的累加和是從1010110001 ~ 1010111000
。
通過以上做法生成help
數組后,我們可以通過這個help
數組快速響應單點更新,並快速求得前綴和。
根據help
數組快速計算前綴累加和
按如上流程生成help
數組后,如果要計算1..i
位置的累加和,則有如下規則:
第一步,提取出i最右側的1,假設為x,將help[i] + help[i-x]
,得出a1
第二步,繼續提取i-x最右側的1,假設為y,將a + help[i- x - y]
,得出a2
...
直到i提取完所有最右側的1,求累加,得到的結果即為1...i
上的累加和。
public int sum(int index) {
int ret = 0;
while (index > 0) {
ret += tree[index];
index -= index & -index;
}
return ret;
}
其中index&-index
即為index最右側的1。
i位置的前綴和
如果單點有更新,比如需要在index位置上的值增加一個d,此時需要考慮哪些位置受到了牽連。
單點的二進制最末尾的1加1,依次到數組結尾,都是受到牽連的位置
public void add(int index, int d) {
while (index <= N) {
tree[index] += d;
index += index & -index;
}
}
index += index & -index;
即為受到牽連的位置,所以這些位置都要執行加d的操作。
線段樹 VS 樹狀數組
線段樹是樹狀數組的升級版,樹狀數組只能做到單點更新后,維持累加和信息的快速更新,線段樹可以支持范圍更新,但是樹狀數組可以很方便改成二維或者三維的,對於線段樹來說,改成二維的太復雜。
二維樹狀數組
二維樹狀數組主要解決:在單點變化時候,快速更新從左上角位置(1,1)
累加到(i,j)
位置的累加和信息這個問題。
熟悉一維數組后,二維的樹狀數組比較簡單,原先一維數組只需要考慮1...i
位置累加和,現在二維除了考慮1...i
位置累加和,還要考慮1...j
位置的累加和
二維樹狀數組的sum
方法和update
方法如下
private int sum(int row, int col) {
int sum = 0;
for (int i = row + 1; i > 0; i -= i & (-i)) {
for (int j = col + 1; j > 0; j -= j & (-j)) {
sum += tree[i][j];
}
}
return sum;
}
public void update(int row, int col, int val) {
if (N == 0 || M == 0) {
return;
}
int add = val - nums[row][col];
nums[row][col] = val;
for (int i = row + 1; i <= N; i += i & (-i)) {
for (int j = col + 1; j <= M; j += j & (-j)) {
tree[i][j] += add;
}
}
}
即在一維的條件下,增加了一個循環。
現在有了二維樹狀數組,如果要求整個二維平面中,任意[row1,col1]
位置到[row2,col2]
位置組成的矩形累加和信息,則可以很方便通過二維數狀數組計算出來:
public int sumRegion(int row1, int col1, int row2, int col2) {
if (N == 0 || M == 0) {
return 0;
}
return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
}