圆内检测算法中的圆优化低于O(n²)

时间:2012-10-18 09:33:42

标签: java algorithm optimization

我正在尝试执行一个带圆圈列表的函数,并仅返回完全重叠的圆圈列表(一个在另一个内部)。问题是该算法至少 O(n²),这是由于getConcentricCircles函数中的嵌套for,以及大数据集的年龄。有没有办法优化它?

编辑:我不知道这是否有帮助,但我使用该算法来检测虹膜和瞳孔检测中的假阳性。如果圆圈完全位于另一个圆圈内,则可能是瞳孔,外部是虹膜。它们应该是同心的,简化了很多,但是人眼中的瞳孔并不完全位于虹膜的中心,这就是我这样做的原因。

编辑2:我已经用Peter Lawrey的解决方案替换了isCircleInCircle,我的某些情况不正确

检查圆圈是否在圆圈内的功能:

private static boolean isCircleInCircle(Circle a, Circle b) {
    // the circle is inside if the distance between the centre is less than the difference in the radius
    double dx = a.getX() - b.getX();
    double dy = a.getY() - b.getY();
    double radiusDifference = a.getRadius() - b.getRadius();
    double centreDistanceSquared = dx*dx + dy*dy; // edited
    return radiusDifference * radiusDifference > centreDistanceSquared;
}

然后我互相检查列表中的每个元素,并只保存重叠的圆圈(和重叠的圆圈):

public HashSet<Circle> getConcentricCircles(List<Circle> circleList) {
    HashSet<Circle> toReturn = new HashSet<Circle>();

    for (Circle circle : circleList) {
        for (Circle toCheck : circleList) {
            // if the circles are not the same and one is inside another,
            if (!toCheck.equals(circle) && isCircleInCircle(circle, toCheck)) {
                // add both to the hashset
                toReturn.add(circle);
                toReturn.add(toCheck);
            }
        }
    }
    return toReturn;
}

6 个答案:

答案 0 :(得分:2)

我的第一印象是看到一个圆圈是否在另一个圈子里面就是知道

  1. 两个圆圈的中心点。
  2. 圆圈的两个半径。
  3. 如果C1至C2 + R2> R1然后在外面,否则它在里面。
  4. 这应该会大大简化你的逻辑。

    编辑:提高复杂性,

    1. 按半径排序(从大到小)
    2. 首先for循环从大到小
    3. 第二个for循环从大到小
    4. 一旦在外部找到了一个内圈,就可以从外圈中删除这个圆圈
    5. 原因是因为第一个外圆圈封装了这个内圈,你不在乎是否有其他任何东西落入这个圆圈,只有当它在随后的较大的圆圈之外。
    6. 这将获得一个被更大圆圈包围的圆圈列表。

答案 1 :(得分:1)

您的数据集是什么样的?检查每个圆圈与每个其他圆圈本质上是O(n ^ 2),为了降低复杂性,您需要一些指标来防止必须相互检查每个圆圈。

根据圆的分布,有各种广义算法可能会有所帮助。例如,如果圆圈占据的空间远大于典型半径,并且圆圈相对均匀地分布在该空间中,spatial partitioning using a quadtree可以帮助最小化检查彼此远离的对象之间的包含。

答案 2 :(得分:1)

此算法的复杂性无法降低到O(n^2)以下。想象一个规则网格,其点是圆的中心,圆的半径是1,相邻网格点之间的距离是2。任何其他圆圈中都不包含圆圈。为了证明这一点,你必须检查每一个圆圈。如果您不能证明每个组合,那么就存在圈ab,它们没有相互测试。所以现在让矩阵看起来有点不同:圆ab小一点,它们共享同一个中心。因此,您没有发现a中包含b,因此您的算法不正确。非常适合复杂性的证明。

为了帮助加快您的计划,您必须专注于平均情况: 这意味着小圆圈包含在较大圆圈中。构建有向图,其节点表示圆,其边表示源圆包含目标圆。从半径最大的圆开始。使用深度优先搜索构建图形。如果您知道圈子a包含在另一个圈子中。然后尝试找到b中包含的圈a。如果存在b,请先继续b。如果b中没有更多内容,请退后一步,继续使用尚未包含在另一个找到的圈子中的所有圈子。在最好的情况下,这会给您带来O(nlog(n))的复杂性。这是由于在搜索包含的节点时管理剩余的节点以及按半径排序。这里最好的情况是所有圆圈都具有相同的中心和不同的半径。

