給定一個數組a[n],求數組a[n]的和sum。通常的方法是遍歷數組而後求和,這樣的時間複雜度爲O(n)。而當修改了數組中的元素,再次求數組的和時,又要付出O(n)的時間代價。此時,咱們能夠用樹狀數組來求和數組的和。獲得樹狀數組C[n]後,時間複雜度將由O(n)變爲O(lgn)。這是如何實現的呢?下面咱們將按照:數組
Lowbit()函數-->樹狀數組-->樹狀數組索引的意義-->樹狀數組的求和函數-->被操做數組更新數據,順序介紹這一巧妙的神器。函數
(1)lowbit()函數spa
Lowbit()函數是對整數的位操做,返回一個二進制數的最低位「1」。如10(10)=01010(2),返回最低位的「1」,即2。lowbit()函數不是庫函數,須要本身定義。blog
int lowbit(int x)索引
{it
return x&-x;原理
}遍歷
原理(以10爲例):二進制
運用計算機的補碼運算規則:原碼取反+1。方法
原碼取反後,各位與原二進制數相反,lowbit位(含)之後全變爲1:
原碼:01010
反碼:00101
反碼加一後,二進制的屬性致使各位都受影響(從最低位向高位進一,直到遇到一個0位變爲1):
補碼:00110
此時,lowbit位前各位與原碼相反,lowbit位相等,lowbit位後全爲0。
將補碼與原碼進行&(與)運算,即可獲得只有lowbit位爲1的二進制數。
原碼:01100
補碼:00110
結果:00100(與運算後)
int lowbit(int x)
{
return x&(x&(x^(x-1));
}
亦可實現返回最低位「1」,讀者可自行推導。
(2)樹狀數組
如圖所示爲樹狀數組的表示形式,下面給出樹狀數組的定義: Cn=a(n-a^k+1)+......+a(n)(k爲n的二進制表示中從右向左數的0的個數,a^k可用lowbit函數求出)。到此,讀者也許在思考此圖劃分的緣由吧。這正是本人思考了好久的點,下面經過數組索引(下標)的意義來理解這種結構。
(3)樹狀數組索引的意義
在圖中咱們能夠看到樹狀數組c的索引與要求和的數組a的索引是一一對應的。暫且理解數組c的下標意義爲對數組a求和的元素數量。
c[1]=a1(a數組第1個數,下同) 1:0001
c[2]=a1+a2 2:0010
c[3]=a1+a2+a3 3:0011
c[4]=a1+a2+a3+a4 4:0100
c[5]=a1+a2+a3+a4+a5 5:0101
c[6]=a1+a2+a3+a4+a5+a6 6:0110
c[7]=a1+a2+a3+a4+a5+a6+a7 7:0111
c[8]=a1+a2+a3+a4+a5+a6+a7+a8 8:1000
可見每一位索引對應的處理元素個數均可經過索引二進制表示後不斷進行lowbit操做求得:7=0001+0010+0100=1+2+4。而lowbit位之前的非零位則是已經處理過的。爲了實現與lowbit前非零位的銜接((2)中的Cn公式),索引對應的元素在被操做數組a中的操數量即爲索引的lowbit。即當前處理位置減去已經處理過的元素數量。以前的計算結果可經過不斷進行n-=lowbit(n)獲得(n爲當前索引值)。
在此以c[7]和c[8]爲例(此例中索引爲在數組的位序,即下標從1開始):
c[7]=a[7-lowbit[7]+1]+....+a[7].此處兩索引相等,爲a[7].
7-lowbit(7)=6
C[6]=a[6-lowbit(6)+1]+....+a[6]=a[5]+a[6]
6-lowbit(6)=4
C[4]=a[4-lowbit(4)+1]+.....+a[4]=a[1]+a[2]+a[3]+a[4]
4-lowbit(4)=0,結束。
c[8]=a[8-lowbit(8)+1]+...+a[8]=a[1]+a[2]+...a[8]
8-lowbit(8)=0,結束。
由此能夠結合樹狀數組下標的意義理解上圖的結構。
(4)求數組的和:
int Sum(int n)
{
int sum=0;
while(n>0)
{
sum+=c[n];
n-=lowbit(n);/*此處即可看出時間複雜度O(lgn)的由來。由於每次lowbit運算都是對原操做數除以二。*/
}
return sum;
}
(5)被操做數組更新數據(對a中第i個數加x)
此時能夠更好的體現樹狀數組的優點:O(lgn)
void change(int i,int x)
{
while(i<=n)
{
c[i]=c[i]+x;
i+=lowbit(i);
}
}
未獲得博主容許轉載者,不追究任何責任,歡迎你們給出更好的理解方式。