數據結構——樹狀數組詳解

一.概念程序員

   樹狀數組(Binary Indexed Tree(B.I.T), Fenwick Tree)是一個查詢修改複雜度都爲log(n)的數據結構。主要用於查詢任意兩位之間的全部元素之和,可是每次只能修改一個元素的值;通過簡單修改能夠在log(n)的複雜度下進行範圍修改,可是這時只能查詢其中一個元素的值(若是加入多個輔助數組則能夠實現區間修改與區間查詢)。算法

  這種數據結構(算法)並無C++和Java的庫支持,須要本身手動實現。在Competitive Programming的競賽中被普遍的使用。樹狀數組和線段樹很像,但能用樹狀數組解決的問題,基本上都能用線段樹解決,而線段樹能解決的樹狀數組不必定能解決。相比較而言,樹狀數組效率要高不少。——百度百科 
  首先明確一點,樹狀數組的本質仍是數組。
二.問題的引入
   先來看這樣一道題目:
   

    要想查看本題,請點這裏(有一些區別,本質相同)數組

       若是直接用數組進行模擬,修改的時間複雜度是O(1),查詢是O(n)m次查詢操做的時間複雜度就是O(mn),時間複雜度太高。數據結構

   樹狀數組就能夠輕鬆處理這類問題,它是一個查詢和修改的複雜度都爲log(n)的數據結構。函數

    來看一下樹狀數組的樣子:
圖片出自水印。

 

    A表明原來的數組,C表明樹狀數組。爲何樹狀數組要長成這樣?優化

   明確一點:樹狀數組是對二進制的應用。spa

   咱們不妨把全部的數字都轉換爲二進制。來觀察一下數字特徵:code

    

    舉幾個例子:blog

   用C來表明樹狀數組,用A來表明原數組:圖片

   C(2) = A(1)+ A(2) 對應二進制 C(0010) = A(0010) + A(0001)

   C(4) = A(4)+A(2)+A(3) 對應二進制C(0100) = A(0100)+A(0010)+  A(0011)

   C(8) = A(8)+A(4)+A(6)+A(7)對應二進制C(1000) = A(1000)+A(0100)+A(0110)+A(0111)

   ......

   不難發現,對於任意的C[i]寫成二進制的形式,都等於原來的數組A[i]的值自己,加上把i轉換成二進制後,把首位變成0,而且開始逐位向後把0變成1,相加。

   例如: 1000(二進制)向後逐位變化,即1000--> 0100-->0110>0111

   ......

   構建出這樣的樹狀數組後:

   查詢A1到A8的和,只須要返回C8

   查詢A1到A7,只需返回C7+C6+C4

   查詢A1到A6,只須要返回C6+C4

   以此類推。

   這樣便優化了詢問的時間複雜度。

   那麼說的這麼好聽,如何作到修改A的值時,順便更新C的全部關於A的值呢?(例如,修改A[1]的時候更新C1,C2,C4,C8)

