给定一组不同大小(可能是)和位置的圆,确定一个大圆(位置和大小),它正好包含所有圆。
public class Circle {
public int x, y, radius;
}
有什么想法吗?
public class Circle {
public int x, y, radius;
}
上述论文提出了一种O(n)算法来计算一组球(n为输入球数量,即二维圆)的最小球。C++实现可在计算几何算法库(CGAL)中找到。(您不需要使用CGAL的所有内容;只需提取所需的头文件和源文件即可。)
注意:我是上述论文的合著者,也是CGAL Min_sphere_of_spheres
包的作者。
我有一个大致为O(n4)的真正解决方案,我正在使用JavaScript实现它作为一个产品:
您需要一个函数来确定解决方案是否有效:确切地说,需要一个函数来检查所有圆是否都在提议的超级圆内。这相当简单:对于每个圆Ci,要求从超级圆的中心到Ci的中心的距离加上Ci的半径小于或等于超级圆的半径。对于一对圆,从Ci的中心到Cj的中心绘制一条线。在每个端点上延伸线段的长度为各自圆的半径。线段的中点是超级圆的中心,其半径是线段长度的一半。
对于3个圆,这是阿波罗尼斯问题: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];
}
你可以找到最大边界(xmin,xmax,ymin,ymax),然后在两个轴上取最大值,然后要求Java在该正方形中绘制椭圆或以其中心和边作为直径。
不行吗?
敬礼, 斯蒂芬
哎呀,如评论中所述,以下内容无法正常工作:
首先解决三个圆的问题。在这种情况下,外接圆将接触到每个内部的三个圆,并且您可以将其中心找到为两个双曲线的交点。(给定两个固定点距离的差异的点的轨迹是双曲线)。经过一些代数运算,这归结为一个二次方程。
现在通过归纳添加更多内部圆。在每个步骤开始时,您都知道包围所有旧圆的最小圆;它将接触到三个特定的旧“角落”圆。如果新圆在其中一个圆内,则无需进行任何操作。如果不是,则将新圆与选择两个旧角落圆的三种方式相结合,并为每个三元组计算外接圆。其中之一应该包括第四个圆,它现在不再是角落圆了。
继续添加所有圆。
这提供了具有有限舍入误差的线性时间算法(因为每个外接圆都是从原始输入坐标重新计算的)。
我认为这可以分为三个步骤完成:
第一个边界圆c1:
第二个边界圆c2: (在此步骤中,您将c1的中心向a方向尽可能移动。)
第三个边界圆c3: (在此步骤中,您将c2的中心向a和b之间的方向尽可能移动。)
我相信c3是一个很好的第一次尝试,你可以通过迭代地删除第一个圆并重复第三步来得到更好的解决方案。如果你到达了一组已经看过的三个圆,这可能是最终的解决方案。
我的建议算法与Svante的算法类似,但有一些不同之处。
首先创建一个圆,该圆可以轻松地包含所有子圆,然后像气泡一样缩小它,直到被1、2或3个圆固定。
第一次近似:
一个以0,0为中心,半径为max(子圆距离0,0的距离+子圆半径)的圆
如果确定圆的半径的子圆包围了所有其他子圆,则它显然是正确的结果,并且可以返回
第二次近似:
在保持圆与其“固定”到的子圆相切的同时减小第一次近似中找到的圆的半径,将圆心移向固定的子圆,直到它与另一个子圆相切。
最终结果:
再次减小圆的半径,使其与上述两个子圆相切,直到另一个子圆与圆相切,或者圆的中心位于其固定的两个子圆的中心之间的线上。这应该是最小的,因为从这里没有办法减小圆的半径而不让其中一个子圆“突破”
我不确定算法的哪个部分是“减小半径直到另一个子圆变得相切”的部分。显然,二分搜索可以在合理的时间内给出足够好的近似值,但我怀疑你可以将其简化为一个方程式。我会尝试找到最西端的点,然后是最南端的点,接着以这些点为直径画一个圆。
为了找到这些点,我会通过圆心和半径进行循环。
最终结果如下:
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
对于两个圆来说很容易。通过两个圆心的一条直线将击中周长,这是一个外接圆会与它们接触的地方。
对于更多的圆,您需要在每个周长点上应用FEM(有限元分析-http://en.wikipedia.org/wiki/Finite_element_method),这些点可能成为与外部圆接触的点。例如,排除那些面对其他圆的段。当您开始将不同的半径应用于您的点,直到找到最小的交叉所有其他点的公共点-即您的外接圆的中心时,计算仍然相当大。