圈套圈的圈子

时间:2011-08-07 22:08:23

标签: algorithm geometry

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

给出不同大小(可能)和位置的圆圈列表,确定一个大圆圈(位置和大小),它恰好包围所有圆圈。

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

有什么想法吗?

11 个答案:

答案 0 :(得分:3)

例如,"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包的作者。

答案 1 :(得分:1)

我有一个大致O(n 4 )真正的解决方案,我正在为JavaScript中的产品实现:

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

  • 然后,在每对和每三个圆圈中构建一个超圆圈。

    • 对于一对,从C i 的中心绘制一条线到C j 的中心。将每一端的线延伸出相应圆的半径。线的中点是超圆的中心,其半径是线的长度的一半。

    • 对于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];
}

答案 2 :(得分:0)

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

答案 3 :(得分:-1)

您可以找到最大边界(xmin,xmax,ymin,ymax),然后在两个轴上取最大值,然后让java在该正方形中绘制一个椭圆,或者将其中心和侧面作为直径。

不是吗?

此致  斯特凡

答案 4 :(得分:-1)

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

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

现在通过归纳添加更多内圈。在每个步骤的开头,您知道包含所有圈子的最小圆圈;它将触及三个特殊的旧“角落”圈子。如果新的圈子在那个圈子里面,那就无所事事了。如果没有,则将新圆圈与所有三种方式组合以选择两个旧角圆并为每个三角形计算外接圆。其中一个应该包括第四个,现在不再是一个角落圈了。

继续,直到你添加了所有圈子。

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

答案 5 :(得分:-1)

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

  • 第一个边界圆 c1

    • 中心由 x c1 确定 =( x max + x min )/ 2 和 y c1 =( y max + y min )/ 2。
    • 对于每个圆,计算其中心到 c1 中心的距离加上其半径(我称之为超距离)。 这些值的最大值是 c1 的半径。相应的圈子是 a
  • 第二个边界圈 c2 : (在此步骤中,您尽可能将 c1 的中心移向 a 的方向。)

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

    • 确定 v 的方向,您可以在其中移动 c2 ,以便超越 a b 保持不变。
    • 对于除 a b 之外的每个圈子,确定 c2 的中心向 v <方向移动的距离< / em>以便从那里到该圆圈的超距离与 a b 相同。其最小值决定了 c3 的中心。半径是找到的三个圆圈的距离。

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

答案 6 :(得分:-1)

我建议的算法类似于Svante,但有一些差异。

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

第一个近似值:

圆心,中心0,0,半径max(距离子圈0,0 +子圈半径)

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

第二个近似值:

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

最终结果:

再次减小圆的半径,使其与上面找到的两个子圆相切,直到另一个圆周与圆相切,或圆的中心位于两个圆周的中心之间的直线上。固定的。这应该是最小的,因为如果没有其中一个子圈突破&#39;

,没有办法从这里减少圆的半径

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

答案 7 :(得分:-2)

有两个圆圈很容易。穿过两个中心的线将会到达一个封闭圆圈与之接触的周边。

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

答案 8 :(得分:-2)

这是一个非常困难的问题,我只想概述可能的方法,你必须自己完成它。我假设你想找到最小边界圈。 正式化你的问题 - 对于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 。祝你好运......

答案 9 :(得分:-2)

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

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

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

该算法的正确性属于数学。

答案 10 :(得分:-2)

我试图找到最顶端的西点,然后是最南端的点,然后用这些点作为直径做一个圆。 为了找到这些点,我将循环通过圆心和它们的半径。

所以最终结果是:

 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(编辑),新成果和计算机科学的新趋势,讲义计算机科学,Vol。 555,Springer-Verlag,359-37(1991)

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