有效找到高密度区域的最佳方法

时间:2013-10-23 05:20:56

标签: algorithm search math optimization

在编码过程中,我遇到了以下问题: 在具有最高粒子密度的2D空间中找到固定大小的区域。可以认为粒子通常在整个空间上随机分布,但理论上应该有一些区域具有更高的密度。

例如,100个粒子随机放置在500x500的2D网格中,我需要找到粒度最大(密度最高)的50x50区域。

除了蛮力测试每个可能的区域(在这种情况下,超过200000个区域)之外,还有其他方法来计算最佳区域吗?对于n长轴,这将在O(n ^ 2)处扩大。

3 个答案:

答案 0 :(得分:8)

算法1

创建500x500 2D阵列,其中每个单元格包含该单元格中粒子数量的计数。然后将该阵列与50x50内核进行卷积,得到的数组将在每个单元格中的50x50区域中具有粒子数。然后找到具有最大值的单元格。

如果您使用50x50的盒子作为区域,内核可以分解为两个单独的卷积,每个卷一个。生成的算法是O(n ^ 2)空间和时间,其中n是您要搜索的2D空间的宽度和高度。

作为提醒,具有boxcar功能的一维卷积可以在O(n)时间和空间中完成,并且可以在适当的位置完成。令x(t)为t = 1..n的输入,并让y(t)为输出。对于t< 1和t> n,定义x(t)= 0和y(t)= 0。将内核f(t)定义为0为0..d-1为1,其他地方为0。卷积的定义给出了以下公式:

  

y(t)= sum i x(t-i)* f(i)= sum i = 0..d-1 x(t-i)

这看起来需要时间O(n * d),但我们可以将其重写为重复:

  

y(t)= y(t-1)+ x(t) - x(t-d)

这表明一维卷积是O(n),与d无关。要执行二维卷积,只需对每个轴执行一维卷积。这是因为可以分解boxcar内核:通常,大多数内核都不能被分解。高斯内核是另一个可以分解的内核,这就是图像编辑程序中高斯模糊如此之快的原因。

对于您指定的数字类型,这将非常快。 500x500是一个非常小的数据集,您的计算机最多可以在几毫秒内检查202,500个区域。您将不得不问自己,是否值得花费额外的时间,数天或数周的时间来进一步优化。

这与justhalf的解决方案相同,除了由于分解的卷积,区域大小不会影响算法的速度。

算法2

假设至少有一点。不失一般性,将2D空间视为整个平面。设 d 为区域的宽度和高度。设N为点数。

引理:存在一个最大密度的区域,其左边有一个点。

证明:设R是最大密度的区域。设R'是相同的区域,右边是R的左边缘和R的最左边点之间的距离.R中的所有点也必须位于R'中,因此R'也是最大密度的区域。

算法

  1. 将所有点插入K-D树。这可以在O(N log 2 N)时间内完成。

  2. 对于每个点,请考虑宽度 d 和高度2 d 的区域,其中点位于区域的左边缘的中心。称这个地区为R。

  3. 在KD树中查询区域R中的点。调用此集合S.这可以在O(N 1/2 + | S |)时间内完成。

  4. 找到包含S中最大点数的R的dxd子区域。这可以在O(| S | log | S |)时间内通过按y坐标排序S然后执行线性扫描来完成

  5. 结果算法的时间为O(N 3/2 + N | S | log | S |)。

    比较

    当密度高时,算法#1优于算法#2。算法#2仅在粒子密度非常低时才具有优势,而算法#2优越的密度随着总板尺寸的增加而降低。

    请注意,连续情况可以被认为是零密度,此时只有算法#2有效。

答案 1 :(得分:1)

我不知道你使用什么蛮力方法,但最强力的方法是O(n^2 d^2),通过在O(n^2)时间迭代每个区域,然后计算那里的粒子数量O(d^2)时区d是您所在地区的大小。

此问题与此问题完全相同:Rat Attack,因为区域区域是固定的,因此密度与计数相同, 解决方案是O(n^2 + k*d^2),其中

  1. n是整个区域的大小(边长)
  2. k是粒子数
  3. d是每个区域的大小(边长)
  4. 通过这个算法:

    1. 对于每个粒子,更新受此粒子影响的O(d^2)区域的计数
    2. 遍历所有O(n^2)个可能的区域,找到最大值
    3. this code所示,我在此复制相关部分供您参考:

      using namespace std;
      
      int mat [1024 + 3] [1024 + 3]; // Here n is assumed to be 1024
      
      int main ()
      {
          int testCases; scanf ("%d", &testCases);
      
          while ( testCases-- ) {
      
              Set(mat, 0);
      
              int d; scanf ("%d", &d); // d is the size of the region
              int k; scanf ("%d", &k); // k is the number of particles
      
              int x, y, cost;
      
              for ( int i = 0; i < k; i++ ) {
                  scanf ("%d %d %d", &x, &y, &cost); // Read each particle position
      
                  // Update the count of the d^2 region affected by this particle
                  for ( int j = max (0, x - d); j <= min (x + d, 1024); j++ ) {
                      for ( int k = max (0, y - d); k <= min (y + d, 1024); k++ ) mat [j] [k] += cost;
                  }
              }
      
              int resX, resY, maxi = -1;
      
              // Find the maximum count over all regions
              for ( int i = 0; i < 1025; i++ ) {
                  for ( int j = 0; j < 1025; j++ ) {
                      if ( maxi < mat [i] [j] ) {
                          maxi = mat [i] [j];
                          resX = i;
                          resY = j;
                      }
                  }
              }
      
              printf ("%d %d %d\n", resX, resY, maxi);
      
          }
          return 0;
      }
      

      我已将我的评论放在代码中向您解释。

答案 2 :(得分:0)

将区域划分为1000x1000并计算每个(重叠)2x2中的粒子数。您可以通过标准化0..1,缩放0..999,并转换为整数来对它们进行分区。计数可以很容易地存储为整数的二维数组(ushort,uint或ulong ... mmmm茶)。这相当于在宽相位碰撞检测中使用的简单2D空间划分。