樹狀陣列入門 簡單的原理講解

2022-06-06 05:15:08 字數 2771 閱讀 5149

樹狀陣列可以解決什麼樣的問題:

這裡通過乙個簡單的題目展開介紹,先輸入乙個長度為n的陣列,然後我們有如下兩種操作:

輸入乙個數m,輸出陣列中下標1~m的字首和

對某個指定下標的數進行值的修改

多次執行上述兩種操作

尋常方法

對於乙個的陣列,如果需要求1~m的字首和我們可以將其從下標1開始對m個數進行求和,對於n次操作,時間複雜度是o(n^2),對於值的修改,我們可以直接通過下標找到要修改的數,n次操作時間複雜度為o(n),在陣列n開得比較大的時候,求字首和的效率顯得低了

樹狀陣列

那麼是否有一種方法可以讓查詢和更新的時間複雜度都小一些呢,至少可以令人接受,這裡將介紹樹狀陣列如何處理字首和查詢和單點更新的問題,對於n次操作,時間複雜度都為o(nlogn)

如圖,對於乙個長度為n的陣列,a陣列存放的是陣列的初始值,引入乙個輔助陣列c(我們通過c陣列建立樹狀陣列)

c1 = a1

c2 = c1 + a2 = a1 + a2

c3 = a3

c4 = c2 + c3 + a4 = a1 + a2 + a3 + a4

c5 = a5

c6 = c5 + a6 = a5 + a6

c7 = a7

c8 = c4 + c6 + c7 + a8 = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8

我們稱c[i]的值為下標為i的數所管轄的數的和,c[8]存放的就是被編號8所管轄的那些數的和(有8個),而下標為i的數所管轄的元素的個數則為2^k個(k為i的二進位制的末尾0的個數)舉兩個例子查詢下標m8和m5所管轄的數的和

而對於輸入的數m,我們要求編號為m的數的字首和a1~am(這裡假設樹狀陣列已經建立,即c1~c8的值已經求出,別著急,在本文的最下方會做出建立樹狀陣列的過程講解,因為現在是在求字首和,就假設c陣列已經可用了吧)舉兩個例子m7和m6(sum(i)表示求編號為i的字首和)

這裡要介紹乙個高效的方法,lowbit(int m),這是乙個函式,它的作用是求出m的二進位制表示的末尾1的位置,對於要查詢m的字首和,m = m - lowbit(m)代表不斷對二進位制末尾1進行-1操作,不斷執行直到m == 0結束,就能得到字首和由哪幾個cm構成,十分巧妙,lowbit也是樹狀陣列的核心

int lowbit(int m)
關於m&(-m)很多童鞋可能感到困惑,那麼就不得不提及一下負數在計算機記憶體中的儲存形式,負數在計算機中是以補碼的形式儲存的,如13的二進位制表示為1101,那麼-13的二進位制而將13二進位制按位取反,然後末尾+1,即0010 + 0001 = 0011,那麼1101 & 0011== 0001,很顯然得到m == 13二進位制末尾1的位置是2的0次方位,將13 - 0001 == 12,再對12執行lowbit操作,1100 & 0100 == 0100,也很輕易得到了m == 12時二進位制末尾1的位置是2的2次方位,將12 - 0100 == 8,再對8執行lowbit操作,0100 & 1100 == 0100,得到m == 8時二進位制位是2的2次方位,8 - 0100 == 0(結束操作),通過迴圈得到的13,12,8,則sum(13) == c13 + c12 + c8

求字首和的**

int ans = 0;

int getsum(int m)

}

對於n次字首和的查詢,時間複雜度為o(nlogn)

接下來講解單點更新值

對於輸入編號為x的值,要求為它的值附加乙個value值,我們把圖再一次拿下來

假設x2,value5,那麼我們先找到a[2]的位置,通過觀察我們得知,如果修改了a[2]的值,那麼管轄a[2]的c[2],c[4],c[8]的字首和都要加上value(所有的祖先節點),那麼和查詢類似,我們如何得到c2的所有祖先節點呢(因為c2和a2的下標相同所以更新時查詢從c[x]開始),依舊是上述的巧妙的方法,但是我們把它倒過來

對於要更新x位置的值,我們把x轉換成二進位制,不斷對二進位制最後乙個1的位置+1,直到達到陣列下標的最大值n結束

給出**

void update(int x, int value)

}

對於n次更新操作,時間複雜度同樣為o(nlogn)

這裡有乙個注意事項,我們對於求字首和與單點更新時,樹狀陣列c是拿來直接使用的,那麼問題來了,樹什麼時候建立好的,我怎麼不知道??

事實上,對於乙個輸入的陣列a,我們一次讀取的過程,就可以想成是乙個不斷更新值的過程(把a1~an從0更新成我們輸入的a[i]),所以一邊讀入a[i],一邊將c[i]涉及到的祖先節點值更新,完成輸入後樹狀陣列c也就建立成功了

#include#includeint a[10005];

int c[10005];

int n;

int lowbit(int x)

int getsum(int x)

return ans;

}void update(int x, int value)

}int main()

int ans = getsum(n-1); //用於測試輸出n-1的字首和 輸出28

printf("%d\n", ans);

} return 0;

}

樹狀陣列簡單入門

樹狀陣列是乙個查詢和修改複雜度都為log n 的資料結構。主要用於查詢任意兩位之間的所有元素之和。但是每次只能修改乙個元素的值,不如線段樹的應用範圍廣,但是寫起來比線段樹簡單很多,空間複雜度也會低一點。可以用來解一些像求逆序數的題。上面這張圖就表示了樹狀陣列與原陣列的關係 c陣列完整的儲存了a的所有...

樹狀陣列 講解

樹狀陣列 binary indexed tree b.i.t 是乙個查詢和修改複雜度都為log n 的資料結構。主要用於查詢任意兩位之間的所有元素之和,但是每次只能修改乙個元素的值 經過簡單修改可以在log n 的複雜度下進行範圍修改,但是這時只 請看下圖 我們令每個葉節點代表每乙個元素。現在我們變...

樹狀陣列 講解

樹狀陣列 插點法 插線法 最常見的一種用途是求乙個數列的前n項和 比如說陣列a 吧 把他轉化一下存入樹狀陣列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 16 a 1 a 2 a 16 也即c n 管理著2 k個數 k代表二進位制n最...