樹狀數組解決數組單點更新后快速查詢區間和的問題


作者: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如果是偶數,則按如下規律:

00100001 ~ 0010的累加和,
0010000001 ~ 00100的累加和,
01100101 ~ 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);
 }

線段樹和樹狀數組題目

segment-tree

binary-indexed-tree

更多

算法和數據結構筆記

參考資料

程序員代碼面試指南(第2版)

算法和數據結構體系班-左程雲


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM