1. 程式人生 > >淺談樹狀數組

淺談樹狀數組

turn span n) clas 這樣的 每一個 bubuko pda 序號

之前也看過了好多關於樹狀數組的博客,結合這幾天做的題,我一直想好好總結一下樹狀數組,這篇文章就來淺談一下樹狀數組。

1.前言

首先我們要明白樹狀數組是一種數據結構,利用樹狀數組可以以空間換取時間,這一點和之前的線段樹一樣,但是樹狀數組訪問會更快,效率更高,樹狀數組不同於線段數的一點就是這棵樹的構成。

二叉樹或者線段樹是這樣的:

技術分享圖片

而樹狀數組是這樣的:

技術分享圖片

對於樹狀數組這種數據結構其中有三個數組是非常重要的:

a[ ]數組——被維護的數組,就是這棵樹最下面的葉子結點。

c[ ]數組——或許這個才可以真正的稱之為樹狀數組,它是用來存儲部分葉子結點之和的,就像是一種工具,就是利用它來提高訪問效率的。

sum[ ]數組——前i項a[ ]數組的和,a[ ]的前綴和,這個才是真正需要用來做題的,如何解題全都要圍繞這這個sum[ ]數組。

2.樹狀數組的建立

技術分享圖片

如圖可以知道

C[1]=A[1]; C[2]=A[1]+A[2]; C[3]=A[3];
C[4]=A[1]+A[2]+A[3]+A[4]; C[5]=A[5];
C[6]=A[5]+A[6];
C[7]=A[7];
C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]; 那麽這個c[ ]數組到底是怎麽樣得到的? 觀察這個圖 技術分享圖片

再將其轉化為二進制

看一下:

C[1] = C[0001] = A[1];

C[2] = C[0010] = A[1]+A[2];

C[3] = C[0011] = A[3];

C[4] = C[0100] = A[1]+A[2]+A[3]+A[4];

C[5] = C[0101] = A[5];

C[6] = C[0110] = A[5]+A[6];

C[7] = C[0111] = A[7];

C[8] = C[1000] = A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

從上圖能看出來每一個數的父節點就是右邊比自己末尾零個數多的最近的一個(結合二進制觀察圖像得到的樸素理解)

對照式子可以發現 C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k為i的二進制中從最低位到高位連續零的長度)例如i=8(1000)時,k=3;

C[8] = A[8-2^3+1]+A[8-2^3+2]+......+A[8]

即為上面列出的式子

為此我們引用的一個函數 lowbit( )

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

其作用是取出x最低位的1,其實不難看出lowbit(x)便是上面的2^k,因為2^k後面一定有k個0

比如說2^5==>100000

正好是i最低位的1加上後綴0所得的值。

那麽我們還是會很好奇這個二進制運算是如何實現這種計算的?

我們知道,對於一個數的負數就等於對這個數取反+1,以二進制數11010為例:11010的補碼為00101,加1後為00110,兩者相與便是最低位的1。其實很好理解,補碼和原碼必然相反,所以原碼有0的部位補碼全是1,補碼再+1之後由於進位那麽最末尾的1和原碼。最右邊的1一定是同一個位置(當遇到第一個1的時候補碼此位為0,由於前面會進一位,所以此位會變為1),所以我們只需要進行a&(-a)就可以取出最低位的1了。

會了lowbit,我們就可以進行區間查詢和單點更新了

3.單點更新

繼續看開始給出的圖,此時如果我們要更改a[1]

則有以下需要進行同步更新

1(001) C[1]+=a[1]

lowbit(1)=001 1+lowbit(1)=2(010) C[2]+=a[1]

lowbit(2)=010 2+lowbit(2)=4(100) C[4]+=a[1]

lowbit(4)=100 4+lowbit(4)=8(1000) C[8]+=a[1]

轉化成代碼:

void update(int x,int d)
{
    while(x<=n)
    {
        c[x]+=d;
        x+=lowbit(x);
    }
}
//x為更新後的位置,d為更新的值,n為數組的最大值

4.區間查詢

舉個例子 i=5

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

可以推出: sum(i = 5) ==> C[4]+C[5];

序號寫為二進制: sum(101)=C[(100)]+C[(101)];

第一次101,減去最低位的1就是100;

其實也就是單點更新的逆操作

代碼如下:

int Getsum(int x)
{
    int s=0;
    while(x>0)
    {
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}

sum(x)就是a[x]的前綴和,想查詢l~r區間的元素和只需要求出來sum(r)-sum(l-1)。

模板代碼:
 1 int lowbit(int x)
 2 {
 3     return x&(-x);
 4 }
 5 int Getsum(int x)
 6 {
 7     int s=0;
 8     while(x>0)
 9     {
10         s+=c[x];
11         x-=lowbit(x);
12     }
13     return s;
14 }
15 void update(int x,int d)
16 {
17     while(x<=n)
18     {
19         c[x]+=d;
20         x+=lowbit(x);
21     }
22 }


淺談樹狀數組