【问题标题】:Optimize circle inside circle detection algorithm lower than O(n²)优化低于 O(n²) 的圆内圆检测算法
【发布时间】:2012-10-18 09:33:42
【问题描述】:

我正在尝试做一个函数,它接受一个圆圈列表,并且只返回一个完全重叠的圆圈列表(一个在另一个里面)。问题在于该算法至少为 O(n²),这是由于 getConcentricCircles 函数中嵌套了 for,并且对于大型数据集需要很长时间。有什么办法可以优化吗?

编辑:我不知道这是否有帮助,但我使用该算法来检测虹膜和瞳孔检测中的误报。如果一个圆圈完全在另一个圆圈内,那很可能是瞳孔,而外面是虹膜。它们应该是同心的,这会简化很多,但是人眼中的瞳孔恰好不在虹膜的中心,这就是我这样做的原因。

编辑 2:我已将 isCircleInCircle 替换为 Peter Lawrey 的解决方案,在某些情况下我的解决方案不正确

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

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;
}

【问题讨论】:

  • @PeterLawrey 你完全正确,我只是逐字翻译了公式。但是,我不知道它可能是乘数的 10 倍!
  • 要执行 pow,它会有效地执行 Math.exp(math.log(x) * 2)
  • 这并没有提高 O(n^2) 复杂度。
  • 如果c1在c2里面,也在c1里面,但是c1和c2重叠,算法应该返回什么?

标签: java algorithm optimization


【解决方案1】:

我看到一个圆圈是否在另一个圆圈内的第一印象是知道

  1. 两个圆的中心点。
  2. 圆的两个半径。
  3. 如果 C1 到 C2 + R2 > R1 则为外部,否则为内部。

这应该会大大简化您的逻辑。

编辑:提高复杂度,

  1. 按半径排序(从大到小)
  2. 第一个 for 循环从大到小
  3. 第二个for循环从大到小
  4. 一旦您在外圈中找到内圈,您就可以从外圈中移除该圈
  5. 原因是因为第一个外圈封装了这个内圈,你不关心是否有其他东西落在这个圈子里,只要它随后在更大的那个之外。

这将得到你的圈子列表,周围有一个更大的圆圈。

【讨论】:

  • 经过这个小优化后,算法还是O(n^2)
  • 谢谢!这确实简化了事情
  • 仍然是 O(n^2),但有一个(稍微)更小的常数倍数。
  • 在我最后一次编辑之后,应该会稍微降低复杂性/时间。
【解决方案2】:

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

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

编辑:
answer of Aki 让我想起了另一种加快速度的方法。在一般情况下,圆圈会形成簇,其中一个圆圈与其他圆圈部分重叠。因此,您可以首先计算依赖集的分区(不,我不是指独立集,因为这将是NP-hard)。这减少了上述算法必须使用的数据量。

在寻找可能完全重叠的候选人时,还有另一个改进可能。由于圆包含在一个平面中,因此可以使用 R-trees 或 quadtrees 等空间数据结构来查找候选对象,它们可以完全重叠,效率更高。

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

【讨论】:

  • 基于第一种方法的数据结构的名称是四叉树 - en.wikipedia.org/wiki/Quadtree
  • 我在想一棵 R 树。但是四叉树也是合适的。
【解决方案3】:

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

根据圆圈的分布情况,有多种宽相位算法可能会有所帮助。例如,如果圆圈占据的空间比典型半径大得多,并且圆圈在该空间中分布相对均匀,spatial partitioning using a quadtree 可以帮助最大限度地减少检查彼此远离的对象之间的包容性。

【讨论】:

  • +1 是的,我也正要建议四叉树...即使...搜索四叉树可能复杂度为 log_n,因此取决于分布
  • 这不是知道哪个候选内圈属于哪个候选外圈的情况吗?如果 n 始终为 1 或 2,则 O(n^2) 无关紧要。或者您是否在大图像中有大量眼睛的图片,并且您正在尝试提取虹膜/瞳孔对?在这种情况下,空间划分可能非常有用,但简单的网格可能比四叉树更好。
【解决方案4】:

虽然不是您问题的答案,但您可以使用以下方法加快检查速度。

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;
}

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

【讨论】:

  • 不过,优化很棒,现在我看到我的算法有多糟糕!
  • 仍然是 O(n^2)。为了减少这种情况,您可以在搜索所有接触相同网格的圆圈时使用网格。这提高了平均值,但最坏的情况仍然是 O(n^2)
【解决方案5】:

两点:

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

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

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

    1. 第一个包含在第二个中的每对圆的列表。
    2. 包含至少一个完全重叠的每个圆圈的列表。
    3. 与其他圆圈的某种组合完全重叠的每个圆圈的列表。

    其中第一个显然没有比 O(N^2) 更好的最坏情况运行时间,因为所有的圆都可能同心排列,所以第一个圆包含彼此,第二个包含除首先,等等,由于输出是 (N^2),所以运行时间再好不过了。

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

    第三个听起来像是要解决的噩梦。

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

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

【讨论】:

  • 你是对的,没有看到这种情况发生......正如你在我的编辑中看到的那样,我希望输出编号为 2
【解决方案6】:

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

事实证明,问题在于眼睛数量足够少的实时眼睛检测,复杂性不会成为问题。可以轻松地在 RT 中每帧花费 10M 次浮点运算,这表明数据集小于 1000 个圆(带有优化的内循环)。

优化的内循环会计算:

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

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

【讨论】:

    猜你喜欢
    • 2019-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 2012-04-12
    • 2020-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多