最高效的寻找高密度区域的方法

20
在我的编码过程中,我遇到了以下问题: 在二维空间中找到一个固定大小的区域,该区域具有最高密度的粒子。 粒子可以被认为是随机分布在整个空间中,但理论上应该有一些区域具有更高的密度。
例如,在500x500的2D网格中随机放置100个粒子,我需要找到最多粒子(最高密度)的50x50区域。
除了对每个可能的区域进行蛮力测试(在这种情况下大约超过200000个区域),是否有其他计算最佳区域的方法? 这会在n长度轴上扩展到O(n ^ 2)。

@MillieSmith:这是个好主意,但是没有一个查询数量的概念,它就不是很有用。 - Dietrich Epp
这与问题 http://poj.org/problem?id=1916 完全相同,最佳解决方案为 O(N^2 + K*D^2),其中 K 是粒子数(在您的情况下为100),D 是区域大小(在您的情况下为50),如此 C++ 代码所示:http://tausiq.wordpress.com/2012/08/24/uva-10360-rat-attack/。 - justhalf
3个回答

10

算法1

创建一个500x500的二维数组,每个单元格包含该单元格中粒子数量的计数。然后将该数组与50x50的卷积核卷积,结果数组将在每个单元格中具有50x50区域中粒子的计数。然后找到具有最大值的单元格。

如果您使用50x50的框作为区域,则卷积可以分解为两个独立的卷积,一个用于每个轴。得到的算法是O(n^2)的空间和时间复杂度,其中n是您要搜索的二维空间的宽度和高度。

提醒一下,使用箱式函数进行一维卷积可以在O(n)的时间和空间内完成,并且可以原地完成。让x(t)为t=1..n的输入,y(t)为输出。对于t<1和t>n,定义x(t)=0和y(t)=0。将核f(t)定义为0之外的1,0..d-1为1。卷积的定义给出了以下公式:

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无关。要执行二维卷积,只需为每个轴执行一维卷积。这有效是因为箱式核可以分解:通常,大多数核无法分解。高斯核是另一个可以分解的核,这就是图像编辑程序中高斯模糊如此快的原因。

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

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

算法2

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

引理:存在具有其左边缘上的点的最大密度区域。

证明:假设R是最大密度区域。令R'为相同的区域,向右平移R左边缘和R中最左边点之间的距离。所有在R中的点也必须在R'中,因此R'也是最大密度区域。

算法

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

  2. 对于每个点,考虑宽度为d,高度为2d的区域,在该区域上以点为中心在左侧边缘。称此区域为R。

  3. 查询区域R中的点集合S。这可以在O(N1/2+|S|)时间内完成。

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

得到的算法的时间复杂度为O(N3/2 + N |S| log |S|)。

比较

当密度很高时,算法#1优于算法#2。只有当粒子密度非常低且总棋盘大小增加时算法#2才优于算法#1,此时算法#2优于算法#1的密度下降。

请注意,连续情况可以视为具有零密度,在这种情况下只有算法#2适用。


我不确定您所说的重叠区域是什么意思...我们只在寻找一个区域,共有202,500个可能的检查。 - Dietrich Epp
@MillieSmith:性能和优化很重要,但现在还为时过早。毕竟,优化的第一条规则是“不要优化”。 - Dietrich Epp
@MillieSmith:我不是在替魔鬼辩护。我只是在评论不完整的解决方案。如果代码不能解决问题,速度再快也没有用。 - Dietrich Epp
@MillieSmith:你似乎在玩“以人攻击”的游戏,这可能是由于DietrichEpp在问题评论中批评了你的想法。 - justhalf
1
@j_random_hacker:分解盒卷积每个单元格的摊销常数时间。没有依赖于d,只有对维数数量的线性依赖。在一维情况下,给定输入x(1)..x(n),输出y(t)的公式可以表示为y(t)= sum i =0..d-1 x(i + t),这确实是O(n*d)。但是,这相当于递归关系y(t)= y(t-1)+ x(t+d-1)- x(t-1),其是O(n)。这个快捷方式是离散输入使用Boxcar函数进行卷积如此高效的原因。 - Dietrich Epp
显示剩余5条评论

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是每个区域的大小(边长)
按照以下算法:
  1. 对于每个粒子,更新受该粒子影响的O(d^2)区域的计数
  2. 迭代所有可能的O(n^2)区域,找到最大值
此代码所示,我在此复制相关部分供您参考:
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;
}

我已经在代码中加入了注释,以便向您解释。

通过轻微的修改,可以实现O(n^2 + k)的运行时间,因为可以使用分解盒卷积在O(n^2)的时间内一次性更新每个d*d大小的区域。 - Dietrich Epp
哇,你似乎比我知道的还要多。你能描述一下什么是分解盒卷积吗?并且在你的回答中,也能提供一些关于K-D树的链接吗? - justhalf
卷积是一种处理过程,其中您将每个单元格的内容添加到相邻单元格中(当您将其添加到相邻单元格时,还可以按常数缩放该值)。您同时对所有单元格执行此操作。核是您影响的相邻单元格集和每个单元格的系数。框函数是一个核,它只是一个矩形,在内部的任何地方都具有恒定值。框函数可以分解为两个一维卷积(高斯核也具有此属性),这使得计算卷积非常快速。 - Dietrich Epp
高斯模糊是卷积的另一个例子,使用高斯核进行卷积。 - Dietrich Epp

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接