1、应用背景
RMQ(range maximum/minimum query)算法是为了解决在一个给定的数组里面,随机的查询某一个区间中的最大或者最小值。如果只是少量的查询,直接遍历数组就可以了,当这种查询频率很高时,这样频繁的遍历数组显然是不可行的,这就需要用到这个算法。一个原则,当我们需要减少程序运行的时间,要么就是优化代码,要么就是用时间换空间。前者也就是部分的减少时间,或者说效果不是那么显著。而后者,如果设计得好,时间可以大大减少。后者的思想也就是我们熟知的动态规划的思想。所以,一切说得很玄乎玄乎的算法,扒光之后,都是从最几个最基本的思想出发的。
2、动态规划的递推求解
动态规划,无非就是用一些数组来保存中间的结果,需要用的时候,可以在O(1)的时间内得到。OK,涉及到动态规划,就不可避免的需要得到递推式。下面来层层解析。以随机求最大值为例,假设数组是{1,2,6,9,3},假设我们现在下标从1开始(为什么?一般的查询区间是从1开始的,当然如果实际应用是查询区间从0开始,做相应的更改就可以了。用一个dpMax[j][i],表示第i个数据开始,长度为2^j的数据段中的最大值。比如说dpMax[1][0],即从1开始,长度为2^0=1的最大值,就是1本身。dpMax[1][1] = max{1,2} = 2,dpMax[1][2] = max{1,2,6,9} = 3。OK,那么任给一个dpMax[i][j],那一定是前2^j-1个数的最大值和后2^(j-1)个数的最大值中较大的一个,即dpMax[i][j] = max(dpMax[i][j-1],dpMax[i+2^(j-1)][j-1])。OK,递推式出来之后,就好处理啦~初始值,直接等于数组中的元素就可以啦~也就是长度为1的情况啦~
void RMQ(int n) { int k = GetBit(n); memcpy(&dpMax[0][1], &arr[1], n * sizeof(int)); memcpy(&dpMin[0][1], &arr[1], n * sizeof(int)); for(int j = 1; j <= k; ++j)//步长 { for(int i = 1; i <= n; ++i) { if(i + (1 << j) - 1 <= n) { dpMax[j][i] = max(dpMax[j - 1][i], dpMax[j - 1][i + (1 << (j - 1))]); dpMin[j][i] = min(dpMin[j - 1][i], dpMin[j - 1][i + (1 << (j - 1))]); } } } }
3、不是2的方幂怎么处理?
但是我们发现这里数组长度是5,不是2的方幂耶~那怎么办呢?假设数组的长度是n,那么就求出它的二进制最左边的一个1所代表的数(怎么求见GetBit)。继而我们发现我们要是求开始点b到结束点e的最大值,可能长度nLen = (e-b+1)也不是2的方幂,即不可能在我们的数组中直接有,没关系,根据我们刚刚求解递推式时候的思想,先求得k = log2(nLen),那么要求的最大值不就是前2^k个数的最大值和后2^k个数的最大值中较大者。这里可能两部分数据会有重复,那又怎么样,我们求最大值而已,一点都不会有影响。现在明白如果数组长度不是2的方幂怎么办了吧?凉拌,直接不管呗~~
int GetBit(unsigned int x) {//最高位的1的位置 int count = -1; while(x) { x >>= 1; ++count; } return count; }
一个实际的题目应用:NYOJ119 士兵杀敌(三)