1. 程式人生 > >樹狀陣列(binary indexed tree)

樹狀陣列(binary indexed tree)

樹狀陣列是一種用於查詢區間和的資料結構。

比如對於陣列a[1,2,3,4,5,6,7,8,9,10],我們要知道它的前4項和,我們就需要把這前四個一個個加起來才能得到結果,也就是O(K)(前k項和)的複雜度。如果查詢次數少,k也比較小還可以接受,但是在大規模的查詢和資料量時,這個複雜度就太高了。樹狀陣列就是為了解決這個問題而創造出來的,它能在log(k)的複雜度內查詢區間和。

樹狀陣列其實就是把一顆二叉樹壓縮在了一個數組裡,看下圖(一如既往的醜):

 

對於陣列[1,2,3,4],我們把它轉化為一個求和樹,每個節點的值就是它的兩個子節點的值,此時我們要求前k項就比較好求了,比如求前4項,我們可以直接給出節點10。但是我們如何用一個樹組來表示它呢?如果要每個節點都有一個位置對應,起碼得有2n個節點,但是我們只用n個。考慮b,d兩個節點,其實b裡面已經包含了d,所以我們可以把他們合併。同樣,a裡面其實包含了e,g他們也可以合併到位置4。

  我們來看看一般的規律(下面的位置索引都是二進位制值):

索引 包含值
001(1) 001
010(2) 001 010
011(3) 011
100(4) 001 010 011 100
 不難看出一個位置包含的區間,其實就是它的二進位制表示去掉從右到左第一個1得到的值 ——>該位置。

要得到從左到右第一個1也很簡單 x&(-x)即可。

下面是具體程式碼:

#include<iostream>
using namespace std;
//Binary Indexed Tree(BIT)
//得到x只剩下從右到左第一個一時的值。
 int getk(int x){
  return x&(-x);
}
//由正常的陣列建立樹狀陣列
 void build(int s[],int size){
  for(int i=1;i<=size;i++){
    int next = getk(i)+i;
    if(next<=size)s[next-1] += s[i-1];
  }
}
//更新一個位置的值
void update(int s[],int size,int k,int dt){
  k++;
  while(k<=size){s[k-1]+=dt;k+=getk(k);}
}
//求前k項和
int ksum(int s[],int size,int k){
  int result = 0;
  while(k>1){cout << k << endl;result += s[k-1];k -= getk(k);}
  return result;
}
int main(){
  int a[10] = {1,2,3,4,5,6,7,8,9,10};
  build(a,10);
  cout << ksum(a,10,4) << " " << ksum(a,10,7) << endl;
  update(a,10,4,-1);
  for(int i=0;i<10;i++){
    cout << a[i] << " ";
  }
  cout << endl;
}