神奇的樹狀數組

最近在學習位運算,正好把樹狀數組總結下,也算是能正式給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)的刪改,並無什麼卵用。code

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

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數組聯繫起來?it


來觀察這個圖:
io

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

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,算這個2^k有一個快捷的辦法,定義一個函數以下便可(求解2^k即求二進制碼右邊第一位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 開始的。


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

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息