作者:dylantsou
出处:http://blog.csdn.net/dylantsou
引言:
在本人的上一篇关于后缀数组的博客中,在例2求最长回文子串中提到过:要求其一个数组中任意区间的最大最小值,是一个RMQ问题,在那篇博客中因为重点是后缀数组,所以对于该问题没有展开讲述,本文就来为大家做进一步的讲解。
原理:
RMQ (Range minimum/maximum query)问题:
已知数组A,长度为n,求在范围[i,j]间的最大值(i此问题很显然可以直接从i遍历到j求得最大值,时间复杂度为O(n),这是朴素算法。但是对于RMQ问题,一般会让你多次求取一段区间内的最大值。例如:对于长度为8的数组,分别求出间隔为4的区间内的最大值,这就需要求出区间 [0,4] [1,5] [2,6] [3,7]内的最大值。如果每一次都用朴素算法,则时间复杂度为O(n2),而且会有很多的重复计算。
这就需要一种更高效的方法,今天我们要介绍的是ST算法,一种基于动态规划的算法。他先用O(logn)的时间进行预处理,求出任意区间内的最大值,然后在查询时只要O(1)时间就能得到结果。方法如下:
1、定义:
d[i,j] 是范围[i,i+j2-1]间最大值。
2、最佳子结构:
[a,b]区间的最大值T,可以用[a,m]区间的最大值M和[n,b]区间的最大值N来求得,即T = max(M,N)。但需要有一个前提条件,就是这两个子区间的合集能够包含所有[a,b]区间内的元素,即m+1>= n.
[a,m]区间的最大值可以用d[a,k]表示,即[a,a+2k-1]区间最大值;
[n,b]区间的最大值可以用d[b-2k+1,k])表示,即[b-2k+1,b]区间最大值。
所以,只要满足a+2k>= b-2k+1, [a,b]区间的最大值就可以用max(d[a,k],d[b-2k+1,k])表示,此时k>=log2(b-a+1)– 1。
3、递推公式:
用d[i,j]表示[i,i+2j-1]间最大值,也就a=i, b= i+2j-1,取k = log2(b-a+1) -1 = log2(i+2j-1-i+1) -1 = j-1.
则 d[i,j] = max(d[a,k], d[b-2k+1,k])
=max(d[ i, j-1 ], d[i+2j-1 – 2j-1+1,j-1])
= max(d[i,j-1],d[i+2j-1,j-1])
4、自底向上构造:
i是区间起始位置,其范围为[0,n);j要满足2j-1
2(n+1)。当j=0时,d[i,0]表示区间[i,i+20-1]的最大值,也就是A[i]的值。所以可以根据d[i,j] = max(d[i,j-1],d[i+2j-1,j-1])构造出整个d数组。
5、求区间最大值方法:
[a,b]区间的最大值为max(d[a,k], d[b-2k+1,k]),取k为满足k>=log2(b-a+1)– 1的最小值。因为d数组在预处理时已经得到,所以d[a,k], d[b-2k+1,k]都可以直接带值。
实现:
#include
#include #define MAX #ifdef MAX #define CMP(x,y) (x)>(y)?(x):(y) #else #define CMP(x,y) (x)<(y)?(x):(y) #endif //RMQ问题,用d[i,j]来表示[i,i+j^2-1]范围内的最大值,则d[i,j] = max(d[i,j-1],d[i+2^(j-1),j-1]) //预处理,求出d数组 void RMQ(int* array,int*d,int n,int m) { //对于j=0的情况,进行初始化 for(int i = 0; i < n; i++) d[i*m] = array[i]; //自底向上,迭代出d数组 int x,y; for(int j = 1; j < m; j++) { for(int i = 0; i+(j^2) <= n; i++) { x = d[ i*m + j-1 ]; y = d[ (i+2^(j-1))*m + j-1 ]; d[ i*m+j ] = CMP(x,y); } } } //获取array数组中,从i到j的最值,其中d是由array数组推导出来的,m是它的列数 int GetValue(int i,int j,const int* d,int m) { if(i>j) return 0; //因为[i,j]间最值为CMP(d[i,k],d[b-2^k+1,k]),所以k要满足k>=log2(b-a+1)-1 int k = log(float(j-i+1))/log(float(2)); int x = d[ i*m + k ]; int y = d[ (j-2^k+1)*m + k ]; return CMP(x,y); } int main() { int a[8] = {50,12,43,34,52,36,37,8}; int m = log((float)8)/log((float)2) + 1; //又j^2-1 <= n-1确定最大的j int* d = (int*)malloc( sizeof(int) * 8 * m ); RMQ(a,d,8,m); int r = GetValue(1,7,d,m); }