資料結構 樹狀陣列

2021-08-18 16:14:25 字數 3966 閱讀 6455

區間資訊的維護與查詢專題———

樹狀陣列

1.

問題**

動態連續和查詢問題。給定乙個n個元素的陣列a1,a2,...,an,你的任務是設計乙個資料結構,支援以下兩種操作。

★ add(x,d)操作:讓ax增加d.

★ query(l,r):計算al

+al+1+...+ar.

對普通陣列進行

一次修改或

特定區間

求和,時間複雜度為o(n),n為修改或求和需要掃瞄的

陣列區間大小。

但有一種稱為

樹狀陣列

(又稱二叉索引樹)的資料結構可以很好的解決這個問題

,時間複雜度則為o(l

ogn)。加了乙個l

og,學過數學的我們應該都知道

效率優化

有多大。

2.

相關的定義與概念

在講實現之前,我們需要先理解乙個函式lowbit(x),這是乙個自定義的函式,函式名是約定成俗的,作用就是

x的二進位制表示式中最右邊的1所對應的值(而不是這個位元的序號)。比如,38288的二進位制是10010101100

10000

,所以lowbit(38288)=16(二進位制是10000)。

**實現

:int lowbit(int x)//位運算,利用計算機補碼特性

寫個長注釋,假設x=10

10的二進位制:1010,我們知道

-x就是

x的二進位制

按位取反

,末尾加一

以後的結果

。-10的二進位制:0101+1=0110 

然後位運算子&(按位與),1010&0110=10,十進位制表示就是2 

所以lowbit(10)=2 

這個函式很重要,所以先在這裡交代清楚原理。

下面上圖,

就正式開講

樹狀陣列

了。下圖是一棵典型的

bit,由15個結點組成,編號為1~15.

灰色結點是樹狀陣列中的結點,每一層結點的lowbit相同,而且lowbit越大,越靠近根。對於結點i,如果它是左子結點,那麼父結點的編號就是i+lowbit(i);如果它是右子結點,那麼父結點的編號是i-lowbit(i).接著構造乙個輔助陣列c,其中

ci=a

i-lowbit(i)+1

+ai-lowbit(i)+2

+...+a

i換句話說,

c的每乙個元素都是a陣列中的一段連續和。在bit中,每個灰色結點i都屬於乙個以它自身結尾的水平長條(對於lowbit=1的那些點,「長條」就是那個結點自己),這個長條中的數之和就是ci。

比如結點12的長條就是從9~12,即c12

=a9+a10

+a11

+a12

。同理,c6

=a5+a6

。這個等式極為重要,請大家花一些時間驗證一下「ci就是以i結尾的水平長條內的元素之和」這一事實。

有了c陣列之和,計算字首和si就變得簡單了。順著結點i往左走,邊走邊「往上爬」(注意並不一定沿著樹中的邊爬),把沿途經過的ci累加起來。就可以了。(請大家驗證,沿途經過的ci所對應的長條不重複不遺漏地包含了所有需要累加的元素),如下圖所示。

而如果修改了乙個ai,需要更新c陣列中的哪些元素呢?從ci開始往右走,邊走邊「往上爬」(同樣不一定沿著樹中的邊爬),沿途修改所有結點對應的ci即可(請大家驗證,有且僅有這些結點對應的長條包含被修改的元素),如下圖所示。

說了那麼多,

大家發現

了嗎?樹狀陣列其實就是

利用二進位制

3.偽**實現

樹狀陣列的第i個元素tree[i]表示a[lowbit(i)+1..i]的和,其中lowbit(i)表示i的最低二進位制位。

3.1當想查詢乙個a[1]+...+a[i]的和,可以依據如下演算法:

(1)令sum=0,轉第(2)步。

(2)假如i≤0,演算法結束,返回sum值,否則sum+=tree[i],轉第(3)步。

(3)i-=lowbit(i),轉第(2)步。

可以看出,這個演算法就是將乙個個區間的和全部加起來,並且i-=lowbit(i)這一步實際上等價於將i的二進位制的最後乙個1減去,而i的二進位制裡最多有logn個1,所以查詢效率是o(logn).

3.2而給a[i]加上x的演算法如下:

(1)當i>n時,演算法結束,否則轉第(2)步。

(2)tree[i]+=x,i+=lowbit(i),轉第(1)步。

i+=lowbit(i)這個過程實際上是乙個把末尾1補為0的過程。容易看出複雜度也是o(logn)

4.模板題講解

nyoj108士兵殺敵(一)

時間限制:1000 ms  |  記憶體限制:65535 kb

難度:3

描述南將軍手下有n個士兵,分別編號1到n,這些士兵的殺敵數都是已知的。

小工是南將軍手下的軍師,南將軍現在想知道第m號到第n號士兵的總殺敵數,請你幫助小工來回答南將軍吧。

注意,南將軍可能會問很多次問題。

輸入只有一組測試資料

第一行是兩個整數n,m,其中n表示士兵的個數(1隨後的一行是n個整數,ai表示第i號士兵殺敵數目。(0<=ai<=100)

隨後的m行每行有兩個整數m,n,表示南將軍想知道第m號到第n號士兵的總殺敵數(1<=m,n<=n)。

輸出對於每乙個詢問,輸出總殺敵數

每個輸出佔一行

**如下:

#include

int c[1000005];

int n,m;

int lowbit(int x)

return x&(-x);

void add(int i,int x)

while(i<=n)

c[i]=c[i]+x;

i=i+lowbit(i);

int sum(int i)

int sum=0;

while(i>0)

sum=sum+c[i];

i=i-lowbit(i);

return sum;

int main()

int i,j,temp,x,y;

scanf("%d%d",&n,&m);

for(i=1;i<=n;i++)

scanf("%d",&temp);

add(i,temp);

for(i=1;i<=m;i++)

scanf("%d%d",&x,&y);

printf("%d\n",sum(y)-sum(x-1));//為什麼這裡是x-1

return 0;

5.其他可供練習本演算法的題目

nyoj 116 士兵殺敵(二)

nyoj 117 求逆序數

hdu 1166 敵兵布陣

hdu 1556 color the ball

hdu 1394 minimum inversion number

6.演算法延伸

1.二維的樹狀陣列

2.rmq(範圍最值問題)

3.線段樹 點修改,區間修改

資料結構 樹狀陣列

原陣列 字首和 範圍和 原陣列更改陣列元素在求和效率較低,引入樹狀陣列 假設原陣列a 樹狀陣列c 樹狀陣列 的三種操作 1.lowbit 子葉數 二進位制最低位的1代表多少 實現 int lowbit int n 求 lowbit x returnx x 2.update a i k 假設a i 是...

資料結構 樹狀陣列

講到了線段樹,那就順便講講樹狀陣列吧。假設乙個長度為 12 的線段樹,構建結果如下 在區間求和問題上,在葉子節點,顯然劃線部分的值可以由父親節點 左端葉子節點得到。那麼,這部分資訊就是冗餘的,沒有儲存的必要。同理,可以推導出所有冗餘的部分如下 那麼,去除冗餘部分後的結果如下 給每乙個節點乙個編號。我...

資料結構 樹狀陣列

include using namespace std const int maxn 1e2 4 int c maxn 編號從1開始 intlowbit int n intupdate int x,int value intsum int x intmain include using namesp...