樹狀陣列詳解

2022-05-09 02:39:11 字數 2993 閱讀 6311

平常我們會遇到一些對陣列進行維護查詢的操作,比較常見的,修改某點的值、求某個區間的和。

資料規模不大的時候,對於修改某點的值是非常容易的,複雜度是o(1),但是對於求乙個區間的和就要掃一遍了,複雜度是o(n)。

如果實時的對陣列進行m次修改或求和,最壞的情況下複雜度是o(m*n),當規模增大後這是划不來的。

而樹狀陣列幹同樣的事複雜度卻是o(m*lgn)。

樹狀陣列是乙個查詢和修改複雜度都為log(n)的資料結構。

主要用於查詢任意兩位之間的所有元素之和,但是每次只能修改乙個元素的值;經過簡單修改可以在log(n)的複雜度下進行範圍修改,但是這時只能查詢其中乙個元素的值(如果加入多個輔助陣列則可以實現區間修改與區間查詢)。

看完概念後發現和線段樹的功能類似,實際上樹狀陣列和線段樹確實類似,不過也有不同,具體區別和聯絡如下:

1.兩者在複雜度上同級, 但是樹狀陣列的常數明顯優於線段樹, 其程式設計複雜度也遠小於線段樹.

2.樹狀陣列的作用被線段樹完全涵蓋, 凡是可以使用樹狀陣列解決的問題, 使用線段樹一定可以解決, 但是線段樹能夠解決的問題樹狀陣列未必能夠解決.

3.樹狀陣列的突出特點是其程式設計的極端簡潔性, 使用lowbit技術可以在很短的幾步操作中完成樹狀陣列的核心操作,與之相關的便是其**效率遠高於線段樹。

樹狀陣列,重點是在樹狀的陣列

一顆普通的二叉樹如下

葉子結點代表a陣列a[1]~a[8]

現在變形一下

現在定義每一列的頂端結點c陣列 ,如下圖

c[i]代表 子樹的葉子結點的權值之和//這裡以求和舉例

如圖可以知道

c[1]=a[1];

c[2]=a[1]+a[2];

c[3]=a[3];

c[4]=a[1]+a[2]+a[3]+a[4];

c[5]=a[5];

c[6]=a[5]+a[6];

c[7]=a[7];

c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

其中c陣列的求法如下:

c[i]=a[i-2^k+1]+a[i-2^k+2]+......a[i]; (k為i的二進位制中從最低位到高位連續零的長度)例如i=8時,k=3;

為了更好的理解上面的公式,將1-32的2^k計算出來如下:

有了這個**之後對照上面的式子就可以輕鬆的知道每乙個c代表的哪幾個數的和。比如

c[8],由上圖知2^k為8,那麼

c[24],由上圖知2^k為8,那麼

這是我們通過簡單的計算得出來的值,那麼應該如何轉換成程式語言呢,大神們給出了非常巧妙的方法,利用下面的函式可以求出2^k的值。

int lowbit(int

x)

為什麼這樣可以呢?這裡複製了一篇證明可以看一下。

首先明白乙個概念,計算機中-i=(i的取反+1),也就是i的補碼 

而lowbit,就是求(樹狀陣列中)乙個數二進位制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。 

所以若乙個數(先考慮四位)的二進位制為abcd,那麼其取反為(1-a)(1-b)(1-c)(1-d),那麼其補碼為(1-a)(1-b)(1-c)(2-d)。 

如果d為1,什麼事都沒有-_-|||但我們知道如果d為0,天理不容2σ( ° △ °|||)︴ 

於是就要進製。如果c也為0,那麼1-b又要加1,然後又有可能是1-a……直到碰見乙個為補碼為0的bit,我們假設這個bit的位置為x 

這個時候可以發現:是不是x之前的bit的補碼都與其自身不同?,x之後的補碼與其自身一樣都是0? 

例如01101000,反碼為10010111,補碼為10011000,可以看到在原來數正數第五位前,補碼的進製因第五位使其不會受到影響,於是0&1=0,; 

但在這個原來數「1」後,所有零的補碼都會因加1而進製,導致在這個「1」後所有數都變成0,再加上0&0=0,所以他們運算結果也都是零; 

只有在這個數處,0+1=1,連鎖反應停止,所以這個數就被確定啦o(∩_∩)o 

有了上面的基礎,我們就可以解決很多問題了。重要的操作有兩個,分別是更新和求和。

void update(int k,int

x)

int getsum(int

x)

#include #include 

#include

#include

#include

#include

#include

#define io ios::sync_with_stdio(false);\cin.tie(

0);\

cout.tie(0);

using

namespace

std;

#define n 50100

intn;

intc[n];

int lowbit(int

x)void update(int k,int

x)int getsum(int

x)int

main()

char s[100

]; cout

<

case

"<

while(1

)

if(s[0] == 's'

)

if(s[0] == 'a'

) }}

return0;

}

樹狀陣列 詳解

對於普通陣列,其修改的時間複雜度位o 1 而求陣列中某一段的數值和的時間複雜度為o n 因此對於n的值過大的情況,普通陣列的時間複雜度我們是接受不了的。在此,我們引入了樹狀陣列的資料結構,它能在o logn 內對陣列的值進行修改和查詢某一段數值的和。假設a陣列為儲存原來的值得陣列,c為樹狀陣列。我們...

樹狀陣列詳解

樹狀陣列求區間和的一些常見模型 樹狀陣列在區間求和問題上有大用,其三種複雜度都比線段樹要低很多 有關區間求和的問題主要有以下三個模型 以下設a 1.n 為乙個長為n的序列,初始值為全0 1 改點求段 型,即對於序列a有以下操作 修改操作 將a x 的值加上c 求和操作 求此時a l.r 的和。這是最...

樹狀陣列詳解

比如說,我這裡有一組數1,2,3,2,k。我想知道第i到第j的和 mathop sum limits j v i 是多少?樸素演算法 for int k 0 k n k if k i k j ans v k 類似這種的寫法,雖然在某些點值改變時也依然可以計算 我們稱這種問題為動態問題 但複雜度最高到...