【问题标题】:Bounding circle of set of circles一组圆的边界圆
【发布时间】:2011-10-22 00:17:25
【问题描述】:

我正在尝试在 Java 中实现以下内容。

给定一个不同大小(可能)和位置的圆的列表,确定一个正好包含所有圆的大圆(位置和大小)。

public class Circle {
    public int x, y, radius;
}

有什么想法吗?

【问题讨论】:

  • 除非是功课,否则这真的与Java无关。
  • 嗯...好吧,第一个简单的解决方案——这不是最佳的,但是嘿,从某个地方开始很有趣——可能是:将每个圆圈视为一个正方形,得到 xL,xR, yT,yB... 获取包含它们的边界矩形。绘制一个包含该矩形的圆(以矩形为中心的圆,直径等于矩形的对角线)。 (但不,它并不多是Java......)我很想看看最佳解决方案是什么。还在琢磨……
  • 我会循环遍历圆圈,看看最大的顶部、左侧、右侧和底部坐标是多少。这会给你一个矩形来覆盖。在矩形的重心处绘制一个半径与到其中一个角的距离相匹配的圆应该可以解决问题。不过,这种推理可能存在缺陷。
  • 这是一个有趣的问题,但它确实与Java无关。
  • 这绝对不是 Java 特定的...除非您想要人们为您编写代码。 - 这不是重复的@trashgod,只是密切相关。

标签: algorithm geometry


【解决方案1】:

你可以找到最大边界(xmin,xmax,ymin,ymax),然后在两个轴上取最大值,然后让 java 在那个正方形上画一个椭圆,或者取它的中心和边作为直径。

没有?

问候, 斯蒂芬

【讨论】:

  • (xmin - dedicated_radius, xmax + c_radius) (ymin/max +- c_radius) 可能是一个好的开始,但还不够。想象一个 8x8 矩阵,半径为 1 的圆位于 (0,-4)、(0, 4)、(-4,0)、(4,0)。在 (0,0) 周围,半径为 5 的大圆圈将覆盖它们,但在 (4,4) 处,半径为 1 的另一个圆圈不会改变 min/max (x/y),但大圆圈不会覆盖它。
【解决方案2】:

有两个圆圈很容易。一条穿过两个中心的线将到达一个包围圈与它们接触的周边。

对于更多的圆圈,您需要在每个周边点上应用 FEM(有限元分析-http://en.wikipedia.org/wiki/Finite_element_method),并有可能成为与外圈的接触点。例如,这排除了那些面向其他圆圈的部分。当您继续对您的点应用不同的半径时,计算仍然相当大,直到您找到与所有其他点相交的最小半径 - 您的封闭圆的中心。

【讨论】:

  • 正如 JRL 所说,一个包装问题,您将需要某种数值分析 (NA),因为我认为不存在降低复杂性的公式。 FEM 可能效果最好,但可以说明 NA 的技术。
  • 我在纸上涂鸦了一点,我也认为需要 NA。更糟糕的是,您的最小化问题也有局部最小值,因此您还需要一些算法来首先找出使您达到全局最小值的起点。祝你好运。 (我想看看有更好解决方案的人。)
  • 我认为有限元方法不能胜任这项工作。它们用于求解微分方程,但情况并非如此。
  • @Tomas 这是一个带有约束的优化问题,您必须找到最佳圆的位置及其半径。当你写这个优化问题时,你会得到一些微分方程。
  • @toto 是的,这是有约束的优化问题,但不会导致微分方程。它更像极小极大问题,更像二次规划。
【解决方案3】:

糟糕,以下内容不起作用,如 cmets 中所述:

首先解决 3 个圆圈的问题。在这种情况下,外接圆将接触三个内圆中的每一个,您可以找到它的中心作为两条双曲线的交点。 (到两个固定点的距离具有给定差异的点的轨迹是双曲线)。经过一点代数,这归结为一个二次方程。

现在通过归纳添加更多内圈。在每一步开始时,您都知道包含所有圆的最小圆;它将触及三个特定的旧“角”圈。如果新圈子在那个圈子之内,则无事可做。如果不是,则将新圆与所有三种方法结合以选择两个旧角圆并计算每个三元组的外接圆。其中一个应该包括第四个,现在它不再是一个角圈了。

继续操作,直到添加完所有圈子。

这给出了一个有界舍入误差的线性时间算法(因为每个外接圆都是从原始输入坐标重新计算的)。

【讨论】:

  • 我非常喜欢这个。不过,在某些(很多?)情况下,最好的圈子只触及两个内圈。例如,连续三个或几乎连续三个。
  • 嗯,你是对的。事实上,这破坏了整个方案,因为在您添加一个非常远的新圆之后,新的外接圆可能会接触到新的外接圆甚至不在一个的内圆之前的角落。回到绘图板...
  • 我想知道您是否可以保留“最佳 3”...但最后可能会将其减少到 2。在这里在黑暗中拍摄。 :)
  • 恐怕整个“最佳 3”的东西是无法挽救的。例如,假设我们有四个以 A(0,1)、B(1,0)、C(10,0)、D(0,10) 和 E(10,10) 为中心的半径为 0 的圆。然后外接圆接触到 C、D 和 E。但是如果我们加上 F(1000, 1000),那么突然我们的角点是 A、B 和 F,与前面的点没有一个共同点。
