神奇的樹狀數組


最近在學習位運算,正好把樹狀數組總結下,也算是能正式給data structure 建個分類。

那么,樹狀數組到底有什么用呢?誠然,一樣沒什么卵用的東西我們學它干嘛。

下面舉個樹狀數組的經典應用:區間求和

假設我們有如下數組(數組元素從 index=1 開始):

 var a = [X, 1, 2, 3, 4, 5, 6, 7, 8, 9];

我們設定兩種操作,modify(index, x) 表示將 a[index] 元素加上x, query(n, m) 表示求解 a[n] ~ a[m] 之間元素的和。如果不了解樹狀數組(當然假設更不了解線段樹等其他數據結構),你可能會很容易地寫下如下代碼:

var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function query(n, m) {
  var sum = 0;
  for (var i = n; i <= m; i++)
    sum += a[i];
  return sum;
}

function modify(index, x) {
  a[index] += x;
}

Ok,復雜度為O(1)的刪改和復雜度為O(n)的查詢。如果數據量很大,這樣反復的查詢是相當耗時的。我們退一步想,如果只有 query(n, m) 這個操作,很容易想到用sum數組預處理前n項的和,然后用 sum[m] - sum[n-1] 獲得答案。但是如果要修改 a[index] 的值,因為該項影響所有index之后的sum數組元素,所以如果這樣做復雜度變為O(1)的查詢和O(n)的刪改,並沒有什么卵用。

但是這個思路是美好的,我們可以用一個sum數組保存一段特定的區間段的值。假設我們有 a[1] ~ a[9] 9個元素,我們根據一個特定的規則:

sum[1] = a[1];
sum[2] = a[1] + a[2];
sum[3] = a[3];
sum[4] = a[1] + a[2] + a[3] + a[4];
sum[5] = a[5];
sum[6] = a[5] + a[6];
sum[7] = a[7];
sum[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];
sum[9] = a[9];

如果要求 a[1] ~ a[9] 的和,即為 sum[9] + sum[8],如果要求 a[1] ~ a[7] 的和,即為 sum[7] + sum[6] + sum[4] ,如果要改變 a[1] 的值,改變sum數組中和 a[1] 有關的項即可(即 sum[1] sum[2] sum[4] sum[8])。 這就是樹狀數組!實現了O(logn)的查詢和刪改。但是如何將a數組和sum數組聯系起來?


來觀察這個圖:

令這棵樹的結點編號為C1,C2...Cn。令每個結點的值為這棵樹的值的總和,那么容易發現(如上所說):

C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

這里有一個有趣的性質:設節點編號為x,那么這個節點管轄的區間為 2^k(其中k為x二進制末尾0的個數)個元素。因為這個區間最后一個元素必然為Ax,所以很明顯:Cn = A(n – 2^k + 1) + ... + An,算這個2k有一個快捷的辦法,定義一個函數如下即可(求解2k即求二進制碼右邊第一位1的值):

int lowbit(int x) {
  return x & (-x);
}

當想要查詢一個SUM(n)(求a[1]~a[n]的和),可以依據如下算法即可:

  1. 令sum = 0,轉第二步;
  2. 假如n <= 0,算法結束,返回sum值,否則sum = sum + Cn,轉第三步;
  3. 令n = n – lowbit(n),轉第二步。

可以看出,這個算法就是將這一個個區間的和全部加起來。

那么修改呢,修改一個節點,必須修改其所有祖先,最壞情況下為修改第一個元素,最多有log(n)的祖先。所以修改算法如下(給某個結點i加上x):

  1. 當i > n時,算法結束,否則轉第二步;
  2. Ci = Ci + x, i = i + lowbit(i)轉第一步。i = i + lowbit(i)這個過程實際上也只是一個把末尾1補為0的過程。 對於數組求和來說樹狀數組簡直太快了!

關於這部分的代碼,將在下文樹狀數組的具體三大應用中給出。

關於樹狀數組,有一點需要注意,為了方便,樹狀數組的a數組基本都是從 index=1 開始的。


下文中樓主會分析下樹狀數組的三大應用場景:改點求段,改段求點,改段求段


免責聲明!

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



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