RMQ问题

来源:互联网 发布:淘宝摄影师的要求 编辑:程序博客网 时间:2024/04/29 15:17

作者:dylantsou

出处:http://blog.csdn.net/dylantsou


引言:

        在本人的上一篇关于后缀数组的博客中,在例2求最长回文子串中提到过:要求其一个数组中任意区间的最大最小值,是一个RMQ问题,在那篇博客中因为重点是后缀数组,所以对于该问题没有展开讲述,本文就来为大家做进一步的讲解。


原理:

          RMQ (Range minimum/maximum query)问题:

已知数组A,长度为n,求在范围[i,j]间的最大值(i<j<n)。

         此问题很显然可以直接从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<n,所以j< log2(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 <math.h>#include <stdlib.h>#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)-1int 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确定最大的jint* d = (int*)malloc( sizeof(int) * 8 * m );RMQ(a,d,8,m);int r = GetValue(1,7,d,m);}