樹狀數組(Binary Indexed Tree)

樹狀數組(BIT - Binary Indexed Tree)是一個用數組表示的樹型數據結構,最早用於頻次累計表中。樹狀數組中每個元素維護一個頻次表A的特定部分區段的累加和。假設輸入動態序列爲A,A中元素值可以被修改,如果使用直接蠻力法來查詢一個區間i..j的累加和,複雜度爲O(n),而樹狀數組由於每個元素存儲的是部分累加和,通過數組下標的二進制索引規律可以在O(logn)時間內找到1...j或i...j的累加和。存儲空間只需要維護樹狀數組T,A的元素值可以很容易地從T得到(即特例i=j時的區間查詢)。

數組A下標通常從0開始,而樹狀數組的有效下標是從1開始。下面關於lowbit的討論假設數組A下標從1開始。

樹狀數組中元素在樹型結構中的位置是根據數組下標的lowbit函數值2^r來確定的。定義每一個元素T[i]的值等於A[i-2^r + 1] + ... + A[i],即T[i]表示共2^r個元素的部分累加和,或者說T[i]元素管轄區段從i開始往前推2^r個元素。2^r的計算方法很簡單,就是i & (-i),原理是利用負數補碼等於相應正數值取反加一,例如當i=6時,對應的二進制數是0110,取反得到1001,加一得到1010,從而2^r=lowbit(i)=0110 & 1010 = 10,即2^r等於十進制值2,或者說T[6]管轄區段長度爲2, 即T[6] = A[5] + A[6]。

有了lowbit值,可以高效地查詢數列A從1到i的區間所有元素的累加和:即將若干個T[j]累計即可(j>=1),設T[j]管轄的區段長度爲lowbit(j),那麼T[j-1]管轄的區段的最高下標值可以通過j = j - lowbit(j)得到;也可以低代價更新數列A的某個元素,這時需要更新受影響的T中相應元素,在樹型結構中,某一個結點的父結點就是受影響的結點之一,而T[j]的父結點管轄的區段的最高下標值可以通過j = j + lowbit(j)得到 (j<=n)。查詢區間可以用差分方法: 如果i<=j,那麼sum(A[i..j]) = query(j) - query(i-1)。


實現:

/**
 * 
 * Binary Indexed Tree (BIT)
 * Peter M. Fenwick: A New Data Structure for Cumulative Frequency Tables
 * 
 * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
 * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) 
 * 
 * @author ljs
 * 2011-08-09
 * 
 */
public class BinaryIndexTree {
	private int[] T;

	public BinaryIndexTree(int n) {
		//each element in T is initialized to 0
		T = new int[n + 1];
	}
	
	private int lowbit(int idx){
		return idx & (-idx);
	}

	//idx starts with 1, not 0
	public void update(int idx, int val) {
		while (idx < T.length) {
			T[idx] += val;
			idx += lowbit(idx);
		}
	}
	//idx starts with 1, not 0
	public int query(int idx) {
		int sum = 0;
		while (idx > 0) {
			sum += T[idx];
			idx -= lowbit(idx);
		}
		return sum;
	}
	//get the sum from A[i] to A[j] inclusively
	public int queryInterval(int i,int j){
		if(j<i){
			//swap  
			int tmp=j;j=i;i=tmp;
		}
		return query(j)-query(i-1);
	}

	private void report(){
		int n = T.length - 1;
		//report
		System.out.print("A from tree=");
		for(int i=1;i<=n;i++){
			int ai = query(i)-query(i-1);
			System.out.format(" %d",ai);
		}
		System.out.println();
		
		//tree
		System.out.format("T[1..%d]=",n);
		for(int j=1;j<=n;j++){
			System.out.format(" %d",T[j]);			 
		}
		System.out.println();
		
		//query
		System.out.print("sums = ");
		for(int j=1;j<=n;j++){
			int r = this.query(j);
			System.out.format(" %d",r);			 
		}
		System.out.println();
		
		
		
	}
	public static void main(String[] args) throws Exception {
		int[] A = new int[]{2,3,4,5,6};
		int n = A.length;
		BinaryIndexTree bit = new BinaryIndexTree(n);
		
		//update all elements
		for(int i=0,j=1;i<n;i++,j++){
			bit.update(j, A[i]);
		}
		//update a single element
		int val = 4;
		int idx = 3;
		A[idx-1] += val;
		bit.update(idx, val);
		
		System.out.format("A[0..%d] =  ",n-1);
		for(int i=0;i<n;i++){
			System.out.format(" %d",A[i]);
		}
		System.out.println();
		
		bit.report();
		
		//sum query
		int q=3;
		int r = bit.query(q);
		System.out.format("sum A[%d..%d]: %d%n", 0,q-1,r);
		
		//interval query
		int p = 2;
		q=4;
		r = bit.queryInterval(p, q);
		System.out.format("sum A[%d..%d]: %d%n", p-1,q-1,r);
		
		System.out.println("*****************");
		A = new int[]{1,0,2,1,1,3,0,4,2,5,2,2,3,1,0,2};
		n = A.length;
		bit = new BinaryIndexTree(n);
		
		//update all elements
		for(int i=0,j=1;i<n;i++,j++){
			bit.update(j, A[i]);
		}
		System.out.format("A[0..%d] =  ",n-1);
		for(int i=0;i<n;i++){
			System.out.format(" %d",A[i]);
		}
		System.out.println();
		
		bit.report();
	}
}


輸出:

A[0..4] =   2 3 8 5 6
A from tree= 2 3 8 5 6
T[1..5]= 2 5 8 18 6
sums =  2 5 13 18 24
sum A[0..2]: 13
sum A[1..3]: 16
*****************
A[0..15] =   1 0 2 1 1 3 0 4 2 5 2 2 3 1 0 2
A from tree= 1 0 2 1 1 3 0 4 2 5 2 2 3 1 0 2
T[1..16]= 1 1 2 4 1 4 0 12 2 7 2 11 3 4 0 29
sums =  1 1 3 4 5 8 8 12 14 19 21 23 26 27 27 29


參考資料:

Peter M. Fenwick (1994): A New Data Structure for Cumulative Frequency Tables

TopCoder - Binary Indexed Trees

NOCOW-樹狀數組

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章