學習部落格:
樹狀陣列(binary indexed tree(b.i.t), fenwick tree)是乙個查詢和修改複雜度都為
log(n)
的資料結構。主要用於查詢任意兩位之間的所有元素之和,但是每次只能修改乙個元素的值;經過簡單修改可以在
log(n)
的複雜度下進行範圍修改,但是這時只能查詢其中乙個元素的值
(如果加入多個輔助陣列則可以實現區間修改與區間查詢)。
樹狀陣列和線段樹很像,但能用樹狀陣列解決的問題,基本上都能用線段樹解決,而線段樹能解決的樹狀陣列不一定能解決。相比較而言,樹狀陣列效率要高很多。
通過二進位制,將陣列的節點序號都轉化成二進位制。
就是這樣的:
1--->001 c1 = a1
2--->010 c2 = a1 + a2
3--->011 c3 = a3
4--->100 c4 = a1 + a2 + a3 + a4
5--->101 c5 = a5
6--->110 c6 = a5 + a6
7--->111 c7 = a7
8--->1000 c8 = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
但是這又有什麼關係呢?
這裡有乙個有趣的性質:
設節點編號為x,那麼這個節點管轄的區間為
2^k(其中k為
x二進位制末尾
0的個數)個元素。因為這個區間最後乙個元素必然為ax,
所以很明顯:cn = a(n – 2^k + 1) + a(n-2^k+2)+... + an
什麼意思呢?舉個栗子,1對應的二進位制是
001,二進位制末尾
0的個數為
0,所以
k=0,所以
c1=a(1-2^k+1)+...an
就是c1=a(1-2^0+1)=a(1);
再來個栗子,4對應的二進位制是
100,二進位制末尾
0的個數為
2,所以
k=2,所以
c4=a(4-2^2+1)+a(4-2^2+2)+...+a(4)=a(1)+a(2)+a(3)+a(4);
再搞不懂就自己再手推幾個數試試就可以了。
至於怎麼得出來的這個神奇的東西,就是靠
二進位制。
接下來就是怎麼**實現這個呢?
就是要靠神奇的lowbit了。
int lowbit(int x)
{return x&(-x);
x&(-x)就是整數
x與其相反數
(負號取反
)的按位與
:相同位的兩個數字都為
1,則為
1;若有乙個不為
1,則為0,即
:1&1=1,0&1=0,0&0=0;
計算機中負數使用對應正數的補碼來表示。
-x就是
x對應的二進位制數先各位取反,0變成
1,1變成
0。然後最低位加1。
舉個栗子,4對應二進位制為
100;
-4對應的為
011+1=100
,所以為
(100)&(100)
所以為100
。知道這個就可以進行區間查詢啦。
區間查詢
利用c[i]求
a陣列前
i個的和;
//**1:
int sum(int n)
{int s=0;
while(n>0){
s+=c[n];
n-=lowbit(n);
return s;
//**2:
int sum(int n)
{int s=0;
for(int i=n;i>0;i-=lowbit(i))
s+=c[i];
return s;
兩個**哪個好理解就理解哪個。
接著剛剛舉的栗子4,求
a陣列前
4個數的和;
lowbit(4)得出
100;然後
100就是
4,所以為
s+=c[4];
此時i=4,4-lowbit(4)=100-100=0;
結束。再舉個栗子7,求前
7個數的和,就是
s+=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7];
因為1--->001 c1 = a1
2--->010 c2 = a1 + a2
3--->011 c3 = a3
4--->100 c4 = a1 + a2 + a3 + a4
5--->101 c5 = a5
6--->110 c6 = a5 + a6
7--->111 c7 = a7
8--->1000 c8 = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
所以為c[4]+c[6]+c[7];
就是c[100]+c[110]+c[111];
首先s+=c[7];
然後lowbit[7]=(111)&(001)=001;此時
i=7,
所以為7-lowbit(7)=111-001=110,110
就是6;s+=c[6];
lowbit(6)=(110)&(010)=010;此時
i=6,
所以為6-lowbit(6)=110-010=100,100
就是4;s+=c[4];
lowbit(4)=(100)&(100)=100;此時
i=4,
所以為4-lowbit(4)=100-100=0,
結束。所以得到a陣列中前
7個數的和為
c[7]+c[6]+c[4];
不懂的話再自己手推乙個數就差不多懂了。
接下來就是單點更新。
單點更新
要從下往上依次更新。
//**1:
void add(int x)
{while(x<=n){
++a[x];
x+=lowbit(x);
//**2:
void add(int x,int y)
{for(int i=x;i<=n;i+=lowbit(i))
c[i]+=y;
更新過程仔細想一想就是查詢過程的逆過程;
舉個栗子,更新a1,還要繼續更新
c[1],c[2],c[4],c[8];
就是c[001],c[010],c[100],c[1000];
i=1;c[1]+=a[1];
lowbit(1)=(001)&(001)=001;此時
i=1,
所以為1+lowbit(1)=001+001=010,010
就是2;c[2]+=a[1];
lowbit(2)=(010)&(110)=010;此時
i=2,
所以為2+lowbit(2)=010+010=100,100
就是4;c[4]+=a[1];
lowbit(4)=(100)&(100)=100;此時
i=4,
所以為4+lowbit(4)=100+100=1000,1000
就是8;c[8]+=a[1];
結束。
樹狀陣列學習
之前寫的題也遇到過用樹狀陣列,當時都是現查現學,而且總是搞不懂,今天又遇到了一道求區間和的題,不管最後是不是用樹狀陣列可以a,但是既然已經想到了這,就打算好好學習一下。可惜之前查到的資料都沒有儲存記錄,所以又重新查了些資料,彙總學習如下 文末附上樹狀陣列的詳細 樹狀陣列主要用到的操作 int low...
樹狀陣列學習
我覺得,樹狀陣列挺重要的就是那個 lower x x x 我說說我的理解吧。每乙個正整數都可以拆分成 2 的某些冪之和,例如 15 8 4 2 1 6 4 2 7 4 2 1 感覺可以解釋lca的倍增跳 那麼轉換成二進位制是什麼樣的呢?15 十進位制 1111 1000 100 10 1 6 十進位...
樹狀陣列學習
樹狀陣列與並查集類似,是一種資料結構,它可以用來維護字首和問題 不過,利用字首和和查分的思想,我們也可以用樹狀陣列解決區間問題 與線段樹不同,目前我暫且認為線段樹解決最值問題,而樹狀陣列解決求和問題 樹狀陣列原理建立在二叉樹上 利用lowbit運算實現向根節點儲存的原理 介紹lowbit的程式實現只...