2015 7 14 樹狀陣列及其初步應用

2021-07-03 23:39:09 字數 3174 閱讀 5685

樹狀陣列(binary indexed tree),是一種設計新穎的陣列結構,它能夠高效地獲取陣列中連續n個數的和。概括說,樹狀陣列通常用於解決以下問題:陣列中的元素可能不斷地被修改,怎樣才能快速地獲取連續幾個數的和?

舉個簡單的例子:

*現在有乙個長度為n的陣列,陣列記憶體有資料,對於這些資料一般有兩類操作:

①修改資料第i個元素

②查詢乙個區間[p,q]內元素的和*

①直接修改,時間複雜度為o(1)

②從p迴圈累加至q,時間複雜度為o(n)

①直接修改a[i],但是對於維護s,需要時間複雜度為o(n)。

②a[p]~a[q]=s[q]-s[p]即為所求,時間複雜度為o(1)。

由此可見,不同資料結構之間沒有絕對優劣之分,這取決於演算法的需求。

在方法二中,我們需要維護乙個陣列的字首和s[i]=a[1]+a[2]+…+a[i]。但是不難發現,如果我們修改了任意乙個a[i],s[i]、s[i+1]…s[n]都會發生變化。可以說,每次修改a[i]後,調整字首和s在最壞情況下會需要o(n)的時間。

但方法二的思想已經給我們了啟發。對於有關「區間」的問題,如果我們只在單個元素上做文章,可能不會有太大的收穫。但是如果對於這些資料元素進行合理的劃分(如方法二將其化為n個字首),然後對於整體進行操作,往往會有神奇的功效。

為了讓樹狀陣列更直觀,我們先看下面這張圖:

傳統陣列(共n個元素)的元素修改和連續元素求和的複雜度分別為o(1)和o(n)。樹狀陣列通過將線性結構轉換成偽樹狀結構(線性結構只能逐個掃瞄元素,而樹狀結構可以實現跳躍式掃瞄),使得修改和求和複雜度均為o(lgn),大大提高了整體效率。

給定序列(數列)a,我們設乙個陣列c滿足:

c[i] = a[i–2^k+ 1] + … + a[i]

其中,k指i在二進位制時末尾0的個數,或者說是i用2的冪方和表示時的最小指數。

如果對概念不是太理解,可以用以下例子來理解:

如23的二進位制為10111,即:23 =1 *2^4+0 *2^3+1 *2^2+1 *2^1+1 *2^0

即23用2的冪方和表示為

2^4+2^2+2^1+2^0

從圖示中我們可以看出,修改第i個元素,為了維護陣列c的意義,需要修改c[i]以及c[i]的全部祖先,而非c[i]的祖先的節點則對於第i個元素的修改,不會發生改變。所以時間複雜度為o(logn)。

要求區間[p,q]元素和,可求[1,q]、[1,p]作差。則問題轉化為如何查詢乙個區間[1,p]的元素和,即求s[p]。

對於求數列的前n項和,只需找到n以前的所有最大子樹,把其根節點的c加起來即可。不難發現,這些子樹的數目是n在二進位制時1的個數,或者說是把n展開成2的冪方和時的項數。因此,求和操作的複雜度也是o(logn)。

例如:23的二進位制為10111,即:

1 0 1 1 1 (2) = 23 c[23] = a[23]

1 0 1 1 0(2) = 22 c[22] = a[21] + a[22]

1 0 1 0 0(2) = 20 c[20] = a[17] + … + a[20]

1 0 0 0 0(2) = 16 c[16] = a[1] + … + a[16]

則s[23] = c[16] + c[20] + c[22] + [23]

易見,對於任意p,求s[p]只需將若干樹狀陣列上節點c進行加和即可,因為所加節點個數等於p的二進位制形式中1的個數,因此統計過程的複雜度為o(log n)。

通過上面的介紹,可以發現,實現樹狀陣列的關鍵,在於求乙個數p的二進位制時末尾0的個數k(用2的冪方和表示時的最小指數)。而2^k就是修改(和統計)時指標滑動的距離,我們定義這個值為p的lowbit。

更具體的說,正整數p的lowbit為將p二進位制中最後乙個1按位抽取的結果。比如,23(10111)的lowbit為1(00001),20(10100)的lowbit為4(00100)。

對於乙個二進位制數p,以111010000為例。

將這個數減1,即p-1為111001111。

將這兩個數取異或,即p^(p-1),為000011111。

將原數與這個數取和,即p&(p^(p-1)),為000010000。

至此已經成功將p二進位制的最後乙個1按位抽取出來了。

lowbit(p) = p & ( p ^ ( p - 1 ) )

根據有符號整數的補碼規則,我們可以發現(p^(p-1))恰好等於-p(具體就不證明了)

即lowbit的求取公式可以更為簡練:

lowbit(p) = p & -p

記住這個結論,這是實現樹狀陣列的核心內容。

操作一:修改第i個元素(下)

修改第i個元素:修改第i個元素,需要更新c[i]以及c[i]的全部祖先。根據樹狀陣列的邏輯結構,易見c[i]的祖先為c[i + lowbit(i)]。要對第i個位置進行修改,修改c[i]後再向上對其父親進行遞迴修改,直至樹根。下面給出乙個對第i個元素進行加法操作(x位置加num)的例子。

void plus(int x, int num)

}

查詢區間[p,q]的元素和:要求[p,q]的元素和,可求s[q]、s[p]作差。要求s[p],只需將p反覆減其lowbit並對其c進行加和即可(讀者可參照第18頁的例子幫助理解)。下面給出求s函式的範例:

int

sum(int x)

return s;

}

以上就是樹狀陣列的具體實現,它解決的問題是:

(1)改變某乙個元素的值

(2)查詢某乙個區間內所有元素的和。

在此基礎上,經過簡單的變形可以變成支援另一組操作:

(1)把乙個區間內所有元素都加上乙個值

(2)查詢某乙個元素的值。

如果你真正的理解了樹狀陣列區間劃分的思想,那麼你會發現這種變形很顯然,只需要將上面的2個基本操作反過來用就可以了,即把乙個區間內所有的元素都加上乙個值時,向前遞迴修改;查詢某乙個元素的值時,沿樹向上遞迴累加,因為它的祖先記錄了所有與它有關的區間整體修改操作。

2015 7 14 樹狀陣列及其初步應用

樹狀陣列 binary indexed tree 是一種設計新穎的陣列結構,它能夠高效地獲取陣列中連續n個數的和。概括說,樹狀陣列通常用於解決以下問題 陣列中的元素可能不斷地被修改,怎樣才能快速地獲取連續幾個數的和?舉個簡單的例子 現在有乙個長度為n的陣列,陣列記憶體有資料,對於這些資料一般有兩類操...

樹狀陣列初步

fuction begin text text text end 原理 定義c i 表示以i為結尾的前lowbit i 個數 原數列 的和 字尾和 維護c i 根據上圖,從底至上 二進位制增加拼湊 更新code inline void update int pos,int val 區間查詢 字首和的...

二維樹狀陣列及其 變式 樹狀陣列

二維樹狀陣列涉及到兩種基本操作,修改矩陣中的乙個點,查詢子矩陣的和 首先是修改點的操作 void update int x,int y,int z 然後是查詢子矩陣的和,這裡查詢的是從左上角到目標點所形成的矩陣的元素和.int sum int x,int y 那麼如果我要查具體的乙個子矩陣,就需要給...