您的位置:首页 > 理论基础 > 数据结构算法

[数据结构]RMQ算法

2015-07-13 15:41 218 查看
记得不久以前,我认真练习过的第一个数据结构是线段树,当时做了很多题目,poj3468hdu1166等等。看着模板敲线段树的感觉其实很不错,但是有一个问题:虽然线段树能高效做很多关于区间查询,区间更新的问题,但是架不住代码量太大......解题时,一定得想清楚每个节点要存哪些信息,以及这些信息如何高效更新,维护,查询。不要一更新就涉及到叶子节点,否则写线段树的代码根本就没有意义了......

听说RMQ算法问题是一个缩小版的线段树,神奇的是它的复杂度级别是和线段树相同的,对于大多数题目都可以使用RMQ算法来代替复杂的线段树,也就是说,要想达到一样的疗效,使用RMQ算法可以花更少的时间!废话少说,赶紧来看今天的内容。

1、概述:

RMQ其实指的是一种经常使用的操作,RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j),其中 i<=j<=n,返回数列A中下表在i,j之间的最小/最大值。这两个问题是在实际应用中经常遇到的问题,下面介绍一下解决这两种问题的比较高效的算法。当然,这个问题也可以用线段树(也叫做区间树)解决,算法复杂度为O(N)~O(NlogN),具体内容可以看以前的线段树内容。

2、RMQ算法

对于该问题,最容易想到的解决方案是遍历,复杂度是O(n)。但是当数据量非常大而且查询很频繁时,该算法无法在有效的时间内查询出正解。本文介绍了一种比较高效的在线算法来解决这个问题。所谓在线算法,是指用户每次输入一个查询,就马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后就可以用较少时间回答每个查询。ST算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(NlogN)的时间内进行预处理,然后在O(1)时间内回答每个查询。

1)首先是预处理,用动态规划(DP)解决。

设A[i]是要求区间最值的数列,F[i,j]表示从第i个数起连续2^j个数中的最大值。(DP的状态)

例如:A数列为:3 2 4 5 6 8 1 2 9 7

令F[1,0]表示第一个数起,长度为2^0 = 1 的最大值,其实就只有 3 这一个数字。

同理,F[1,1]=max(3,2) =3;

F[1,2]=max(3,2,4,5)=5;

F[1,3]=max(3,2,4,5,6,7,1,2) = 8;

由于F[i,0]中只有一个数A[i],所以F[i,0]的最大值一定等于A[i]。

这样,DP的初始状态,初值都已经有了,剩下的就是状态转移方程。

我们把F[i,j]平均分成两段(因为F[i,j]一定是偶数个数字),从i到 i+2^(j-1) -1 为第一段,i+2^(j-1) 到 i+2^j - 1 为第二段,两段长度相同,都是 2^(j-1)。以上面的A数列为例,当i=1,j=3时就是3,2,4,5和6,8,1,2这两段。F[i,j]就是max(
max(F[1...4]),max(F[5...8]) ),于是我们可以推出状态转移方程:

F[i,j]= max( F[i,j-1] , F[i+2^(j-1),j-1])。

代码如下:

const int MAXN =50010;

int dp[MAXN][20];
int mm[MAXN];
//初始化RMQ,b数组下标是从1开始的
void initRMQ(int n, int b[])
{
mm[0] = -1;
for(int i = 1; i<=n;i++)
{
mm[i] = ((i&(i-1)) == 0) ? mm[i-1]+1:mm[i-1];
dp[i][0] = b[i];
}
for(int j = 1;j<=mm
;j++)
{
for(int i = 1; i +(1<<j)-1 <= n;i++)
// dp[i][j] = max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
dp[i][j] = min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
其中n为RMQ需要初始化的长度,b[]为传入的区间的值,更新后,最大值关于mm和dp[][]相关。需要求max或者min时,把其中的max或者min改为相应需要求的问题即可。

这里我们需要注意的是循环的顺序,外层是j,内层是i,为什么呢?可以是i在外,j在内部吗?

答案是不可以,我们需要理解这个状态转移方程的意义。

状态转移方程的含义是:先更新所有长度为F[i,0]即一个元素,然后通过两个一个元素的最值,获得所有长度为F[i,1]即两个元素的最值,然后再通过2个2个元素的最值,获得所有长度为F[i,2]即4个元素的最值,以此类推更新所有长度最值。

而如果是i在外,j在内的话,我们更新的顺序就是F[1,0],F[1,1],F[1,2],F[1,3],表示更新从1开始,1个元素,2个元素,4个元素,8个元素。这里F(1,3)=max(max(A[0],A[1],A[2],A[3]),max(A[4],A[5],A[6],A[7]))的值,但是我们根本没有计算max(A[0],A[1],A[2],A[3]),max(A[4],A[5],A[6],A[7]),所以这样的方法肯定是错的。

为了避免这样的错误,一定要好好理解这个状态转移方程所代表的含义。

接下来是查询操作:

int rmq(int x,int y)
{
int k = mm[y-x+1];
return min(dp[x][k],dp[y-(1<<k)+1][k]);
}输入x,y就可以查询在此区间内的最大值了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: