在二维数组的矩形区域内找到最大值的快速方法

Posted

技术标签:

【中文标题】在二维数组的矩形区域内找到最大值的快速方法【英文标题】:Fast way to find the maximum value within a rectangle region of a 2d array 【发布时间】:2016-08-04 05:23:55 【问题描述】:

我有一个二维深度值数组,需要一种快速方法来找到给定矩形区域内的最大值。许多矩形将针对给定的深度缓冲区进行测试,因此可以接受合理的预处理步骤。

简单的方法是扫描矩形中的每个像素,跟踪最大值,需要宽度 * 高度迭代。

通过首先创建深度缓冲区的四叉树,其中每个父节点包含其子节点的最大值,可以将复杂性降低到大约宽度 + 高度迭代。这种方法很好,但我想知道它是否可以做得更快。

我已经给出了一个通过使用线性时间预处理here来查找平均值而不是恒定时间内的最大值的方法示例。

有人知道找到最大值的类似技术吗?

【问题讨论】:

矩形的大小是可变的吗? 最大操作和加法有一个主要区别:最大是不可逆的。取 max(a, b) 后,两个数中的一个将永远丢失。这与 a+b 形成对比,因此您可以在知道 a 的情况下检索 b。因此,不能使用“整体图像”技巧。 可能"Two-Dimensional Range Minimum Queries" 就是你要找的。​​span> 【参考方案1】:

是的,您可以概括平均的技巧,但仅限于较小的颜色深度,例如 8 位深度 (0-255)。假设您有 k 种颜色(或不同的深度值)。

供您参考,这里有一个很好的解释,通过积分图像Viola/Jones CVPR 2001计算矩形的均值,请参阅第 2.1 节。

我的通用算法是预先计算维度为 k 的向量的积分,颜色/深度值多久出现一次。从此向量中,您可以采用与技巧相同的差异来计算平均值。这不仅为您提供了矩形区域内的最大值,而且还为您提供了在恒定时间内该矩形内的直方图的向量。当然,直方图允许您提取最大值(或最小值,或其他分位数)。

时间和内存需求当然随着颜色的数量而增长,我认为复杂度等级是 O(k) 用于查找和 O(k * width * height) 用于预计算。

(如果我的想法以前被使用过,我会很感兴趣。)

【讨论】:

我喜欢这个主意。如果 OP 将简单地使用最终结果进行 <> 比较,那么您可以通过将向量的第 i 个元素设置为深度等于 的元素数来获得 O(1) 查找时间或小于 i;这也不应该改变预处理时间。 其实应该是“深度等于或大于 i”。 @j_random_hacker 如何从预先计算的向量中获得矩形内的最大值? 实际上,我现在意识到我的直方图向量变体将允许通过二分搜索在 O(log k) 时间内找到最大值,因为 v 中的值不会增加! :) 首先设置 L = 0 和 U = 255。现在设置 i = (L+U)/2,并使用积分图像技巧在 O(1) 时间内为查询矩形计算 v[i]:如果 v[ i] > 0,i太低,设L = i,重复;否则,如果 v[i] = 0,则 i (或可能)太高,因此设置 U = i 并重复。当 U-L = 这个深度,所以返回 i-1。 [修复了较早的评论] 确定最大值仍需要 O(k) 时间:您首先在 O(k) 时间内计算查询的直方图向量 v[0 .. 255]矩形,然后寻找最小的 i 使得 v[i+1] = 0。更快的是如果我们已经有一个测试深度 x,我们想知道查询矩形中是否有任何深度 > x:在这种情况下,我们只需要计算直方图向量的第 x 个元素(即 v[x])并测试它是否 > 0。【参考方案2】:

通过添加零将数组填充到 2 的平方幂。然后将其金字塔化成一个堆栈,每次在每个维度上缩小 2 倍。在每个级别,每个元素的值等于金字塔中下一个最大数组中 4 个对应元素的最大值。这将在填充的数组大小之上额外占用 1/3 的存储空间,就像 mip-maps 一样。

编写一个递归函数,它接受给定数组级别的单个元素,并检查查询区域是否覆盖了它所覆盖的边界。如果不是,则检查与查询区域重叠的下一个最大数组中的每个区域,返回每个区域的递归计算最大值。

伪代码:

 int getMax(Rect query_rect, int level, int level_x, int level_y)
 
      int level_factor = 1 << level;
      // compute the area covered by this array element:
      Rect level_rect(level_x * level_factor, level_y * level_factor,
          (level_x + 1) * level_factor, (level_y + 1) * level_factor);

      // if the regions don't overlap then ignore:
      if(!query_rect.intersects(level_rect))
          return -1;

      // query rect entirely contains this region, return precomputed max:
      if(query_rect.contains(level_rect))
          return pyramid[level][level_x][level_y];

      if(level == 0)
          return -1;  // ran out of levels

      int max = getMax(query_rect, level - 1, level_x * 2, level_y * 2);
      max = maxValue(max, getMax(query_rect, level - 1, (level_x * 2) + 1, level_y * 2);
      max = maxValue(max, getMax(query_rect, level - 1, level_x * 2, (level_y * 2) + 1);
      max = maxValue(max, getMax(query_rect, level - 1, (level_x * 2) + 1, (level_y * 2) + 1);

      return max;
 

最初在顶层(金字塔中的顶点 1x1 数组)调用它:

 int max = getMax(query_rect, 10, 0, 0);

查询矩形会有一个最小尺寸,低于这个尺寸,仅遍历所有元素会更便宜。您可以调整它以使用非方形数组,它只需要在递归时对每个级别的数组大小进行一些额外的测试。

【讨论】:

以上是关于在二维数组的矩形区域内找到最大值的快速方法的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode﹝前缀和ி﹞一维二维前缀和应用

在c ++中找到二维数组中的最大区域

给定 0 和 1 的二维数组,使用回溯找到其中的所有正方形

bzoj3132 上帝造题的七分钟(差分+二维树状数组)

在二维数组中查找最大值

将二维数组转换为列表(一维)的快速方法