修改
answer of Aki让我想起了另一种加快速度的方法。在一般情况下,圆圈将形成群集,其中一个圆圈部分地与其他圆圈重叠。所以你可以先计算一个依赖集的分区(不,我不是指独立集,因为这将是NP - 很难)。这减少了上述算法必须使用的数据大小。

然后,在寻找可能完全重叠的候选人时,还有另一种改进。由于圆圈包含在一个平面中,因此可以使用像R树或四叉树这样的空间数据结构来找到候选者,这些候选者可能完全重叠,效率更高。

但是,我不认为这会降低最坏情况的复杂性,即使这些建议也会改善上述最坏情况下的性能。新的最坏情况可能是圆形,其中心是规则网格的点,但是当将其与常规网格中的点之间的距离进行比较时,其半径非常大。

答案 3 :(得分:0)

虽然不是您问题的答案,但您可以使用以下内容更快地进行检查。

private static boolean isCircleInCircle(Circle a, Circle b) {
    // the circle is inside if the distance between the centre is less than the difference in the radius
    double dx = a.getX() - b.getX();
    double dy = a.getY() - b.getY();
    double radiusDifference = a.getRadius() - b.getRadius();
    // double centreDistance = Math.sqrt(dx*dx + dy+dy);
    // return radiusDifference > centreDistance;
    double centreDistanceSquared = dx*dx + dy+dy;
    return radiusDifference * radiusDifference > centreDistanceSquared;
}

private static boolean isPointInCircle(Point center, int outsideRadius, Point toCheck) {
    // distance between two points is sqrt((x1-x2)²+(y1-y2)²)
    double dx = center.getX() - toCheck.getX();
    double dy = center.getX() - toCheck.getY();
    double distSquared = dx * dx + dy * dy;
    // if the distance is less than the radius, then the point is inside
    return distSquared < outsideRadius * outsideRadius;
}

注意:第一种方法不再需要第二种方法。

答案 4 :(得分:0)

如果圈子的分布允许,可以根据圈子的位置将圈子分成几个或多个圈子 - 然后将测试限制在相同或附近的位置。

事实证明,问题是眼睛数量足够少的实时眼睛检测,复杂性不会成为问题。人们可以很容易地在RT中每帧花费10M浮点运算,这表明数据集<1000圈(使用优化的内环)。

优化的内环将计算:

(x0-x1)^2 + (y0-y1)^2 < (r0-r1)^2

对于每对圆圈,检查是否完全包含另一对。 该公式非常适合并行性,并且可以通过增加通过上述测试的每个圆的计数器来排除奇怪的情况。最后应该是1。

答案 5 :(得分:0)

两点:

  1. 您的算法不正确。考虑此图中的圆圈:

    Small circle overlapping but not contained by large circle

    小圆圈的四个罗盘点位于较大的圆圈内,但不包含在其中。这个问题可以通过Alan和Peter描述的更好的圆圈测试来解决。

  2. 您的问题描述并不完全清楚。输出应该是:

    1. 第一个包含在第二个圆圈中的每对圆圈的列表。
    2. 每个圆圈的列表,其中至少有一个圆圈完全重叠。
    3. 与其他圈子的某些组合完全重叠的每个圈子的列表。
    4. 其中第一个显然没有比O(N ^ 2)更好的最坏情况运行时间,因为所有圆圈可能是同心排列的,所以第一个圆圈包含所有其他圆圈,第二个圆圈包含所有其他圆圈除了首先,等等,因为输出是(N ^ 2),运行时间不能再好了。

      第二个可能有更好的最坏情况运行时间,但我对此并不太自信。

      第三听起来像是一场噩梦。

      如果您可以保证重叠量较低,那么您可以通过创建每个圆圈的最小(最左侧)和最大(最右侧)x位置的列表并对其进行排序来获得更好的运行时间。完成列表工作,在遇到左边缘时将圆圈添加到工作集中,并在遇到右边缘时将其删除。当每个圆圈添加到集合中时,只将其与当前集合中的其他圆圈进行比较。

      这仍然是最坏情况的O(n ^ 2),除非你能够对圆的分布和大小做出足够的保证,但它应该是一个很大的进步。