ST(Sparse Table)算法的基本思想是,預先計算從起點A[i]開始長度爲2的j次方(j=0,1...logn)的區間的最小值,然後在查詢時將任何一個區間A[i..j]劃分爲兩個預處理好的可能重疊的區間,取這兩個重疊區間的最小值。
在預處理階段,從起點A[i]開始,任何一個長度爲2^j的區間都可以劃分爲兩個長度2^(j-1)的區間,其中第一個區間的範圍爲:i...i+2^(j-1)-1;第二個區間的範圍爲:i+2^(j-1)...i+2^j-1。用M[i,j]表示從A[i]開始,長度爲2^j的區間(即A[i]...A[i+2^j-1])最小值對應的下標,那麼A[M[i,j]] = min{A[i...i+2^(j-1)-1], A[i+2^(j-1)...i+2^j-1]}。 利用DP思想,先計算M[i,j-1]的值,然後計算M[i,j]的值。
在查詢階段,任何區間A[i..j]的長度d=j-i+1,令k=floor(logd),那麼該區間可以被兩個長度爲2^k的子區間完全覆蓋,這兩個長度爲2^k的區間可以有重疊。由於這兩個區間已經在預處理中求得最小值,因此可以取二者的最小值得到A[i..j]的最小值。
ST算法預處理階段的複雜度爲O(nlogn),查詢階段的複雜度爲O(1)。
實現:
/**
*
* Using ST(Sparse Table) algorithm to solve RMQ problem
* time complexity: <O(nlogn),O(1)>
*
*
* Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
* Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
*
* @author ljs
* 2011-08-02
*
*/
public class RMQ_ST {
//ST: O(nlogn) for preprocessing
public int[][] preprocess(int[] A){
int n = A.length;
//floor value
int maxJ=(int)(Math.log(n)/Math.log(2));
int[][] M = new int[n][maxJ+1];
//initial condition for dynamic programming: the RMQ for interval length=1
for (int i = 0; i < n; i++)
M[i][0] = i;
//dynamic programming: compute values from smaller(j=1) to bigger intervals
for (int j = 1; j<=maxJ; j++){
for (int i = 0; i + (1 << j) - 1 < n; i++){
int nexti = i + (1 << (j - 1));
if (A[M[i][j - 1]] <= A[M[nexti][j - 1]])
M[i][j] = M[i][j - 1];
else
M[i][j] = M[nexti][j - 1];
}
}
return M;
}
//ST: O(1) for querying
public int query(int[] A,int[][] M,int i,int j){
if(j<i){
//swap i and j
int tmp=i;i=j;j=tmp;
}
int k = (int)(Math.log(j-i+1)/Math.log(2));
//the first interval
int mina = M[i][k];
int minb = M[j-(1<<k)+1][k];
if(A[mina]<=A[minb])
return mina;
else
return minb;
}
public static void main(String[] args) {
int[] A=new int[]{0,1,2,3,7,1,9,2,8,6};
RMQ_ST st = new RMQ_ST();
int[][] M = st.preprocess(A);
System.out.format("%n***********************%n");
int i=3,j=7;
int min = st.query(A,M, i,j);
System.out.format("RMQ for A[%d..%d]: A[%d]=%d", i,j,min,A[min]);
System.out.format("%n***********************%n");
j=3;i=7;
min = st.query(A,M, i,j);
System.out.format("RMQ for A[%d..%d]: A[%d]=%d", i,j,min,A[min]);
System.out.format("%n***********************%n");
for(int x=0;x<A.length;x++){
for(int y=0;y<x;y++){
System.out.format(" ");
}
for(int y=x;y<A.length;y++){
int p = st.query(A,M,x,y);
System.out.format(" %d/%d",A[p],p);
}
System.out.println();
}
}
}
參考資料:
The LCA Problem Revisited (2000) by Michael A. Bender & Martin Farach-Colton