三.補碼、lowbit函數

  1.補碼

   首先說一說補碼。

    

                                                       ——百度百科。

   補碼是計算機表示符號數的一種方式,概念內容太雜亂,對於咱們樹狀數組沒有太大的用處,只須要了解兩個事情:

   ·正整數的補碼和原碼(原來的二進制的代碼)相同。

   ·負整數的補碼將其正整數的原碼全部爲取反(0變1,1變0),以後加一。

   2.lowbit函數

   lowbit函數即是幫助咱們找到二進制表達式中最低位1所對應的值。

   好比,6的二進制是110,因此lowbit(6)=2。 

   lowbit的實現方式一共兩種:我的推薦下面這種寫法:  

  int lowbit(int x)   {    return x&(-x);   }

     &符號意思爲按位與,把兩個數的二進制一位一位比較,都爲1,結果則爲1,不然就是0。

    這個函數什麼意思?將一個數x的原碼和補碼按位與?返回的就是最後一位1?

    的確是這樣。

    舉個例子(example):

      6 = 0110(二進制)

      它的補碼爲0010。按位與以後獲得的確實就是最後一位1。

    正是由於補碼在取反後+1,纔有了lowbit函數的產生。

    您不妨打開計算器,切換到程序員模式,試上幾組,或許會有新的領悟。

    畢竟「紙上得來終覺淺,絕知此事要躬行」。

    ......

  3.lowbit查詢與更新的應用:

    首先先來運行一段代碼:

  #include<stdio.h>   int main()   {    int i,j;   for(i=1;i<=8;i++)   {    printf("%d:",i);   for(j=i;j<=8;j+=j&-j)    printf("%d ",j);   printf("\n");   }   return 0;   }    

    Output:
   1: 1 2 4 8
   2: 2 4 8
   3: 3 4 8
   4: 4 8
   5: 5 6 8
   6: 6 8
   7: 7 8
   8: 8

     

 

    圖片數字對比來看更新A1,須要更新C1,C2,C4,C8。

   更新A2,須要更新C2,C4,C8。更新A3,須要更新C3,C4,C8.....

   正好與咱們代碼運行出來的結果一致。這就爲咱們向上更新提供了條件。

      再來看一下查詢。若是查詢A1到A8和,只須要返回C8,查詢A1到A7,只須要返回C7+C6+C4

      再來看下面這組代碼:

  #include<stdio.h>   int main()   {    int i,j;    for(i=1;i<=8;i++)    {    printf("%d:",i);    for(j=i;j;j-=j&-j)    printf("%d ",j);    printf("\n");    }    return 0;   }

    

  Output:
  1: 1
  2: 2
  3: 3 2
  4: 4
  5: 5 4
  6: 6 4
  7: 7 6 4
  8: 8

   這段代碼只是將以前的i+=lowbit(i)修改成了i-=lowbit(i)

  再對比以前原圖,查詢A1到A8,只須要返回C8,查詢A1到A7,只須要返回C7,C6,C4與咱們代碼運行的效果一致,這就爲咱們向下查詢提供了條件。

 四.樹狀數組應用

  再回頭看以前引出樹狀數組的題目,這時候就能夠有必定的思路了。

    樹狀數組主要的函數分爲兩個,即更新函數和查詢函數。

    

    void fix(int x) { int i; for(i=x;i<=n;i+=i&-i)    //向上更新
            e[i]++;        //一維樹狀數組e
 } //注意fix()的形參值x<=0時死循環。
    int getsum(int x) { int ret=0,i;       //返回值爲ret,初值爲0
        for(i=x;i;i-=i&-i)//向下查詢
            ret+=e[i]; return ret; } //注意getsum()的形參值x<0時e[ ]數組越界。
  

 

    void fix(int x) { int i; for(i=x;i;i-=i&-i)    //向下更新
            e[i]++; } int getsum(int x) { int ret=0,i; for(i=x;i<=n;i+=i&-i)//向上查詢
            ret+=e[i]; return ret; }

   樹狀數組能夠有兩個方向,1.向下更新,向上查詢 2.向上更新,向下查詢。本題用的是向上更新,向下查詢。

           注意:樹狀數組更新時是增長量,初始時候更新量就是它自己。

   因此開始時利用更新函數,將每個點更新,以後只要輸出就好了。

  代碼:

  

#include<stdio.h>
int a[100005]; int c[100005]; int n; int lowbit(int x) { return x&(-x); } void add(int x,int ad) { for(int i = x;i<=n;i+=lowbit(i)) { c[i]+=ad; } } int getsum(int x) { int ans = 0; for(int i = x;i>0;i-=lowbit(i)) { ans+=c[i]; } return ans; } int main() { scanf("%d",&n); for(int i = 1;i<=n;i++) { scanf("%d",&a[i]); add(i,a[i]); } int m; scanf("%d",&m); for(int i = 1;i<=m;i++) { char s[2]; int x,y; scanf("%s%d%d",s,&x,&y); if(s[0]=='C') { add(x,y-a[x]); a[x] = y; }else { printf("%d\n",getsum(y) - getsum(x-1)); } } return 0; }

    樹狀數組其餘應用,以後會陸續補充。若對於其有新的理解,也會加入到其中。

   更新時間(2018.12.6)

   


去超越本身不認同的人,去追趕本身理想的人。我想所謂的成長,就是不斷的重複這些吧。
相關文章
相關標籤/搜索