深刻理解樹狀數組

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

百度上給出了使人難以理解的概念,其實這個東西我也是琢磨了一天,參考了大量博客的筆記才搞清楚了大體思路和原理,說說心得吧!數組

假設數組a[1..n],那麼查詢a[1]+...+a[n]的時間是log級別的,並且是一個在線的數據結構,支持隨時修改某個元素的值,複雜度也爲log級別。
來觀察這個圖:
令這棵樹的結點編號爲C1,C2...Cn。令每一個結點的值爲這棵樹的值的總和,那麼容易發現:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
這裏有一個有趣的性質:
設節點編號爲x,那麼這個節點管轄的區間爲2^k(其中k爲x二進制末尾0的個數)個元素。由於這個區間最後一個元素必然爲Ax,
因此很明顯:Cn = A(n – 2^k + 1) + ... + An
算這個2^k有一個快捷的辦法,定義一個函數以下便可:
int lowerbit(int x)
{
return x&(x^(x–1));
}
 
利用機器補碼特性,也能夠寫成:
int lowerbit(int x)
{
    return x&-x;
}
 
當想要查詢一個SUM(n )(求a[n]的和),能夠依據以下算法便可:
step1: 令sum = 0,轉第二步;
step2: 假如n <= 0,算法結束,返回sum值,不然sum = sum + Cn,轉第三步;
step3: 令n = n – lowbit(n),轉第二步。
能夠看出,這個算法就是將這一個個區間的和所有加起來,爲何是效率是log(n)的呢?如下給出證實:
n = n – lowbit(n)這一步實際上等價於將n的二進制的最後一個1減去。而n的二進制裏最多有log(n)個1,因此查詢效率是log(n)的。
那麼修改呢,修改一個節點,必須修改其全部祖先,最壞狀況下爲修改第一個元素,最多有log(n)的祖先。
因此修改算法以下(給某個結點i加上x):
step1: 當i > n時,算法結束,不然轉第二步;
step2: Ci = Ci + x, i = i + lowbit(i)轉第一步。
i = i +lowbit(i)這個過程實際上也只是一個把末尾1補爲0的過程。
對於 數組求和來講樹狀數組簡直太快了!
注:
求lowbit(x)的建議公式:
lowbit(x):=x and -x;
或lowbit(x):=x and (x xor (x - 1));
lowbit(x)即爲2^k的值。
上面的解釋可能會讓人產生疑惑,下面給出稍微容易理解的解釋吧!

先看兩幅圖(網上找的,若是雷同,不要大驚小怪~),下面的說明都是基於這兩幅圖的,左邊的叫A圖吧,右邊的叫B圖:數據結構

      是否是很像一顆樹?對,這就是爲何叫樹狀數組了~先看A圖,a數組就是咱們要維護和查詢的數組,可是其實咱們整個過程當中根本用不到a數組,你能夠把它看成一個擺設!c數組纔是咱們全程關心和操縱的重心。先由圖來看看c數組的規則,其中c8 = c4+c6+c7+a8,c6 = c5+a6……先沒必要糾結怎麼作到的,咱們只要知道c數組的大體規則便可,很容易知道c8表示a1~a8的和,可是c6倒是表示a5~a6的和,爲何會產生這樣的區別的呢?或者說發明她的人爲何這樣區別對待呢?答案是,這樣會使操做更簡單!看到這相信有些人就有些感受了,爲何複雜度被lg了呢?能夠看到,c8能夠看做a1~a8的左半邊和+右半邊和,而其中左半邊和是肯定的c4,右半邊其實也是一樣的規則把a5~a8一分爲二……繼續下去都是一分爲二直到不能分,能夠看看B圖。怎麼樣?是否是有點二分的味道了?對,說白了樹狀數組就是巧妙的利用了二分,她並不神祕,關鍵是她的巧妙!函數

       她又是怎樣作到不斷的一分爲二呢?說這個以前我先說個叫lowbit的東西,lowbit(k)就是把k的二進制的高位1所有清空,只留下最低位的1,好比10的二進制是1010,則lowbit(k)=lowbit(1010)=0010(2進制),介於這個lowbit在下面會常常用到,這裏給一個很是方便的實現方式,比較廣泛的方法lowbit(k)=k&-k,這是位運算,咱們知道一個數加一個負號是把這個數的二進制取反+1,如-10的二進制就是-1010=0101+1=0110,而後用1010&0110,答案就是0010了!明白了求解lowbit的方法就能夠了,繼續下面。介於下面討論十進制已經沒有意義(這個世界原本就是二進制的,人非要主觀的構建一個十進制),下面全部的數沒有特別說明都看成二進制。this

       上面那麼多文字說lowbit,還沒說它的用處呢,它就是爲了聯繫a數組和c數組的!ck表示從ak開始往左連續求lowbit(k)個數的和,好比c[0110]=a[0110]+a[0101],就是從110開始計算了0010個數的和,由於lowbit(0110)=0010,能夠看到其實只有低位的1起做用,由於很顯然能夠寫出c[0010]=a[0010]+a[0001],這就爲何咱們任何數都只關心它的lowbit,由於高位不起做用(基於咱們的二分規則它必須如此!),除非除了高位其他位都是0,這時自己就是lowbit。spa

