Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2461276
  • 博文数量: 392
  • 博客积分: 7040
  • 博客等级: 少将
  • 技术积分: 4138
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-17 13:03
个人简介

范德萨发而为

文章分类

全部博文(392)

文章存档

2017年(5)

2016年(19)

2015年(34)

2014年(14)

2013年(47)

2012年(40)

2011年(51)

2010年(137)

2009年(45)

分类:

2010-04-19 14:27:49

RMQ(Range Minimum Query) : 给定一个数组,求给定的两个索引(下标)间最小值元素的索引.

符号意义 :

假设一个算法有 f(n) 的预处理时间和g(n) 的查询时间.则这个算法的总的时间复杂度记为

记数组A 在索引ij 之间的最小值元素的索引为RMQA (i, j) .


例子:A[0,9]




对于给定的数组A[0,N-1] ,N 为元素个数

解法一 :动态规划

对于每对索引(i , j) 存储值RMQA (i, j) 在数组M[0, N-1][0, N-1]

预处理函数如下:

  1. void process1(int M[MAXN][MAXN], int A[MAXN], int N)  
  2.  {  
  3.      int i, j;  
  4.      for (i =0; i < N; i++)  
  5.          M[i][i] = i;  
  6.      for (i = 0; i < N; i++)  
  7.          for (j = i + 1; j < N; j++)  
  8.              if (A[M[i][j - 1]] < A[j])  
  9.                  M[i][j] = M[i][j - 1];  
  10.              else  
  11.                  M[i][j] = j;  
  12.  }  

算法时间复杂度为2 ) , O(1)> ,空间复杂度为O(N2 ) ,对于N比较大时,很耗内存

解法二Sparse Table ( 稀疏表 )

一个好的方法是对长度为 2k 的子数组进行预处理。保存一个数组M[0, N-1][0, logN]M[i][j] 表示从一个下标从i 开始,长度为2j 的子数组中最小元素的下标

例子如下:




  为了计算M[i][j] ,我们必须搜索这个区间的前半部分和后半部分.很明显,每一部分的长度为 2j - 1 。递推式如下:



(原文这里貌似有问题,i+2^(j-1)-1应该改为i+2^(j-1))
预处理函数如下所示:

  1. void process2(int M[MAXN][LOGMAXN], int A[MAXN], int N)  
  2.  {  
  3.      int i, j;  
  4.     
  5.  //initialize M for the intervals with length 1  
  6.      for (i = 0; i < N; i++)  
  7.          M[i][0] = i;  
  8.  //compute values from smaller to bigger intervals  
  9.      for (j = 1; 1 << j <= N; j++)  
  10.          for (i = 0; i + (1 << j) - 1 < N; i++)  
  11.              if (A[M[i][j - 1]] < A[M[i + (1 << (j - 1))][j - 1]])  
  12.                  M[i][j] = M[i][j - 1];  
  13.              else  
  14.                  M[i][j] = M[i + (1 << (j - 1))][j - 1];  
  15.  }    


预处理后,让我们看看怎么计算RMQA (i, j) . 思想就是选择出两个将区间[i..j] 完全覆盖的小区间(预处理的M ),然后取其较小值. 取k = [log(j - i + 1)] . 为了计算 RMQA (i, j) 使用如下公式:


此算法总的时间复杂度为 .


解法三 :Segment Tree(线段树)

为了解决RMQ,还可以使用线段树.线段树能在对数时间内在数组区间上进行更新与查询。 我们定义线段树在区间[i, j] 上如下:
  • 第一个节点维护着区间 [i, j] 的信息。
  • if i , 那么左孩子维护着区间[i, (i+j)/2] 的信息,右孩子维护着区间[(i+j)/2+1, j] 的信息。
可知 N 个元素的线段树的高度 为 [logN] + 1(只有根节点的树高度为0) . 下面是区间 [0, 9] 的一个线段树:






线段树和堆有一样的结构, 因此如果一个节点编号为 x ,那么左孩子编号为2*x 右孩子编号为2*x+1 (在这里下标从1开始).

使用线段树解决RMQ问题,我们要维护一个数组M[1, 2 * 2[logN] + 1 ]M[i] 维护着被分配给该节点的区间的最小值元素的下标。 该数组初始状态为-1 . 树应该被以下函数初始化 (b e 是当前区间的左右边界):

  1. void initialize(intnode, int b, int e, int M[MAXIND], int A[MAXN], int N)  
  2.   {  
  3.       if (b == e)  
  4.           M[node] = b;  
  5.       else  
  6.        {  
  7.   //compute the values in the left and right subtrees  
  8.           initialize(2 * node, b, (b + e) / 2, M, A, N);  
  9.           initialize(2 * node + 1, (b + e) / 2 + 1, e, M, A, N);  
  10.   //search for the minimum value in the first and   
  11.   //second half of the interval  
  12.           if (A[M[2 * node]] <= A[M[2 * node + 1]])  
  13.               M[node] = M[2 * node];  
  14.           else  
  15.               M[node] = M[2 * node + 1];   
  16.       }  
  17.   }   


该函数反映的是树被构建的方法。 我们应该用 node = 1 , b = 0e  = N-1 来调用这个函数

现在开始查询. 如果我们想要找出区间 [i, j] 上的最小值的索引,我们应该使用下面这个简单的函数:

  1. int query(int node, int b, int e, int M[MAXIND], int A[MAXN], int i, int j)  
  2.  {  
  3.      int p1, p2;  
  4.   
  5.     
  6.  //if the current interval doesn't intersect   
  7.  //the query interval return -1  
  8.      if (i > e || j < b)  
  9.          return -1;  
  10.     
  11.  //if the current interval is included in   
  12.  //the query interval return M[node]  
  13.      if (b >= i && e <= j)  
  14.          return M[node];  
  15.     
  16.  //compute the minimum position in the   
  17.  //left and right part of the interval  
  18.      p1 = query(2 * node, b, (b + e) / 2, M, A, i, j);  
  19.      p2 = query(2 * node + 1, (b + e) / 2 + 1, e, M, A, i, j);  
  20.     
  21.  //return the position where the overall   
  22.  //minimum is  
  23.      if (p1 == -1)  
  24.          return M[node] = p2;  
  25.      if (p2 == -1)  
  26.          return M[node] = p1;  
  27.      if (A[p1] <= A[p2])  
  28.          return M[node] = p1;  
  29.      return M[node] = p2;  
  30.   
  31.  }  



我们可以用 node = 1 , b = 0e = N - 1 来调用这个函数。 因为第一个节点的区间是 [0, N-1] .

很容易看出任何查询都能在时间 O(log N) 内完成。
使用线段树得到了一个 的算法.
线段树非常有用,不仅仅因为它可以用在解决RMQ问题上。它是一个非常灵活的数据结构, 在范围搜索问题上有很多应用.
阅读(1186) | 评论(0) | 转发(0) |
0

上一篇:Junk-Mail Filter

下一篇:简单背包 硬币分堆

给主人留下些什么吧!~~