我对Bouncing Bubble Algorithm感兴趣,为一组点找到近似最小的封闭球体。
我理解基本概念:“每次发现球外的一个点时,球会向它移动并同时增加半径。每一步的增长都是为了使它能够不超过最佳半径,因此半径越来越接近最佳值。“
但我无法在网上找到任何更具体的信息。如何计算增长?转向新点的距离有多远?
我正在寻找一个示例实现或足够的信息来实现我自己的解决方案。
答案 0 :(得分:10)
我意识到这篇文章已经有一年了,但是我用C ++实现了弹跳泡泡算法,并且可能有些人会发现它很有用。这是基于田波的论文,我以18美元的价格购买。
BoundSphere calculateBoundSphere(vector<Vertex> vertices){
Vector3D center = vertices[0].position;
float radius = 0.0001f;
Vector3D pos, diff;
float len, alpha, alphaSq;
vector<Vertex>::iterator it;
for (int i = 0; i < 2; i++){
for (it = vertices.begin(); it != vertices.end(); it++){
pos = it->position;
diff = pos - center;
len = diff.length();
if (len > radius){
alpha = len / radius;
alphaSq = alpha * alpha;
radius = 0.5f * (alpha + 1 / alpha) * radius;
center = 0.5f * ((1 + 1 / alphaSq) * center + (1 - 1 / alphaSq) * pos);
}
}
}
for (it = vertices.begin(); it != vertices.end(); it++){
pos = it->position;
diff = pos - center;
len = diff.length();
if (len > radius){
radius = (radius + len) / 2.0f;
center = center + ((len - radius) / len * diff);
}
}
return BoundSphere(center, radius);
}
答案 1 :(得分:2)
Here是您要求的实施方式。对于分析部分,您可以阅读以下内容。
这些分析用于圆(2d),但球体(3d)的扩展应该很容易。我相信分析会有所帮助。
分析
此算法很简单,但实施起来很昂贵。步骤1,2和3需要给定组中的点数的线性时间。在上面的步骤4中,找到每个新点F需要线性时间。但是,找到点F并不能保证算法的终止。必须重复步骤4,直到圆圈不包含长于其圆周一半的无点间隔。在最坏的情况下,这需要步骤4的(n-2)次迭代,这意味着在步骤4中花费的总时间可以是点集大小的平方的量级。
因此,该算法为O(n2)。
Nimrod Megiddo提出了算法,并使用剪枝和搜索技术进行线性规划,以便在O(n)时间内找到最小的封闭圆。
剪枝和 - 搜索
Megiddo算法的本质是修剪和搜索。在剪枝和搜索算法中,在每个步骤完成线性量的工作以将输入大小减小恒定分数f。如果可以实现这一点,则完成的工作总量将减少到O(n)*(1 +(1-f)+(1-f)2 + ...)。在该公式中,无穷级数是几何的并且总和为常数值,因此总运行时间为O(n)。
例如,假设通过检查我们的n个输入元素集合,我们可以丢弃其中1/4与解决方案无关。通过对剩余元素重复应用这种检查,我们可以将输入减少到一个很容易解决的大小,比如n≤3。实现这种减少所需的总时间将与(n + 3n / 4 + 9n / 16 + ...)成比例。很容易证明这个系列接近并且永远不会超过4n的限制。因此,根据需要,总运行时间与n成正比。
使用几何级数将算法简化为线性时间的想法早于Megiddo的工作;特别是,它先前已被用于开发O(n)中值发现算法。然而,他是第一个将其应用于计算几何中的一些基本问题的人。
Megiddo的线性时间算法
为了找到一组点的最小包围圆(MEC),Megiddo算法在每次(线性时间)迭代时丢弃至少n / 16个点。也就是说,给定n个点的集合S,算法识别可以从S中移除的n / 16个点而不影响S的MEC。可以重复应用该过程直到达到一些普通的基本情况(例如n = 3)。 ),总运行时间与(n + 15n / 16 + 225n / 256 + ...)= 16n成正比。
为了找到丢弃的n / 16分,需要很大的聪明才智。该算法大量使用两个子程序:
如上所述,中位数()早于Megiddo的工作,而此处描述为MEC-center()的算法是1983年论文的一部分。详细探讨这些过程将超出本大纲的范围,但每个过程都使用剪枝和搜索来在线性时间内运行。 MEC-center()使用的算法相当于整个算法的简化版本。
鉴于这些原语,丢弃n / 16个输入点的算法运行如下:
我们现在可以在S中为I&#39;中的每个n / 16交叉点丢弃一个点。推理如下。在两次调用MEC-center()之后,我们发现MEC中心C必须位于ymid之上且位于L的右侧,而I&#39;中的任何一点都在在低于ymid和L的左边。
I&#39;中的每一点。是在两个平分线的交汇点。这些平分线中的一个必须具有≥mmid的斜率,因此绝不能穿过我们知道C所在的象限。称这个平分线B.现在,我们知道B C的哪一侧所在,所以在平分线为B的两个点上,让PC成为与C位于同一侧的那个,让另一个成为PX。
很容易证明PC必须比PX更接近C。因此,PC不能位于最小的封闭圆上,因此我们可以安全地丢弃I&#39;中每个n / 16交叉点的点PC。
我们这里没有讨论如何使用这种算法来处理退化输入(并行平分线,共线点等),但事实证明我们在这种情况下得到了相同的性能保证。事实上,对于退化输入,该算法能够丢弃超过n / 16点。简而言之,Megiddo的算法保证在每次迭代中至少修剪n / 16个点而不依赖于输入。
因此,通过基于几何级数的论证,Megiddo算法计算线性时间内的最小包围圆。
有关O(n)算法
的更多信息,请参阅this文章答案 2 :(得分:0)
更新:仔细阅读动画和维基百科文章,我认为我的算法不是Bouncing Bubble。从动画中可以清楚地看到Bouncing Bubble移动了边界球体,而我的算法一次只能在一个方向上增长。此外,我的理解是Bouncing Bubble使用近似值并且可能不包含所有点;但是,似乎有一个可以控制的公差参数。
我认为我的算法仍有一些不错的属性:
我自己想出了一个O(n)增量算法,根据维基百科上的描述我认为必须是Bouncing Bubble算法。我不知道它与最小边界圈有多接近。首先,我将尝试解释算法背后的原因。它可能有助于在纸上绘制。
想象一下,你有一个带有中心 C 和半径 r 的边界圆。您想要扩展圆圈以包含新点 P 。您计算 d 到 C 到 P 的距离,发现它大于 r ,所以它必须是在圈外。现在,如果您从 P 到 C 投射光线,它会在 A 处退出圆圈背面。
现在假设你将圆圈固定在 A 上,然后沿着光线向 P 展开。它将继续包含所有先前的点,现在它包含 P 。这个新圆的直径是 r + d ,因此它的半径是 r'=(r + d)/ 2 。沿着从 P 到 A 的光线沿着新半径的距离走,你就在新圆的中心。
以下是该算法的草图:
这是我今天写的一些Objective-C代码:
- (void)updateBoundingVolume {
self.boundingSphereCenter = GLKVector3Make(0, 0, 0);
self.boundingSphereRadius = 0;
GLfloat* vertex = self.vertices;
GLfloat* endV = vertex + 3 * self.vertNum;
if (vertex < endV) {
// initialize to zero radius sphere centered on the first point
self.boundingSphereCenter = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
vertex += 3;
}
while (vertex < endV) {
GLKVector3 p = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
vertex += 3;
float d = GLKVector3Distance(self.boundingSphereCenter, p);
if (d <= self.boundingSphereRadius) {
// already inside the sphere
continue;
}
// length of line from other side of sphere through the center to the new point
// divide by 2 for radius
self.boundingSphereRadius = (self.boundingSphereRadius + d) / 2;
// unit vector from new point to old center
GLKVector3 u = GLKVector3DivideScalar(GLKVector3Subtract(self.boundingSphereCenter, p), d);
// step back from the new point by the new radius to get the new center
self.boundingSphereCenter = GLKVector3Add(p, GLKVector3MultiplyScalar(u, self.boundingSphereRadius));
}
}