【解决方案4】:

我认为这可以分三步完成:

  • 第一个边界圆c1

    • 中心由 xc1 = (xmax + x 确定分钟) / 2 和 yc1 = (ymax + ymin em>) / 2.
    • 对于每个圆,计算其中心到c1 中心的距离加上它的半径(我称之为超距离)。 这些值的最大值是c1 的半径。对应的圆圈是a
  • 第二个边界圆c2: (在此步骤中,您将 c1 的中心尽可能向 a 方向移动。)

    • 对于除 a 之外的每个圆,确定您必须将 c1 的中心向 a 方向移动多少,以使- 从那里到这个圆圈的距离与到 a 的距离相同。其中的最小值决定了 c2 的中心。对应的圆圈是b。到ab的超距离(两者相同)就是c2的半径。
  • 第三个边界圆c3: (在此步骤中,您将 c2 的中心向 ab 之间的方向移动尽可能远。)

    • 确定您可以移动 c2 的方向 v,使得到 ab 的超距离> 保持不变。
    • 对于除 ab 之外的每个圆,确定您必须将 c2 的中心向 v 使得从那里到这个圆的超距离与到 ab 的距离相同。这个最小值决定了 c3 的中心。半径是找到的三个圆的超距离。

我相信 c3解决方案 (edit) 一个很好的初步近似。您可以通过迭代删除第一个圆圈并重复第三步来获得更好的解决方案。如果你到达一组你已经看到的三个圆圈,这应该可能是最终的解决方案。

【讨论】:

  • 好吧,“你相信” :) 问题在于圆 a 的选择,它基于一些“矩形逻辑”而不是适当的优化算法......但是,你的算法可能是一个微不足道的近似(不是解决方案)值得一试。
  • 不!您的编辑错误!这样一来,您仍然不能追求任何比近似值更多的东西。这是一个复杂的问题(见我的回答)。有一组 2D 可能的解决方案,但您只是在一些 i-think-quite-good zig-zag 线上搜索最佳解决方案。在设计算法时,您应该证明您的解决方案是唯一的。
  • @Tomas Telensky:“可能”的哪一部分让您感到困惑?
  • 我没有任何困惑,我只是说您的算法只是一个近似值,甚至不要考虑以这种方式达到的最佳解决方案。 “可能” .... 在非常特殊的情况下只是偶然。
【解决方案5】:

这是一个非常困难的问题,我只是概述了可能的方法,你必须自己完成它。我假设您想找到最小边界圆。 将您的问题形式化 - 对于 i = 1..N 有 xi, yi, ri,您正在寻找点 [x, y] 使得:

max(distance([x, y], [xi, yi]) + ri)

是最小的。这是一个非平凡的极小极大问题。先看一下这个问题的简单版Smallest circle problem,只是为了分:

max(distance([x, y], [xi, yi]))

所以首先尝试改进上面链接中提出的算法来解决更复杂的情况。如果这种方式无法通过,您可能需要选择 quadratic programming。祝你好运...

