改进C ++算法以找到半径为r的球体内的所有点

时间:2014-06-10 03:06:21

标签: c++ algorithm vector multidimensional-array

语言/编译器:C ++(Visual Studio 2013) 经验:~2个月

我在3D空间的矩形网格中工作(尺寸:xdim由ydim by zdim),其中," xgrid,ygrid和zgrid"分别是x,y和z坐标的3D数组。现在,我有兴趣找到半径范围内的所有点" r"以#34;(vi,vj,vk)"为中心。我想将这些点的索引位置存储在矢量" xidx,yidx,zidx"中。对于单个点,该算法可以工作并且足够快,但是当我希望迭代3D空间内的许多点时,我会遇到很长的运行时间。

有没有人对如何在C ++中改进此算法的实现有任何建议?运行一些我在网上发现的分析软件(非常困,Luke stackwalker)似乎" std :: vector :: size"和" std :: vector :: operator []"成员函数正在困扰我的代码。非常感谢任何帮助。

注意:由于我不知道球体内有多少个体素,我将矢量xidx,yidx,zidx的长度设置为大于必要值,然后擦除函数末尾的所有多余元素。

void find_nv(int vi, int vj, int  vk, vector<double> &xidx, vector<double> &yidx, vector<double> &zidx, double*** &xgrid, double*** &ygrid, double*** &zgrid, int r, double xdim,double ydim,double zdim, double pdim)

{
double xcor, ycor, zcor,xval,yval,zval;
vector<double>xyz(3);
xyz[0] = xgrid[vi][vj][vk];
xyz[1] = ygrid[vi][vj][vk];
xyz[2] = zgrid[vi][vj][vk];
int counter = 0;

// Confine loop to be within boundaries of sphere
int istart = vi - r;
int iend = vi + r;
int jstart = vj - r;
int jend = vj + r;
int kstart = vk - r;
int kend = vk + r;

if (istart < 0) {
    istart = 0;
}
if (iend > xdim-1) {
    iend = xdim-1;
}
if (jstart < 0) {
    jstart = 0;
}
if (jend > ydim - 1) {
    jend = ydim-1;
}
if (kstart < 0) {
    kstart = 0;
}
if (kend > zdim - 1)
    kend = zdim - 1;

//-----------------------------------------------------------
 // Begin iterating through all points
//-----------------------------------------------------------
for (int k = 0; k < kend+1; ++k)
{
    for (int j = 0; j < jend+1; ++j)
    {
        for (int i = 0; i < iend+1; ++i)
        {
            if (i == vi && j == vj && k == vk)
                continue;
            else
            {
            xcor = pow((xgrid[i][j][k] - xyz[0]), 2);
            ycor = pow((ygrid[i][j][k] - xyz[1]), 2);
            zcor = pow((zgrid[i][j][k] - xyz[2]), 2);
            double rsqr = pow(r, 2);
            double sphere = xcor + ycor + zcor;
            if (sphere <= rsqr)
            {
                xidx[counter]=i;
                yidx[counter]=j;
                zidx[counter] = k;
                counter = counter + 1;
            }

            else
            {
            }
            //cout << "counter = " << counter - 1;
            }
        }
    }
}

// erase all appending zeros that are not voxels within sphere
xidx.erase(xidx.begin() + (counter), xidx.end()); 
yidx.erase(yidx.begin() + (counter), yidx.end()); 
zidx.erase(zidx.begin() + (counter), zidx.end()); 

return 0;

4 个答案:

答案 0 :(得分:1)

你似乎已经使用了我最喜欢的技巧,摆脱了相对昂贵的平方根函数,只使用了半径和中心到点距离的平方值。

可能加速(a)的另一种可能性是取代所有:

xyzzy = pow (plugh, 2)

使用更简单的调用:

xyzzy = plugh * plugh

您可能会发现删除函数调用可能会加快速度,但是很少。

如果可以建立目标数组的最大大小,另一种可能性是使用实数数组而不是向量。我知道它们使矢量代码尽可能地非常优化,但它仍然无法与固定大小的数组匹配以获得性能(因为它必须执行固定大小的数组 plus 处理可能的扩展)

同样,这可能只会以更多的内存使用成本提供非常微小的改进,但时间交易空间是一种经典的优化策略。

除此之外,请确保明智地使用编译器优化。在大多数情况下,默认构建具有较低的优化级别,以使调试更容易。加速生产代码。


(a)与所有优化一样,你应该测量,而不是猜测!这些建议正是如此:建议。它们可能会也可能不会改善情况,所以由你来测试它们。

答案 1 :(得分:1)

您最大的问题之一,也就是可能阻止编译器进行大量优化的问题是您没有使用网格的常规性质。

如果你真的使用常规网格,那么

xgrid[i][j][k] = x_0 + i * dxi + j * dxj + k * dxk
ygrid[i][j][k] = y_0 + i * dyi + j * dyj + k * dyk
zgrid[i][j][k] = z_0 + i * dzi + j * dzj + k * dzk

如果你的网格是轴对齐的,那么

xgrid[i][j][k] = x_0 + i * dxi
ygrid[i][j][k] = y_0 + j * dyj
zgrid[i][j][k] = z_0 + k * dzk

替换核心循环中的这些应该会导致显着的加速。

答案 2 :(得分:1)

你可以做两件事。减少要测试的点数,并将问题简化为多个2d测试。

如果你沿着z轴看球体,你可以在球体中得到y + r到yr的所有点,使用这些点你可以将球体切割成包含所有点的圆圈。 x / z平面限于您正在测试的特定y处的圆半径。计算圆的半径是一个简单的解决直角三角形问题的基数的长度。

现在你正在测试一个立方体中的所有点,但是球体的上部范围排除了大多数点。上述算法背后的想法是,您可以将球体每个级别测试的点限制为包含该高度处圆的半径的方形。

这是一个简单的手绘图形,从侧视图显示球体。enter image description here

这里我们看的是半径为ab的球体切片。由于您知道直角三角形的长度ac和bc,因此可以使用毕达哥拉斯定理计算ab。现在你有一个简单的圆圈,你可以测试点,然后向下移动,它减少长度ac并重新计算ab和重复。

现在,一旦你拥有了它,你实际上可以做更多的优化。首先,您不需要针对圆圈测试每个点,您只需要测试四分之一的点。如果测试圆的左上象​​限(球体切片)中的点,那么其他三个点中的点只是相同点的镜像,从确定的点到右边,底部或对角线偏移在第一象限。

最后,你只需要做球体上半部分的圆形切片,因为下半部分只是上半部分的一面镜子。最后,您只测试了球体中遏制点的四分之一。这应该是一个巨大的性能提升。

我希望这是有道理的,我现在不在机器上,我可以提供样品。

答案 3 :(得分:0)

这里简单的事情就是从球体中心进行3D洪水填充,而不是在需要访问较小点时迭代封闭的正方形。此外,您应该实现泛洪填充的迭代版本以获得更高的效率。

Flood Fill