「樹狀數組」第 1 節:樹狀數組能解決的問題

關鍵詞:位運算、前綴和的查詢與更新。

第 1 節 樹狀數組能解決的問題

樹狀數組,也稱作「二叉索引樹」(Binary Indexed Tree)或 Fenwick 樹。
它能高效地實現下面兩個操作:

  • 數組的「前綴和」查詢;
  • 數組的「單點更新」。

在這裏插入圖片描述

下面具體解釋這兩個操作。

數組的「前綴和」查詢

首先看下面這個例子,瞭解什麼是數組的前綴和查詢。

例 1

已知數組 arr = [10, 15, 17, 19, 20, 14, 12],求下標 0 至下標 4 的所有元素的和。
分析:

  • 「前綴和」定義了一個數組從「頭」開始的區間,計算的是從下標位置是 0 開始的區間內的所有元素的和;
  • 注意:理解「前綴」的意思,下標位置必須從 0 開始計算;
  • 其它不是從 0 開始的數組的區間和可以轉化成前面的這個問題。

解:在 Python 語言中,可以使用 sum(arr[0:5]) 得到下標 0 至下標 4 的所有元素的和。
說明:在 Python 的語法中,切片操作不包括結下標的數值,因此 arr[0:5]=[arr[0], arr[1], arr[2], arr[3], arr[4]]

數組的「單點更新」

例 2

已知數組 [10, 15, 17, 19, 20, 14, 12]

  • 將下標爲 4 的元素增加 2
  • 將下標爲 6 的元素減少 3

分析:

  • 給出這個例題只是爲了讓大家熟悉這個提法,「單點更新」並不關心這個數「變成了什麼」,它的提法是給出一個數變化了多少,因爲增加一個數等價於減去這個數的相反數,因此以上兩個提法其實可以合併成:將某個下標的元素增加多少;
  • 如果我們不使用任何任何數據結構,僅依靠定義,「單點更新」操作的時間複雜度是 O(1)O(1),數組的「前綴和」查詢的時間複雜度是 O(n)O(n),要掃描這個區間的一大部分元素,才能得到這個區間的和。優化的做法是:先計算出一個“前綴和”數組,這個數組的每個元素的值對應的正是原來數組的前綴和。

例 3

已知數組 arr = [1, 2, 3, 4, 5, 6, 7],計算「前綴和」數組 cumsum(arr)

分析:根據「前綴和」的定義,容易計算出前綴和數組是 cumsum(arr) = [1, 3, 6, 10, 15, 21, 28]
Python 代碼:

arr = [1, 2, 3, 4, 5, 6, 7]
cumsum = [0] * len(arr)
cumsum[0] = arr[0]
for i in range(1, len(arr)):
    cumsum[i] = cumsum[i - 1] + arr[i]
print(cumsum)

有了「前綴和」數組以後,每次查詢「前綴和」的時間複雜度變成了 O(1)O(1),此時計算「區間和」就容易了。

例 4

已知數組 arr = [1, 2, 3, 4, 5, 6, 7] ,求第 3 個元素到第 7 個元素(包括第 7 個元素)的和。

分析:

  • 3 個元素到第 7 個元素(包括第 7 個元素)的和可以表示爲 sum(arr[2:7])
  • 注意:第幾個元素與下標的序號有一個位置的偏移,並且 Python 中的切片不包含結尾端點);
  • 我們假設我們有了「前綴和」數組,就可以以 O(1)O(1) 時間複雜度完成這個問題。
    1 個元素到第 7 個元素(包括第 7 個元素)的和可以表示成:
cumsum(arr[0:7]) = nums[0] + nums[1] + nums[2] + nums[3] + nums[4] + nums[5] + nums[6]

1 個元素到第 2 個元素(包括第 2 個元素)的和可以表示成:

cumsum(arr[0:2]) = nums[0] + nums[1]

於是第 3 個元素到第 7 個元素(包括第 7 個元素)的和:

sum(arr[2:7]) = cumsum(arr[0:7]) - cumsum(arr[0:2])

那麼問題來了:執行「單點更新」操作,就得更新「前綴和」數組又得計算一次前綴和,時間複雜度爲 O(n)O(n)。那如果一次業務場景中計算「前綴和」和「單點更新操作」的次數都很多,使用「前綴和」數組就不高效了。而 Fenwick 樹就是一個實現了快速計算「前綴和」和「單點更新」操作這兩個操作的數據結構。

(本節完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章