【讨论】:

    【解决方案6】:

    我建议的算法与 Svante 的算法相似,但有一些不同。

    这个想法是首先创建一个包含所有子圆的圆,然后像气泡一样缩小它,直到它被 1,2 或 3 个圆固定。

    第一个近似值:

    圆心为 0,0 且半径为 max(距子圆 0,0 的距离 + 子圆的半径)

    如果决定圆半径的子圆包围了所有其他子圆,那么它通常是正确的结果,并且可以返回

    第二个近似值:

    减小在第一次近似中找到的圆的半径,同时保持它与它被“固定”到的子圆相切,将中心移向固定的子圆,直到它与另一个子圆相切。

    最终结果:

    再次减小圆的半径,使其与上面找到的两个子圆相切,直到另一个子圆与圆相切,或者圆的中心位于两个子圆的中心之间的线上固定到。这应该是最小值,因为如果没有一个子圆“突破”,就无法从这里减小圆的半径

    我不确定的算法部分是“减小半径直到另一个子圆变为切线”部分。显然,二分搜索可以在相当长的时间内给出足够好的近似值,但我怀疑你可以将其简化为一个方程。

    【讨论】:

    • 再次,这只是一个近似值,请参阅我在 Svante 算法中的 cmets。在声明您的结果是“最终结果”之前,您应该先做一些证明。您刚刚选择了一些解决方案,而忽略了其他可能解决方案的无穷大(二维集)。
    【解决方案7】:

    我认为这本身不是包装问题。这听起来更像凸包。我认为问题是:

    在飞机上给你一组圆圈。找出每个圆的每个边界点都位于包含圆的边界内或边界上的最小圆的中心点和半径。

    为此,您可以递归运行:找到包含前两个圆的最小圆(并且该圆的中心位于连接两个中心的线上,并且它的半径也应该很容易确定),替换第一个两个圈与新圈,并重复。

    这个算法的正确性属于数学。

    【讨论】:

    • 同意前两段。第 3 段中提到的算法当然不起作用,因为在封装圆圈时,您会创建一个新的边界,而该边界在给定的圆圈中不存在(并使最终的圆圈变得不必要地大)。
    • 您可以证明不会添加不必要的积分。没有足够的空间给出完整的证明; SO甚至支持tex吗?
    • 这显然不是真的,可以立即看出,通过封装当然添加了下一步需要覆盖的新区域。伙计们,在声称某件事是最佳解决方案之前,您应该先证明。
    • 尝试设计一个反例。仅仅因为您“添加新区域”并不意味着您没有添加必须覆盖的点。例如,考虑以 (0,0) 和 (5,0) 为中心的半径为 1 的圆。包含两个圆的任何圆还必须包含具有角 (0,-1)、(0,1)、(5,1)、(5,-1) 的框。
    • 是的,但是按照您的算法,您制作了封装圆 (2.5, 0, 2.5),该圆将进行另一次迭代。这个圈子还包含例如点(2.5, 2.5),不包含在两个原始圆内,也不一定包含在最后一个圆内。
    【解决方案8】:

    我会尝试找到最西端的点,然后是最南端的点,然后以这些点为直径做一个圆。 为了找到这些点,我会循环穿过圆的中心和它们的半径。

    所以结果是:

     initiate max object and min object to average center of circle and zero radius
     For every circle object
         calculate topmost western point of the circle
         check if further away than current point
         if further, calculate position of topmost western point required to pass over this new point and set as new min object
         calculate down most eastern point of the circle
         do the same as previous step, only for max object
     make a circle with center equals to middle of min-max line and radius equals to half min-max line length
    

    如果您是书呆子类型,好的大学图书馆会收藏以下内容:E.Welzl,最小的封闭磁盘(球和椭圆体),H. Maurer(主编),计算机科学的新结果和新趋势,讲座笔记计算机科学,卷。 555,施普林格出版社,359-37(1991)

    如果你想阅读 C++ 代码并将其改编为 Java,http://miniball.sourceforge.net/。 当然,对于圆圈,d=2。

    【讨论】:

    【解决方案9】:

    维基百科文章Smallest circle problem 描述了一种线性平均时间算法,用于初始圆的大小相等的情况。将其推广到不同大小的初始圆圈看起来很简单,尽管我不确定复杂性分析会发生什么。

    【讨论】:

    • 我不确定将这些包含点的算法概括为包含不同半径磁盘的算法是否简单......
    • 我同意@JosephO'Rourke 的观点,即如何将这些算法推广到不同半径的情况并不完全清楚——至少对我来说不是:) 但是,有一个算法和一个 C++ 实现Computational Geometry Algorithms Library (CGAL)。 (您不需要使用所有 CGAL;只需提取所需的头文件和源文件即可。)
    【解决方案10】:

    我有一个大约 O(n4) 真正的解决方案,我正在用 JavaScript 为产品实现:

    • 您需要一个函数来确定解决方案是否有效:准确地说,是一个检查所有圆是否都位于建议的超级圆内的函数。这是相当简单的:对于每个圆 Ci,要求从超级圆的中心到 Ci 的中心的距离加上 C 的半径i 小于等于超圆的半径。

    • 然后,从每对和每三组圆中构造一个超级圆。

      • 对于一对,从 Ci 的中心到 Cj 的中心画一条线。将线在每一端延伸相应圆的半径。线的中点为超圆的中心,半径为线长的一半。

      • 对于 3 个圆,这是 Apollonius 的问题:http://mathworld.wolfram.com/ApolloniusProblem.html;请注意,您需要获得正确的标志才能获得包含所有三个圆圈的标志。

    • 正确的解是半径最小的有效超圆。

    这是我的代码:

    'use strict';
    
    /**
     * Epsilon value for floating point equality.
     * @const
     */
    var EPSILON = 1E-6;
    
    /**
     * Calculates the minimum bounding circle for a set of circles.
     * O(n^4)
     *
     * @param {Array.<Object.<string, number>>} circles A list of 2+ circles.
     * @return {Object.<string, number>} {cx, cy, radius} of the circle.
     */
    function minimumBoundingCircleForCircles(circles) {
    
        var areAllCirclesInOrOnCircle = function(circle) {
            for (var i = 0; i < circles.length; i++) {
                if (!isCircleInOrOnCircle(circles[i], circle)) return false;
            }
            return true;
        };
    
        // try every pair and triple
        var best = {radius: 9E9};
    
        for (var i = 0; i < circles.length; i++) {
            for (var j = i + 1; j < circles.length; j++) {
                var circle = circleFrom2Circles(circles[i], circles[j]);
                if (areAllCirclesInOrOnCircle(circle) &&
                    circle.radius < best.radius) {
                    best.cx = circle.cx; best.cy = circle.cy;
                    best.radius = circle.radius;
                }
    
                for (var k = j + 1; k < circles.length; k++) {
                    var signs = [-1, 1, 1, 1];
                    circle = apollonius(circles[i], circles[j], circles[k],
                                        signs);
                    if (areAllCirclesInOrOnCircle(circle) &&
                        circle.radius < best.radius) {
                        best.cx = circle.cx; best.cy = circle.cy;
                        best.radius = circle.radius;
                    }
                }
            }
        }
    
        return best;
    }
    
    
    /**
     * Calculates a circle from 2 circles.
     *
     * @param {Object.<string, number>} circle1 The first circle.
     * @param {Object.<string, number>} circle2 The second circle.
     * @return {Object.<string, number>} cx, cy, radius of the circle.
     */
    function circleFrom2Circles(circle1, circle2) {
    
        var angle = Math.atan2(circle1.cy - circle2.cy,
                               circle1.cx - circle2.cx);
    
        var lineBetweenExtrema = [[circle1.cx + circle1.radius * Math.cos(angle),
                                   circle1.cy + circle1.radius * Math.sin(angle)],
                                  [circle2.cx - circle2.radius * Math.cos(angle),
                                   circle2.cy - circle2.radius * Math.sin(angle)]];
    
        var center = lineMidpoint(lineBetweenExtrema[0], lineBetweenExtrema[1]);
        return { cx: center[0],
                 cy: center[1],
                 radius: lineLength(lineBetweenExtrema[0], 
                                    lineBetweenExtrema[1]) / 2
               };
    }
    
    /**
     * Solve the Problem of Apollonius: a circle tangent to all 3 circles.
     * http://mathworld.wolfram.com/ApolloniusProblem.html
     *
     * @param {Object.<string, number>} circle1 The first circle.
     * @param {Object.<string, number>} circle2 The second circle.
     * @param {Object.<string, number>} circle3 The third circle.
     * @param {Array.<number>} signs The array of signs to use.
     *                               [-1, 1, 1, 1] gives max circle.
     * @return {Object.<string, number>} The tangent circle.
     */
    function apollonius(circle1, circle2, circle3, signs) {
    
        var sqr = function(x) { return x * x };
    
        var a1 = 2 * (circle1.cx - circle2.cx);
        var a2 = 2 * (circle1.cx - circle3.cx);
        var b1 = 2 * (circle1.cy - circle2.cy);
        var b2 = 2 * (circle1.cy - circle3.cy);
        var c1 = 2 * (signs[0] * circle1.radius + signs[1] * circle2.radius);
        var c2 = 2 * (signs[0] * circle1.radius + signs[2] * circle3.radius);
        var d1 = (sqr(circle1.cx) + sqr(circle1.cy) - sqr(circle1.radius)) -
            (sqr(circle2.cx) + sqr(circle2.cy) - sqr(circle2.radius));
        var d2 = (sqr(circle1.cx) + sqr(circle1.cy) - sqr(circle1.radius)) -
            (sqr(circle3.cx) + sqr(circle3.cy) - sqr(circle3.radius));
    
        // x = (p+q*r)/s; y = (t+u*r)/s
    
        var p = b2 * d1 - b1 * d2;
        var q = (- b2 * c1) + (b1 * c2);
        var s = a1 * b2 - b1 * a2;
        var t = - a2 * d1 + a1 * d2;
        var u = a2 * c1 - a1 * c2;
    
        // you are not expected to understand this.
        // It was generated using Mathematica's Solve function.
        var det = (2 * (-sqr(q) + sqr(s) - sqr(u)));
        var r = (1 / det) * 
            (2 * p * q + 2 * circle1.radius * sqr(s) + 2 * t * u -
             2 * q * s * circle1.cx - 2 * s * u * circle1.cy + signs[3] *
             Math.sqrt(sqr(-2 * p * q - 2 * circle1.radius * sqr(s) - 2 * t * u +
                           2 * q * s * circle1.cx + 2 * s * u * circle1.cy) - 
                       4 * (-sqr(q) + sqr(s) - sqr(u)) * 
                       (-sqr(p) + sqr(circle1.radius) * sqr(s) - sqr(t) +
                        2 * p * s * circle1.cx - sqr(s) * sqr(circle1.cx) + 
                        2 * s * t * circle1.cy - sqr(s) * sqr(circle1.cy))))
    
        //console.log(r);
        r = Math.abs(r);
    
        var x = (p + q * r) / s;
    
        var y = (t + u * r) / s;
    
        //console.log(x); console.log(y);
        return {cx: x, cy: y, radius: r};
    }
    
    /**
     * Is the circle inside/on another circle?
     *
     * @param {Object.<string, number>} innerCircle the inner circle.
     * @param {Object.<string, number>} outerCircle the outer circle.
     * @return {boolean} is the circle inside/on the circle?
     */
    function isCircleInOrOnCircle(innerCircle, outerCircle) {
        return ((lineLength([innerCircle.cx, innerCircle.cy],
                            [outerCircle.cx, outerCircle.cy]) +
                 innerCircle.radius - EPSILON) < outerCircle.radius);
    }
    
    
    /**
     * Calculates the length of a line.
     * @param {Array.<number>} pt1 The first pt, [x, y].
     * @param {Array.<number>} pt2 The second pt, [x, y].
     * @return {number} The length of the line.
     */
    function lineLength(pt1, pt2) {
        return Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) +
                         Math.pow(pt1[1] - pt2[1], 2));
    }
    
    /**
     * Calculates the midpoint of a line.
     * @param {Array.<number>} pt1 The first pt, [x, y].
     * @param {Array.<number>} pt2 The second pt, [x, y].
     * @return {Array.<number>} The midpoint of the line, [x, y].
     */
    function lineMidpoint(pt1, pt2) {
        return [(pt1[0] + pt2[0]) / 2,
                (pt1[1] + pt2[1]) / 2];
    }
    

    【讨论】:

      【解决方案11】:

      例如,"The smallest enclosing ball of balls: combinatorial structure and algorithms" 研究了 迷你球问题。这项研究的一个结果是,至少有一些用于小球问题的算法(如 Welzl 算法)不能轻易地从点推广到球。

      上述论文提出了一种O(n)-算法来计算一组球的迷你球(n 是输入球的数量,即在2D)。 Computational Geometry Algorithms Library (CGAL) 中提供了其 C++ 实现。 (您不需要使用所有 CGAL;只需提取所需的头文件和源文件即可。)

      注意:我是上述论文的合著者,也是 CGAL Min_sphere_of_spheres 包的作者。

      【讨论】:

        猜你喜欢
        • 2010-12-18
        • 1970-01-01
        • 1970-01-01
        • 2022-11-10
        • 1970-01-01
        • 1970-01-01
        • 2019-12-24
        • 1970-01-01
        • 2013-11-23
        相关资源
        最近更新 更多