我們知道,對長度為n的陣列,如果我們要改變其中某個值,則時間複雜度為o(1)。如果要求出s[m]=a[1]+a[2]+.....+a[m],則需要o(m)的時間複雜度。若我們一邊修改陣列的值,一邊要求求出其部分和s[m],使用一般的方法,時間複雜度是o(m*n)的,若m和查詢次數n很大,那麼該演算法將不可取。
為了解決這個問題,出現了樹狀陣列這一資料結構。它可以以o(log n)的時間複雜度修改陣列中的值,同時以o(log n)的時間複雜度求部分和。這裡的log是以2為底的。
1、基本結構
樹狀陣列可以簡單地用下面的圖理解:
可以看到,c[1]「管轄」乙個元素,而c[2],c[6]「管轄」2個元素,c[8]「管轄」8個元素。那麼c[n]「管轄」多少個元素呢?在樹狀陣列中,這是個重要的概念。我們規定,把n轉換為二進位制,最右側0的個數為k,則它管轄的元素個數就是2^k個。如2的二進位制位10,2^1=2。4的二進位制為100,2^2=4。定義函式lowbit(n)=2^k。那麼c[n]管轄了lowbit[n]個元素。根據這樣的定義,你就可以自己畫出更大範圍的樹狀陣列了。
2、樹狀陣列的求和過程及其原理
假設c陣列已經初始化好了。如何利用它來求和呢?
舉個例子。
我們要求s[6]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]
先令s=c[6]=a[5]+a[6],注意到lowbit(6)=2,現在的s中只有兩個數的和。所以我們還求6-lowbit(6)=4個數的和。這時找到c[4],s=s+c[4],lowbit(4)=4。這時我們已經求了2+4=6個數的和了,所以s就是答案。
可能上面的過程表達的還是不太清楚。我們用乙個程式段來更好地表達這個過程:
int sum(int k)
在上述程式段中,我們要求s[k],先讓i=k;s+=c[i],之後每次讓i=i-lowbit(i),s+=c[i],直到到最開頭做完為止。
為什麼可以這樣做?且為什麼這樣做的複雜度是log級別的?下面再深入解釋下原理:
注意觀察,其實每次每次讓i=i-lowbit(i),都減去了i二進位制中最右邊的乙個1!而i的二進位制中最多有log i個1,因此時間複雜度是log級的。
這樣做的正確性在於:把求s[k]轉換成求幾段和的累加,而「分段」是以k的二進位制中的1來決定的。
聽起來有點拗口,我們還是用乙個具體的例子解釋。
比如求s[105],105的二進位制是1101001。首先s+=c[105],注意到lowbit(105)=1,這樣就減去了1101001中最右邊的1。1101001-1=1101000。1101000是104的二進位制。下面s+=c[104],lowbit(104)=8,104-8=96。96的二進位制是1100000。這就減去了右邊第二個1。下面s+=c[96],lowbit(96)=32,96-32=64.64的二進位制是1000000,這就減去了右邊第三個1。最後s+=c[32]結束。
整個求和過程i的變化就是1101001->1101000->1100000->1000000->0.
3、樹狀陣列的初始化
初始化c陣列有兩種方法。
一種是利用sum函式,c[n]=a[n]+sum(n-1)-sum(n-lowbit(n)),這很好理解。
還有一種是利用以下的程式段:
void init()
}
這個程式段和上面的思路類似,讀者可自行分析其正確性。
4、樹狀陣列對值的修改。
若修改陣列中的乙個值,則c陣列需要做相應修改。我們每次往上找父節點就行了,時間複雜度也是log級的。
void modify(int x,int v)
5、其他
注意寫樹狀陣列的時候,最好把陣列下標定為1..n,而不是0..n-1,這樣有利於程式設計方便!
6、參考程式
#include#includeint n,a[10001],c[10001],m,t,x,y,v;
int lowbit(int x)
int sum(int k)
void modify(int x,int v)
void init()
}int main()else
} return 0;
}
樹狀陣列理解
無意間看到樹狀陣列,查了很多資料被各種圖和公式繞暈了,下面記錄一點個人理解。假設陣列a 0 a 1 a 2 a n 記0 m元素之和為sum m 0 當我們頻繁的求s m 時,第一種方法不適用,當我們頻繁的修改陣列a時,第二種方法每次都要修改陣列s,修改陣列s的時間複雜度為o n 而樹狀陣列可以很好...
樹狀陣列理解
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 再將其轉化為二進位製看...
深入理解樹狀陣列
先上模板 單點更新const int n 1003 int n int a n void update int i,int d intgetsum int i 區間更新const int n 1003 int n int a n void update int i,int d intgetsum i...