RMQ算法

来源:互联网 发布:在淘宝开店快递怎么搞 编辑:程序博客网 时间:2024/05/14 08:31

RMQ(Range Max/Min Query)算法,即区间最大/最小值查询算法。给定一个长度为n的数串A,RMQ(A,i,j)即是查找A中第i到第j个数中间最大/最小的数(1<= i,j <= n)。其实想想,找一个区间最值,最简单的直接比较,复杂度也是O(n),所以如果查找次数很少,用RMQ没有意义。RMQ的应用场景就是要对一个数串查询多次的情况。基本思想是对串中所有可能的区间组合的最值用二维数组保存,也就是所谓的预处理,查询时直接数组下标获取,O(1)的时间。下面采用动态规划来对数串进行预处理,也就是填充二维数组。

设A是要求区间最值的数串,F[i, j]表示从第i个数起连续2^j个数中的最大值。注:同其它动态规划状态数组类似,下标都是从1开始,下标0为初始化值,初始化值则根据问题不同,取值不同。例如:

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

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数本身,所以F[i,0]就是原数列本身。同理 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,8,1,2) = 8; 

这里有一点不好理解的地方,如果你在纸上画求解过程,一般会横着写这个数串,也惯性的把i作为行下标,j作为列下标,但这里是反着的,也就是i是对数列进行遍历,j是对倍增的次方进行遍历(2的倍增嘛,有兴趣可以研究下倍增算法,道理一样的),这样会好算些。

原理直白一点说:比如我们要求最大值,我们一次只能进行两个数的比较,所以,开始我们对整个数列进行(1,2),(3, 4)...两两比较,比较完了后,就筛选出了一组较大的数,较小的数就不用管了,需要比较的长度一下减少了一半。然后再对这组较大的数进行比较,以此类推。任何时候数列长度如果是奇数,最后一数直接拿出来就行了,这样,状态转移方程基本也就出来了。为了求F[i,j],它上一状态是什么样呢,因为i是对元素的遍历,所以和状态无关;j是对比较串长度的遍历,和状态有关,就是要用到F[i, j-1]。而F[i, j]的结果是上个状态两个长度相等的数列的比较结果(max或min)。第一个数列的结果为F[i, j-1],第二个数列的起始元素下标就应该是第一个的起始元素下标加第一个数列长度,因为无论何时都是两个长度相等的数列的比较。

从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1为一段(长度都为2 ^ (j - 1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。F[i,j]就是这两段各自最大值中的最大值。于是我们得到了状态转移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。

#include<iostream>#include<string>#include<algorithm>#include<cmath>using namespace std;const int N = 1000;int maxsum[20][N], minsum[20][N];void RMQ(int num) //预处理->O(nlogn){int k = (int)(log(num) / log(2.0));for (int i = 1; i <= k; ++i)for (int j = 1; j <= num; ++j)if (j + (1 << i) - 1 <= num){maxsum[i][j] = max(maxsum[i - 1][j], maxsum[i - 1][j + (1 << i >> 1)]); minsum[i][j] = min(minsum[i - 1][j], minsum[i - 1][j + (1 << i >> 1)]);}}int main(){int iStr[11] = {3,2,4,5,6,8,1,2,9,7};int num = 10;int src, des;for (int i = 0; i < num; ++i) {maxsum[0][i+1] = iStr[i];minsum[0][i+1] = iStr[i];}RMQ(num);while (1) //O(1)查询{cout << "输入查询首尾下标:" << endl;<span style="white-space:pre"></span>//下标从1开始scanf_s("%d %d", &src, &des);int k = (int)(log(des - src + 1.0) / log(2.0));int maxres = max(maxsum[k][src], maxsum[k][des - (1 << k) + 1]);int minres = min(minsum[k][src], minsum[k][des - (1 << k) + 1]);printf("最大值:%d\n", maxres );printf("最小值:%d\n", minres);}return 0;}

这里,在建立状态二维数组的时候,令总行数 k = (int)(log(num) / log(2.0)); 多了的话没有必要!



1 0
原创粉丝点击