• 沒有找到結果。

Fenwick tree (Binary indexed tree) 這裡介紹區間元素總和問題:「給定一個長度為

N/A
N/A
Protected

Academic year: 2022

Share "Fenwick tree (Binary indexed tree) 這裡介紹區間元素總和問題:「給定一個長度為"

Copied!
5
0
0

加載中.... (立即查看全文)

全文

(1)

Fenwick tree (Binary indexed tree)

這裡介紹區間元素總和問題:「給定一個長度為 n 的序列,q 次詢問區間 [a, b] 的 總和」,該問題一直都是非常經典的資料結構問題,一個簡單的做法便是預處理序列 的「前綴和」,這是因為我們發現,「區間 [a, b] 的總和」可以轉成「區間 [1, b] 的總和」

減掉「區間 [1, a− 1] 的總和」。也因此,只需要預處理所有區間 [1, i] 的總和,便能在 O(1) 的時間內回答任意區間 [a, b] 的總和。不過,靜態的詢問往往滿足不了資訊科學 家的野心,也因此出現了「動態區間和」的問題,也就是在多次的詢問區間和之間穿 插元素值的修改,透過前綴和的轉換,我們可以把問題簡化成:如何快速的求得一個 陣列的前綴和,以及更改其中一個元素的值?

Fenwick tree 是一個處理前綴時很有效率的資料結構,他也被稱為 Binary indexed tree,或簡稱 BIT,在中國被稱做樹狀數组。以下我們都簡稱它為 BIT。對於一個長 度為 n 的陣列,BIT 可以在 O(n) 的時間初始化,在 O(log n) 時間詢問一個前綴的訊 息 (例如前綴和),以及在 O(log n) 的時間修改其中一個值。BIT 的時間常數比大多 數能維護區間訊息的資料結構都來得小 (時間複雜度會把常數拿掉,但實際上還是有 影響的),空間也非常小 (只需要多開一個大小恰好為 n 的陣列即可),程式碼也非常 精簡 (初始化 + 修改 + 詢問,全部約 20 行)。一般來說,如果問題的性質可以使用 BIT,效率會和使用其他區間資料結構有明顯的差別。當然,BIT 的缺點就是有些問 題無法轉為前綴之間的運算,例如區間 [a, b] 的最大值就無法由區間 [1, b] 和 [1, a− 1]

的最大值算出,這時候就無法使用它了。以下將詳細介紹 BIT 的操作。

首先,我們先介紹一個函數:lowbit(x),表示 x 在二進位表示時,最接近 LSB(Least Significant Bit),也就是最靠右邊的 1 所對應的值。例如十進位下的數字 6(10),在二 進位的寫法是 110(2),其中的兩個 1 分別表示 22 和 21,因此 lowbit(6) = 21 = 2。同 理,20(10) = 10100(2),所以 lowbit(20) = 22 = 4。lowbit 函數可以用位元運算在 O(1) 時間內得到,在作業中會再多做說明。

為了快速回答問題又能動態修改,BIT 先記錄了一些區間的訊息,並由這些預先 處理過的區間拼出真正詢問的區間,而在單點修改時,也要另外修改包含該點的區 間。假設原始陣列長度為 n,且索引值為 1 至 n(1-based),BIT 只需記錄恰好 n 個 區間。BIT 的這 n 個區間的右界即是 1 到 n,若一個區間的右界為 x,其左界就是 x− lowbit(x) + 1。意即,BIT 的區間集合為

{ [x − lowbit(x) + 1, x] | 1 ≤ x ≤ n }

畫成圖之後可以發現,BIT 其實是棵刪除掉一些區間的遞迴樹 (各位在做 Merge sort 時應該會寫出類似的樹狀結構),如下圖所示:

(2)

因為每個範圍的右界都不同,我們可以只使用右界來表示這些區間,並以 range(x) 表示,也就是

range(x) = [x− lowbit(x) + 1, x]

以下我們用前綴和當例子:已知一個長度為 n 的陣列 arr,我們希望建立一個 BIT 的陣列 bit,並滿足 bit[x] 為 arr 陣列中相對應範圍的元素和,即:

bit[x] =

i∈range(x)

arr[i] =

x i=x−lowbit(x)+1

arr[i]

定義完 range(x) 和 bit[x] 之後,我們討論三種操作:詢問,單點修改,以及初始 化時,如何快速的得到答案或完成操作。

1. 詢問前綴和

詢問 [1, x] 的區間和很容易,因為該區間可以拆成 [1, x−lowbit(x)] 和 [x−lowbit(x)+

1, x] 兩個區間和的加總。區間 [x− lowbit(x) + 1, x] 的和就是 bit[x],另外一個區 間則遞迴處理。由於 x− lowbit(x) 在二進位表示法中會比 x 少一個 1,因此最多遞⌈log2x⌉ 次之後就會終止,詢問的複雜度也就是 O(log n)。(雖然概念上另外一個 區間是遞迴處理,實作時只要使用迴圈就可以了,這也是 BIT 常數比較小的原因 之一。)

1 int query(int x) { 2 int sum = 0;

3 for(int i = x; i > 0; i -= lowbit (i))

4 sum += bit[i];

(3)

3. 初始化

和詢問的操作類似,我們可以使用已經計算好的 bit[x] 值來計算未知的 bit[x] 值。

令 x 由小到大計算 bit[x],由定義,

bit[x] =

x i=x−lowbit(x)+1

arr[i] = arr[x] +

x−1

i=x−lowbit(x)+1

arr[i]

除了 arr[x] 這項以外,區間內其餘元素的和可以用前面已經算好的 bit 較快的得 到答案,如 bit[8] = arr[8] + bit[7] + bit[6] + bit[4]。此做法雖然看起來計算單一 個 bit[x] 的時間複雜度不是 O(1),但是總時間複雜度計算之後是 O(n)。演算法程 式碼如下:

1 void init(int n) {

2 for(int x = 1; x <= n; ++x) {

3 bit[x] = arr[x];

4 int y = x - lowbit (x);

5 for(int i = x -1; i > y; i -= lowbit (i))

6 bit[x] += bit[i];

7 }

8 }

另一個做法則是計算完 bit[x] 之後,「主動」往上更新上一層的值。該做法和上一 個做法原理完全一樣,由程式碼就可以輕易看出這是個 O(n) 的演算法:

1 void init2(int n) {

2 for(int x=1; x<=n; ++x)

3 bit[x] = 0;

4 for(int x=1; x<=n; ++x) { 5 bit[x] += arr[x];

6 int y = x + lowbit (x);

7 if(y <= n) arr[y] += arr[x];

8 }

9 }

(4)

習題

1. (10 pts) 請證明在二補數系統下 (即 −x ≡ ~x + 1),lowbit(x) = x&(−x)。(假設 x > 0)。

2. 有一個初始化 BIT 的方法是一開始先把 bit 陣列歸零,並對於 arr 陣列中的每個 元素都呼叫一次 update(x, arr[x]),如以下程式碼所示:

1 void init3(int n) {

2 for(int x=1; x<=n; ++x)

3 bit[x] = 0;

4 for(int x=1; x<=n; ++x) 5 update (x, arr[x]);

6 }

這個演算法的時間複雜度顯然是 O(n log n),然而這只是一個上界,實際上也許並 不會那麼差,因為更新時不一定會改到滿滿的 log n 個區間,也許均攤後也是 O(n) 呢!對此我們需要更細的計算一下,以下將證明該做法的複雜度是 Ω(n log n),也 就是均攤之後還是比較差。

為了方便起見,先假設 n = 2m,其中 m 為非負整數。令 f (m) 表示當 arr 長度為 2m 時,用這個初始化做法需要更新幾次區間,也就是操作次數。由 BIT 的結構可 以發現,大小為 2m 的 BIT 最上層一定是區間 [1, 2m],且不論更改哪個位置的值,

這個區間一定會被更改到。將最上層的區間拿走之後,下面剩下的剛好是大小為 2m−1, 2m−2,· · · , 21, 20 的 BIT,於是就得到

f (m) = 2m+

m−1 k=0

f (k), f (0) = 1

(a) (10 pts) 請證明:∑n

i=0

i· 2i−1 = (n− 1) · 2n+ 1 , for m ≥ 0。

(b) (20 pts) 請證明:f (m) ≥ m · 2m−1 , for m ≥ 0。(即:f(m) ≥ n2 · log2n , for n = 2m, m≥ 0)

(5)

(a) (10 pts) 請給出一組會讓時間複雜度超過 O(log n) 的詢問。

(b) (15 pts) 請問該做法中,詢問的時間複雜度為何?

4. 給定原始陣列 arr,我們定義一個差分陣列 dif,滿足

dif[x] =

{arr[1] , if x = 1

arr[x]− arr[x − 1] , if x ̸= 1 接著再定義另外一個陣列 dif2,滿足

dif2[x] = dif[x]× x

並且定義以下函式,時間複雜度皆為 O(log n),可以想像內部就是用 BIT 實作的:

• query(dif, x):回傳x

i=1

dif[i] 的值

• query(dif2, x):回傳x

i=1

dif2[i] 的值

• update(dif, x, val):將 dif[x] 的值加上 val

• update(dif2, x, val):將 dif2[x] 的值加上 val 請回答以下問題:

(a) (10 pts) 請問如何使用給定的函式,在 O(log n) 時間內得到 arr[x] 的值?

(b) (15 pts) 現在將 arr 陣列中的一段區間 [a, b] 內的數都加上 val,請問如何在 O(log n) 時間內,使用給定的函式改變 dif 和 dif2 陣列以滿足定義?

(c) (15 pts) 請以 dif 陣列表示 arr 陣列的前綴和,也就是 ∑x

i=1

arr[i]。(答案中可 以有很多項 dif[i],甚至包含

,只要能表示就可以了。) (d) (15 pts) 請使用給定的函式,在 O(log n) 時間內得到x

i=1

arr[i]。

參考文獻

相關文件

為使讀 者清楚問題的背景, 先給出定角對定邊的基本問題。 如圖 1, 線段 AB 的長是定長, 在平 面內一點 C, ∠ACB 度數為定值, 則點 C 的運動軌跡是三角形 ABC 外接圓上的圓弧,

有高中老師問起, 怎樣告訴學生這個題目真正的難度在那裡, 所以引發我去想這個問題,

這個定理既然掛名給畢達哥拉斯,正史就從他開始。於是,第二個單元介紹他的一 些成就,讀者在閱讀時請注意分別 Pythagoras 和 Pythagoreans

不過 vector space 不 只是一個 group, 它的元素還可以和一個 field 的元素 “作用” (action), 這是所謂的 scaler multiplication... 注意這 8

接下來要說明要探討的集合如何用 index set 裡的元素編碼, 這樣才能形成一個 indexed family.. 注意, 即使只有有 限個集合, 我們仍可將其視為

Binary tree based data structure Data structure based on binary

[r]

[r]