我对Bouncing Bubble Algorithm感兴趣,可以找到一组点的近似最小封闭球体。
我理解基本概念:“每次找到球外的一点,球就会向它移动并同时增加半径。每一步的增长都被设计成不会超过最佳半径,因此半径越来越接近最优值。 ”
但是,我无法在网上的任何地方找到更具体的信息。增长是如何计算的?向新点的转变有多远?
我正在寻找一个示例实现或足够的信息来实现我自己的解决方案。
我对Bouncing Bubble Algorithm感兴趣,可以找到一组点的近似最小封闭球体。
我理解基本概念:“每次找到球外的一点,球就会向它移动并同时增加半径。每一步的增长都被设计成不会超过最佳半径,因此半径越来越接近最优值。 ”
但是,我无法在网上的任何地方找到更具体的信息。增长是如何计算的?向新点的转变有多远?
我正在寻找一个示例实现或足够的信息来实现我自己的解决方案。
我意识到这篇文章已经有一年了,但我用 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);
}
这是您要求的实现。对于分析部分,您可以阅读以下内容来获得想法。
这些分析适用于圆(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 个点,需要大量的聪明才智。该算法大量使用了两个子程序:
如上所述,median() 早于 Megiddo 的工作,而这里描述为 MEC-center() 的算法是作为他 1983 年论文的一部分提出的。详细探索这些过程将超出本大纲的范围,但每个过程都使用修剪和搜索以线性时间运行。MEC-center() 使用的算法相当于整个算法的简化版本。
给定这些原语,丢弃 n/16 个输入点的算法运行如下:
我们现在可以为 I'' 中的 n/16 个交点中的每一个丢弃 S 中的一个点。推理如下。在两次调用 MEC-center() 之后,我们发现 MEC 中心 C 必须位于 ymid 上方和 L 的右侧,而 I'' 中的任何点都位于 ymid 下方和 L 的左侧。
I'' 中的每个点都在两条平分线的交点处。这些平分线之一的斜率必须≥ mmid,因此决不能通过我们知道 C 所在的象限。将此平分线称为 B。现在,我们知道 BC 位于哪一侧,因此在平分线为 B 的两点中,设 PC 与 C 位于同一侧,另一点为 PX。
很容易证明 PC 必须比 PX 更接近 C。因此,PC 不能位于最小封闭圆上,因此我们可以安全地丢弃 I'' 中 n/16 个交点中的每一个的点 PC。
我们在这里没有讨论如何使该算法处理退化输入(平行平分线、共线点等),但事实证明我们在这种情况下获得了相同的性能保证。事实上,对于简并输入,该算法能够丢弃超过 n/16 个点。简而言之,Megiddo 的算法保证在与输入无关的每次迭代中至少修剪 n/16 个点。
因此,通过基于几何级数的论证,Megiddo 算法在线性时间内计算出最小包围圆。
有关 O(n) 算法的更多信息,请参阅本文
更新:在仔细查看动画和维基百科文章后,我决定我的算法不是弹跳气泡。从动画中可以清楚地看出,Bouncing Bubble 移动了边界球,而我的算法一次只在一个方向上增长它。另外,我的理解是,Bouncing Bubble 使用近似值,可能不包含所有点;但是,似乎有一个您可以控制的容差参数。
我认为我的算法仍然有一些不错的属性:
我自己想出了一个 O(n) 增量算法,从 Wikipedia 上的描述来看,我相信它一定是 Bouncing Bubble 算法。我不知道它离最小边界圆有多近。首先,我将尝试解释我的算法背后的推理。把它画在纸上可能会有所帮助。
想象一下,您有一个以C为中心,半径为r的边界圆。您想扩展圆以包含新点P。您计算从C到P的距离d并发现它大于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));
}
}