二叉索引樹(樹狀數組)入門(一)
二叉索引樹,即樹狀數組,被某神犇稱之為是最漂亮的數據結構,所以蒟蒻北籬也去學習了一下傳說中的樹狀數組。
限於蒟蒻北籬的語言表達能力太差(其實是懶),於是引用了度娘的一段對樹狀數組的解釋
樹狀數組(Binary Indexed Tree(B.I.T), Fenwick Tree)是一個查詢和修改復雜度都為log(n)的數據結構。主要用於查詢任意兩位之間的所有元素之和,但是每次只能修改一個元素的值;經過簡單修改可以在log(n)的復雜度下進行範圍修改,但是這時只能查詢其中一個元素的值(如果加入多個輔助數組則可以實現區間修改與區間查詢)。
樹狀數組可以O(logn)的完成單點修改,單點查詢和區間查詢等操作,其實就是動態維護前綴和的過程,其中的所有操作都是以lowbit(x)
lowbit
先來說一下lowbit(x)操作,lowbit(x)操作其實就是求x的二進制最低位(最低位代表的數字而不是位數),求x的lowbit也很簡單,代碼如下:
1 inline int lowbit(int x) { 2 return x&(-x); 3 }
其實就是利用了補碼,證明很簡單,這裏略過。如果你不想寫函數也可以宏定義:
1 #define lowbit(x) ((x)&(-(x)))
加多層括號是為了防止優先級錯誤,不必要時完全可以不加。
存儲結構
樹狀數組看名字就知道只是一個數組,沒什麽好說的,但是,每個數組元素存儲的不是原數組中的內容(這不是廢話嗎),而是原數組中(x-lowbit(x),x]內所有元素的和,而這就是我們查詢時間為O(logn)的原因。
單點修改
如果要把數組中下標為x的元素加上t,那麽學過數組的人肯定都會寫:
1 inline void change(int x,int t) { 2 c[x]+=t; 3 }
這樣就行了嗎?不行。
我們在說存儲結構的時候就說過了,樹狀數組中下標為元素的值等於原數組中(x-lowbit(x),x]內所有元素的和,所以我們修改樹狀數組的其他元素,我們假設樹狀數組有n個元素,於是代碼如下:
1 inline void change(int x,int t) { 2 for(;x<=n;x+=lowbit(x)) { 3 c[x]+=t; 4 }5 }
這樣才是沒有問題的修改操作。如果看懂了上面的存儲原理想要理解的話也不難,理解不了就多看幾遍圖就懂了。
有沒有發現我們並沒有說建樹的代碼?那是因為直接把每一個點調用一次change操作就好了。
前綴和查詢
我們利用樹狀數組可以做到O(logn)的前綴和查詢,和單點修改類似的原理,這裏不再多說,看代碼就能懂的(笑
1 inline int sum(int x) { 2 int s=0; 3 for(;x;x-=lowbit(x)) { 4 s+=c[x]; 5 } 6 return s; 7 }
區間查詢
我們可以利用樹狀數組求[s,t]內所有元素的和,做法很簡單,既然我們已經寫出前綴和查詢的代碼,我們現在就要好好利用它,求出sum(t)和sum(s),然後兩者相減就能得出答案,所以區間求和的時間也是O(logn)。
1 inline int query(int s,int t) { 2 return sum(t)-sum(s-1); 3 }
樹狀數組模板
最後丟一個模板好了(溜
1 struct BIT{ 2 private: 3 static const int maxn=5e5+10; 4 int c[maxn],n; 5 public: 6 void init(int n) { 7 memset(c,0,sizeof(c)); 8 this->n=n; 9 } 10 inline void change(int x,int t) { 11 for(;x<=n;x+=lowbit(x)) { 12 c[x]+=t; 13 } 14 } 15 inline int sum(int x) { 16 int s=0; 17 for(;x;x-=lowbit(x)) { 18 s+=c[x]; 19 } 20 return s; 21 } 22 inline int query(int s,int t) { 23 return sum(t)-sum(s-1); 24 } 25 };
二叉索引樹(樹狀數組)入門(一)