RMQ問題的樹狀數組解法

樹狀數組中每個元素覆蓋了不同長度的子區間,類似於稀疏表(ST)算法的思想,每一個數組元素存儲了輸入數列A在該區間的最小值下標。注意:這裏樹狀數組不是用來存儲區間累加值,而是區間的最小值下標。

這裏針對預處理階段提供兩個算法:方法1(參考以下實現代碼中的方法preprocess)採用類似於累加和中的update做法,每一個元素A[i]需要處理樹狀數組T中O(logn)個受影響的元素,因此預處理複雜度爲O(nlogn)。方法2(參考以下實現代碼中的方法preprocess2或preprocess3)利用DP(動態規劃)思想,觀察到樹狀數組中每一個下標爲i的元素覆蓋的區間都可以劃分爲:r個子區間 + 最後一個元素A[i]。(其中2^r=lowbit(i))因此只需要計算這r+1個區間中的最小值即可,該算法的複雜度爲可以這樣計算:對樹中每一層所有元素基本操作做累加,例如葉子結點個數爲n/(2^1),每一個葉子結點基本操作次數爲1,倒數第二層的結點個數爲n/(2^2),其中每一個結點的基本操作次數爲2....所以總的基本操作次數爲1 * n/(2^1) + 2 * n/(2^2) + 3 * n/(2^3) +  ...+ k * n/(2^k) + ... + logn * n/(2^logn) = O(n)。可見第二個方法比第一個方法複雜度要小。在以下的實現代碼中,方法preprocess2或preprocess3是等效的,preprocess3使用了類似累加和中的query做法,即找出互不重疊的子區間。

查詢時,採用遞歸做法,分兩種情形: case 1) 如果查詢的區間在當前區間中,則將當前區間最後一個元素剝離開來,使查詢區間長度減1;case 2)如果查詢的區間與當前區間沒有重疊,則將當前區間所有元素剝離開來,使查詢區間長度減去lowbit值(即2^r)。最糟情況下需要執行case 1)總共logn次,每次查詢時樹高度減1,所以查詢的複雜度爲(logn-1) + (logn-2)+...+1=O(logn*logn)。

跟ST算法或線段樹算法相比,樹狀數組解RMQ問題的優勢是存儲空間小。預處理複雜度跟線段樹算法相當(採用上面的方法2),但是查詢的複雜度比線段樹算法中的O(logn)要大。通過時間換空間達到時空平衡,再加上編程簡單,樹狀數組算法還是有一定價值的。

注意:在下面的代碼中數組A的下標永遠都是從0開始,而樹狀數組T的有效下標從1開始(T[0]沒有被利用)。另外,查詢部分是tail recursion,實際上可以改寫爲迭代計算。

實現:

/**
 * 
 * RMQ algorithm using Binary Indexed Tree (BIT)
 * 
 * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
 * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) 
 * 
 * 
 * @author ljs
 * 2011-08-10
 *
 */
public class RMQ_BIT {
	//each element is a min-val index in the range i-2^r +1 ...  i
	private int[] T; 

	public RMQ_BIT(int n) {
		//each element in T is initialized to 0
		T = new int[n + 1];
		//init T
		for(int i=1;i<=n;i++){
			T[i] = -1;
		}		
	}
	//preprocess method 1: create BIT - O(nlogn)
	public void preprocess(int[] A){
		int n = A.length;		
		for(int i=0;i<n;i++){
			update(A,i);
		}
	}
	//A better preprocess method 2: create BIT - O(n)
	public void preprocess2(int[] A){
		int n = A.length;		
		for(int i=1;i<=n;i++){
			int k = lowbit(i);
			T[i] = i-1; //initial value
			//each subrange's index is left shifted by 1 (=divide by 2)
			for(int j=1;j<k;j<<=1){
				//Note: use == for canonical RMQ result
				if(A[T[i]]>=A[T[i-j]]){
					T[i] = T[i-j];
				}
			}
		}
	}
	
	//another form of preprocess2: similar to query in BIT 
	public void preprocess3(int[] A){
		int n = A.length;		
		for(int i=1;i<=n;i++){
			int j = i - lowbit(i);					
			T[i] =  i - 1; //set the initial value of T[i]: i-1 is an index of A
			int k = i - 1; //i-1 is the next index of T
			while(k>j){
				//Note: use == for canonical RMQ result
				if(A[T[i]]>=A[T[k]]){
					T[i] = T[k];
				}				
				k -= lowbit(k);
			}
		}
	}
 