既然關係創建好了,看看如何實現a某一個位置數據跟改的,她不會直接改的(開始就說了,a根本不存在),她每次改其實都要維護c數組應有的性質,由於後面求和要用到。而維護也很簡單,好比更改了a[0011],咱們接着要修改c[0011],c[0100],c[1000],這是很容易從圖上看出來的,可是你可能會問,他們之間有申明必然聯繫嗎?每次求解總不能總要拿圖來看吧?其實從0011——>0100——>1000的變化都是進行「去尾」操做,又是本身造的詞--'',我來解釋下,就是把尾部應該去掉的1都去掉轉而換到更高位的1,記住每次變換都要有一個高位的1產生,因此0100是不能變換到0101的,由於沒有新的高位1產生,這個變換過程剛好是能夠藉助咱們的lowbit進行的,k +=lowbit(k)。code

       好吧,如今更新的次序都有了,可能又會產生新的疑問了:爲何它非要是這種關係啊?這就要追究到以前咱們說c8能夠看做a1~a8的左半邊和+右半邊和……的內容了,爲何c[0011]會影響到c[0100]而不會影響到c[0101],這就是以前說的c[0100]的求解其實是這樣分段的區間 c[0001]~c[0001] 和區間c[0011]~c[0011]的和,數字過小,可能這樣不太理解,在好比c[0100]會影響c[1000],爲何呢?由於c[1000]能夠看做0001~0100的和加上0101~1000的和,可是0101位置的數變化並會直接做用於c[1000],由於它的尾部1不能一下在跳兩級在產生兩次高位1,是經過c[0110]間接影響的,可是,c[0100]卻能夠跳一級產生一次高位1。orm

         可能上面說的你比較繞了,那麼此時你只需注意:c的構成性質(實際上是分組性質)決定了c[0011]只會直接影響c[0100],而c[0100]只會直接影響[1000],而下表之間的關係剛好是也必須是k +=lowbit(k)。此時咱們就是寫出跟新維護樹的代碼:htm

1 void add(int k,int num)  
2 {  
3        while(k<=n)  
4         {  
5             tree[k]+=num;  
6             k+=k&-k;  
7         }  
8 }  

 

       有了上面的基礎,說求和就比較簡單了。好比求0001~0110的和就直接c[0100]+c[0110],分析方法與上面的剛好逆過來,並且寫法也是逆過來的,具體就不累述了:

 

 1 int read(int k)//1~k的區間和  
 2 {  
 3        int sum=0;  
 4         while(k)  
 5         {  
 6             sum+=tree[k];  
 7             k-=k&-k;  
 8         }  
 9         return sum;  
10 }  

下面給出一道模版題吧!blog

POJ 2352

Stars
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 45080   Accepted: 19567

Description

Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of the stars that are not higher and not to the right of the given star. Astronomers want to know the distribution of the levels of the stars.

For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it's formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3.

You are to write a program that will count the amounts of the stars of each level on a given map.

Input

The first line of the input file contains a number of stars N (1<=N<=15000). The following N lines describe coordinates of stars (two integers X and Y per line separated by a space, 0<=X,Y<=32000). There can be only one star at one point of the plane. Stars are listed in ascending order of Y coordinate. Stars with equal Y coordinates are listed in ascending order of X coordinate.

Output

The output should contain N lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level N-1.

Sample Input

5
1 1
5 1
7 1
3 3
5 5

Sample Output

1
2
1
1
0

Hint

This problem has huge input data,use scanf() instead of cin to read data to avoid time limit exceed.

Source

題意:
就是求每一個小星星左小角的星星的個數。座標按照Y升序,Y相同X升序的順序給出
因爲y軸已經排好序,能夠按照x座標創建一維樹狀數組
 1 #include <stdio.h>
 2 #include <string.h>
 3 const int MAXN=32005;
 4 const int MINN=15005;
 5 int tree[MAXN];//下標爲橫座標
 6 int level[MINN];//下標爲等級數
 7 /*int lowerbit(int x)
 8 {
 9     return x&-x;
10 }*/
11 void add(int k,int num)
12 {
13     while(k<=MAXN)
14     {
15         tree[k]+=num;
16         k+=k&-k;
17     }
18 }
19 int read(int k)//1~k的區間和
20 {
21     int sum=0;
22     while(k)
23     {
24         sum+=tree[k];
25         k-=k&-k;
26     }
27     return sum;
28 }
29 int main()
30 {
31     int n,x,y,i;
32     memset(tree,0,sizeof(tree));
33     memset(level,0,sizeof(level));
34     while(scanf("%d",&n)!=EOF)
35     {
36         for(i=1;i<=n;i++)
37         {
38             scanf("%d%d",&x,&y);
39             int temp=read(x+1);//加入x+1,是爲了不0,X是可能爲0的
40             level[temp]++;
41             add(x+1,1);
42         }
43         for(i=0;i<n;i++)
44             printf("%d\n",level[i]);
45     }
46     return 0;
47 }
相關文章
相關標籤/搜索