RMQ算法

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)); 多了的话没有必要!



  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我不确定您指的是哪种RMQ算法。一般来说,RMQ是“区间最小值查询”(Range Minimum Query)的缩写,其实现算法有多种。以下是两种常见的RMQ算法的实现代码,供参考: 1. 线段树RMQ算法 ```python class SegmentTree: def __init__(self, arr): self.tree = [0] * (4 * len(arr)) self.build(arr, 0, 0, len(arr) - 1) def build(self, arr, index, left, right): if left == right: self.tree[index] = arr[left] else: mid = (left + right) // 2 self.build(arr, index * 2 + 1, left, mid) self.build(arr, index * 2 + 2, mid + 1, right) self.tree[index] = min(self.tree[index * 2 + 1], self.tree[index * 2 + 2]) def query(self, index, left, right, qleft, qright): if left > qright or right < qleft: return float('inf') elif qleft <= left and qright >= right: return self.tree[index] else: mid = (left + right) // 2 return min(self.query(index * 2 + 1, left, mid, qleft, qright), self.query(index * 2 + 2, mid + 1, right, qleft, qright)) # 示例 arr = [1, 3, 2, 7, 9, 11] tree = SegmentTree(arr) print(tree.query(0, 0, len(arr) - 1, 1, 4)) # 输出2,即arr[2:5]的最小值 ``` 2. ST算法 ```python import math class ST: def __init__(self, arr): n = len(arr) k = int(math.log2(n)) self.table = [[0] * (k + 1) for _ in range(n)] for i in range(n): self.table[i][0] = arr[i] for j in range(1, k + 1): for i in range(n - 2 ** j + 1): self.table[i][j] = min(self.table[i][j - 1], self.table[i + 2 ** (j - 1)][j - 1]) def query(self, left, right): k = int(math.log2(right - left + 1)) return min(self.table[left][k], self.table[right - 2 ** k + 1][k]) # 示例 arr = [1, 3, 2, 7, 9, 11] st = ST(arr) print(st.query(1, 4)) # 输出2,即arr[2:5]的最小值 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值