	private int lowbit(int x){
		return x & (-x);
	}
	//idx is A's index, starting with 0	
	private void update(int[] A,int idx) {
		int x = idx+1; //A's index starts with 0
		int val = A[idx];
		while (x < T.length) {
			if(T[x] ==-1 || A[T[x]] > val){
				T[x] = idx;
			}
			x += lowbit(x);
		}
	}
	//i,j are A's index, starting with 0
	public int query(int[] A,int i,int j){
		if(i>j){
			//swap  
			int tmp=i;i=j;j=tmp;
		}
		return query(A,i+1,j+1,j);
	}
	//precondition: p...q must be in the range: 1...n (inclusive)
	private int query(int[] A,int p,int q,int minIndex){
		if(p>q) return minIndex;
		
		int j = q - lowbit(q);
		if(p>j){
			//p..q is equal to or less than T[q]'s coverage
			if(A[q-1] <= A[minIndex])minIndex = q-1;
			return query(A,p,q-1,minIndex);
		}else{ // if(p<=j)
			//p..q is greater than T[q]'s coverage
			if(A[T[q]] <= A[minIndex])minIndex = T[q];
			return query(A,p,j,minIndex);
		}		
	}
	
	private void reportLUTable(int[] A){
		System.out.format("%n***********************%n");
		for(int x=0;x<A.length;x++){
			System.out.format("%d..[%d-%d]",x,x,A.length-1);
			for(int y=x;y<A.length;y++){
				int p = query(A,x,y);				
				System.out.format(" %d/%d",A[p],p);
			}
			System.out.println();
		}			
	}

	public static void main(String[] args) {
		int[] A = new int[]{2,4,3,1,6,7,8,9,1,7};
		int n = A.length;
		RMQ_BIT bitRMQ = new RMQ_BIT(n);
		
		bitRMQ.preprocess3(A);
		int i=0;
		int j=3;
		int min = bitRMQ.query(A, i,j);
		System.out.format("RMQ for A[%d..%d]: A[%d]=%d", i,j,min,A[min]);
		
		bitRMQ.reportLUTable(A);
		
		////////////////////
		System.out.format("%n***********************%n");
		
		A=new int[]{10,15,34,20,7,5,18,68,29,40, //0..9
				24,3,45,26,7,23,43,12,68,34,  //10..19
				26,34,33,12,80,57,24,42,77,27, //20..29
				56,33,23,32,54,13,79,65,19,33,  //30..39
				15,24,43,73,55,13,63,8,23,17};  //40..49
		n = A.length;
		bitRMQ = new RMQ_BIT(n);
		
		bitRMQ.preprocess3(A);
		
		i=0;
		j=10;		
		min = bitRMQ.query(A,i,j);
		System.out.format("RMQ for A[%d..%d]: A[%d]=%d%n", i,j,min,A[min]);
		
		i=12;
		j=49;		
		min = bitRMQ.query(A,i,j);
		System.out.format("RMQ for A[%d..%d]: A[%d]=%d%n", i,j,min,A[min]);
		
		i=20;
		j=46;		
		min = bitRMQ.query(A,i,j);
		System.out.format("RMQ for A[%d..%d]: A[%d]=%d%n", i,j,min,A[min]);
		
		i=20;
		j=49;		
		min = bitRMQ.query(A,i,j);
		System.out.format("RMQ for A[%d..%d]: A[%d]=%d%n", i,j,min,A[min]);
		
	}

}


測試輸出:


RMQ for A[0..3]: A[3]=1
***********************
0..[0-9] 2/0 2/0 2/0 1/3 1/3 1/3 1/3 1/3 1/3 1/3
1..[1-9] 4/1 3/2 1/3 1/3 1/3 1/3 1/3 1/3 1/3
2..[2-9] 3/2 1/3 1/3 1/3 1/3 1/3 1/3 1/3
3..[3-9] 1/3 1/3 1/3 1/3 1/3 1/3 1/3
4..[4-9] 6/4 6/4 6/4 6/4 1/8 1/8
5..[5-9] 7/5 7/5 7/5 1/8 1/8
6..[6-9] 8/6 8/6 1/8 1/8
7..[7-9] 9/7 1/8 1/8
8..[8-9] 1/8 1/8
9..[9-9] 7/9

***********************
RMQ for A[0..10]: A[5]=5
RMQ for A[12..49]: A[14]=7
RMQ for A[20..46]: A[23]=12
RMQ for A[20..49]: A[47]=8


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