一維樹狀陣列

2021-07-04 08:47:16 字數 3026 閱讀 9396

2013-05-30 20:54

329人閱讀收藏

舉報 一維陣列相信大家平時都是經常使用,對於一維陣列而言,查詢以及求和的時間複雜度分別為o(1) 和 o(n) 。

今天我們介紹乙個新的資料結構——樹狀陣列英文名稱為binary index tree,直譯過來就是二進位制索引樹,我覺得二進位制索引樹更能說明其本質。樹狀陣列的本質就是一種通過二進位制位來維護乙個序列前i和的資料結構。它的查詢和求和的時間複雜度均為o(logn)。而且特別是當陣列元素的值可以隨時發生變化時,它的求和不需要像線性陣列一樣重新從頭掃到尾更新求和而是只用更新改變的部分元素即可。其實樹狀陣列通俗地說就是把一位線性陣列模擬成樹狀,從而達到跳躍性的查詢和求和。

序列c就是樹狀陣列, 那麼c如何求得?

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[i]的抽象定義:因為a中的每個元素對應滿二叉樹的每個葉子,所以我們乾脆把a中的每個元素當成葉子,那麼:c[i] = c[i]的所有葉子的和。

現在不得不引出關於二進位制的乙個規律:  將十進位制化成二進位制,然後觀察這些二進位制數最右邊1的位置:

1 --> 00000001

2 --> 00000010

3 --> 00000011

4 --> 00000100

5 --> 00000101

6 --> 00000110

7 --> 00000111

8 --> 00001000

1的位置其實從我畫的滿二叉樹中就可以看出來。

但是這與c有什麼關係呢?

接下來的這部分內容很重要:

在滿二叉樹中,以1結尾的那些結點(c[1],c[3],c[5],c[7]),其葉子數有1個,所以這些結點c[i]代表區間範圍為1的元素和;以10結尾的那些結點(c[2],c[6]),其葉子數為2個,所以這些結點c[i]代表區間範圍為2的元素和;以100結尾的那些結點(c[4]),其葉子數為4個,所以這些結點c[i]代表區間範圍為4的元素和;以1000結尾的那些結點(c[8]),其葉子數為8個,所以這些結點c[i]代表區間範圍為8的元素和。

擴充套件到一般情況:i的二進位制中的從右往左數有連續的x個「0」,那麼擁有2^x個葉子,為序列a中的第i-2^x+1到第i個元素的和。

終於,我們得到了乙個c[i]的具體定義:c[i]=a[i-2^x+1]+…+a[i],其中x為i的二進位制中的從右往左數有連續「0」的個數。

理解了c[i]後,前i個元素的和s[i]就很容易實現。

從c[i]的定義出發:c[i]=a[i-2^x+1]+…+a[i] (其中x為i的二進位制中的從右往左數有連續「0」的個數)我們可以知道:c[i]是肯定包括a[i]的,那麼:s[i]=c[i]+c[i-2^x]+… (其中x為i的二進位制中的從右往左數有連續「0」的個數)也許上面這個公式太抽象了。

我們拿乙個具體的例項來看:

s[7]=c[7]+c[6]+c[4]   因為c[7]=a[7],c[6]=a[6]+a[5],c[4]=a[4]+a[3]+a[2]+a[1],所以s[7]=c[7]+c[6]+c[4]s[7] = c[7] + c[6] + c[4]

但為什麼s[7] 是 c[7] 和 c[6] 和c[4] 的和呢?s[7] 中必然有c[7], 但c[6] 和 c[4] 是如何得出的?

首先i=7 ( 7 的二進位制為111 沒有0 ,所以x = 0 )求得x=0根據公式 (s[i]=c[i]+c[i-2^x]+… 其中 i = i -2^x )

其次x = 0   i = i -2^x => i = 7 - 2^0 = 6 

求得c[6] ( 6 的二進位制為110 有乙個0 所以x = 1)x = 1i = i -2^x => 6- 2^1 = 4

最後一項為c[4],所以 s[7] = c[7] + c[6] + c[4]。

但是如何寫程式判斷二進位制末尾有多少個零呢?

這裡有個簡單的結論:2 ^ x = i & (-i)至於為什麼是這樣,請先記住吧,這裡要講的太多了。

這個結論的**如下:

[cpp]view plain

copy

print?

int lowbit(int i)    

int lowbit(int i)

下面是**初始化或者更新:給某個節點i加上value

[cpp]view plain

copy

print?

void change(int i, int value)  

}  

void change(int i, int value)

}

求前i項和:

首先令sum = 0.

判斷,如果i>0 的話,就令sum+=c[i] 轉向第三部,否則,終止演算法返回sum值.

i = i-lowbit(i), (將n的二進位制表示的最後乙個零刪掉)回第二步

**如下:

[cpp]view plain

copy

print?

int sum( int i )  

return sum;  

}  

int sum( int i )

return sum;

}

更新 和 求和 乙個 是 i+=lowbit(i) ,另乙個是 i-=lowbit(i) 區別在於更新是從樹葉向樹根更新,也就是i 是從小到大 而求和是求 前 i 項的和,所以 i是最大,然後遞減.

一維樹狀陣列

一維樹狀陣列中的元素是某乙個區間內的n個元素和,對於n的值,用二進位制位和圖形結合則更容易理解。c1 0 0 0 1 1個元素 c2 0 0 1 0 2個元素 c3 0 0 1 1 1個元素 c4 0 1 0 0 4個元素 自己多寫幾個會容易發現 cn內的元素個數是n的二進位制表示形式的最低位1構成...

一維樹狀陣列和二維樹狀陣列

hdu1166敵兵布陣 樹狀陣列適用於頻繁對陣列元素進行修改,同時又要查詢陣列內任一區間元素之和。聽說線段樹也可以做,線段樹的作用 點修改,區間查詢,區間修改。include include include include using namespace std int a 50005 int c ...

從一維樹狀陣列到二維樹狀陣列

今天接觸二維樹狀陣列。其實,要明確的一點是,不管是一維還是二維樹狀陣列,都只是工具而已,只是幫助我們更快地求和,查詢,樹狀陣列的這些操作都可以用我們平常的方法求,例如一直加。面對乙個二維陣列,我們要求它們的和,會怎麼做呢?先求出第一行的總和 再求出第二行的總和 再求出第三行的總和 求出最後一行的總和...