八叉树C++简单实现

八叉树(Octree)是一种空间分割数据结构,用于在三维空间中对对象进行高效的存储和查询。
将三维空间划分为八个相等大小的子立方体(八个子节点),每个子节点可以进一步细分为八个子节点,以此类推。

八叉树的构造和操作如下:
    构造八叉树:开始时,将整个三维空间划分为一个大的立方体,作为根节点。然后,递归地将根节点分割成八个子节点,每个子节点代表一个子立方体。这个过程会一直递归下去,直到满足某个终止条件(如达到最大深度或子立方体中的对象数量达到阈值)。
    插入对象:要将一个对象插入八叉树中,需要找到对象所在的子立方体。从根节点开始,根据对象的位置依次遍历子节点,直到找到合适的子节点。如果子节点不存在,则创建一个新的子节点。将对象插入到该子节点中。如果子节点已经达到终止条件,则根据具体情况进行处理(如将对象存储在该节点中,或进一步细分子节点)。
    查询对象:要查询八叉树中的对象,可以从根节点开始,根据查询条件判断是否需要遍历子节点。如果查询条件与子节点的范围有重叠,则递归遍历该子节点。如果查询条件与子节点没有重叠,则可以跳过该子节点。

八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。

八叉树的节点可分为三类:
    灰节点: 它对应的立方体部分的为查找元素所占据;
    白节点: 它对应的立方体没有查找元素的内容;
    黑节点: 它对应的立方体全部为查找元素所占据。

八叉树算法思想
(1). 设定最大递归深度。
(2). 找出场景的最大尺寸,并以此尺寸建立第一个立方体。
(3). 依序将单位元元素丢入能被包含且没有子节点的立方体。
(4). 若没达到最大递归深度,就进行细分八等份,再将该立方体所装的单位元元素全部分担给八个子立方体。
(5). 若发现子立方体所分配到的单位元元素数量不为零且跟父立方体是一样的,则该子立方体停止细分,因为跟据空间分割理论,细分的空间所得到的分配必定较少,若是一样数目,则再怎么切数目还是一样,会造成无穷切割的情形。
(6). 重复3,直到达到最大递归深度。

如何确定最大递归深度
可以通过设置分辨率来计算最大递归深度,分辨率描述最低一级的八叉树的最小体素的尺寸,因此八叉树的深度是分辨率和点云数据的函数。

基于八叉树的搜素
    体素近邻搜索
    体素近邻搜索就是把查询点所在的体素中的其他点的索引返回,因此,搜索点和搜索结果之间的距离取决于八叉树的分辨率参数

看一下C++简单实现;

#include <iostream>
using namespace std;//定义八叉树节点类
template<class T>
struct OctreeNode
{
    T data; //节点数据
    T xmin,xmax; //节点坐标,即六面体个顶点的坐标
    T ymin,ymax;
    T zmin,zmax;
    OctreeNode <T> *top_left_front,*top_left_back; //该节点的个子结点
    OctreeNode <T> *top_right_front,*top_right_back;
    OctreeNode <T> *bottom_left_front,*bottom_left_back;
    OctreeNode <T> *bottom_right_front,*bottom_right_back;
    OctreeNode //节点类
        (T nodeValue = T(),
        T xminValue = T(),T xmaxValue = T(),
        T yminValue = T(),T ymaxValue = T(),
        T zminValue = T(),T zmaxValue = T(),
        OctreeNode<T>* top_left_front_Node = NULL,
        OctreeNode<T>* top_left_back_Node = NULL,
        OctreeNode<T>* top_right_front_Node = NULL,
        OctreeNode<T>* top_right_back_Node = NULL,
        OctreeNode<T>* bottom_left_front_Node = NULL,
        OctreeNode<T>* bottom_left_back_Node = NULL,
        OctreeNode<T>* bottom_right_front_Node = NULL,
        OctreeNode<T>* bottom_right_back_Node = NULL )
        :data(nodeValue),
        xmin(xminValue),xmax(xmaxValue),
        ymin(yminValue),ymax(ymaxValue),
        zmin(zminValue),zmax(zmaxValue),
        top_left_front(top_left_front_Node),
        top_left_back(top_left_back_Node),
        top_right_front(top_right_front_Node),
        top_right_back(top_right_back_Node),
        bottom_left_front(bottom_left_front_Node),
        bottom_left_back(bottom_left_back_Node),
        bottom_right_front(bottom_right_front_Node),
        bottom_right_back(bottom_right_back_Node){}
};
//创建八叉树
template <class T>
void createOctree(OctreeNode<T> * &root,int maxdepth,double xmin,double xmax,double ymin,double ymax,double zmin,double zmax)
{
    //cout<<"处理中,请稍候……"<<endl;
    maxdepth=maxdepth-1; //每递归一次就将最大递归深度-1
    if(maxdepth>=0)
    {
        root=new OctreeNode<T>();
        root->data = 9; //为节点赋值,可以存储节点信息,如物体可见性。由于是简单实现八叉树功能,简单赋值为。
        root->xmin=xmin; //为节点坐标赋值
        root->xmax=xmax;
        root->ymin=ymin;
        root->ymax=ymax;
        root->zmin=zmin;
        root->zmax=zmax;
        double xm=(xmax-xmin)/2;//计算节点个维度上的半边长
        double ym=(ymax-ymin)/2;
        double zm=(zmax-zmin)/2;
        //递归创建子树,根据每一个节点所处(是几号节点)的位置决定其子结点的坐标。
        createOctree(root->top_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmax-zm,zmax);
        createOctree(root->top_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmax-zm,zmax);
        createOctree(root->top_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmax-zm,zmax);
        createOctree(root->top_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmax-zm,zmax);
        createOctree(root->bottom_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmin,zmax-zm);
        createOctree(root->bottom_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmin,zmax-zm);
        createOctree(root->bottom_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmin,zmax-zm);
        createOctree(root->bottom_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmin,zmax-zm);
    }
}
int i=1;
//先序遍历八叉树
template <class T>
void preOrder( OctreeNode<T> * & p)
{
    if(p)
    {
        cout<<i<<".当前节点的值为:"<<p->data<<"\n坐标为:";
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        i+=1;
        cout<<endl;
        preOrder(p->top_left_front);
        preOrder(p->top_left_back);
        preOrder(p->top_right_front);
        preOrder(p->top_right_back);
        preOrder(p->bottom_left_front);
        preOrder(p->bottom_left_back);
        preOrder(p->bottom_right_front);
        preOrder(p->bottom_right_back);
        cout<<endl;
    }
}
//求八叉树的深度
template<class T>
int depth(OctreeNode<T> *& p)
{
    if(p == NULL)
        return -1;
    int h = depth(p->top_left_front);
    return h+1;
}
//计算单位长度,为查找点做准备
int cal(int num)
{
    int result=1;
    if(1==num)
        result=1;
    else
    {
        for(int i=1;i<num;i++)
            result=2*result;
    }
    return result;
}
//查找点
int maxdepth=0;
int times=0;
static double xmin=0,xmax=0,ymin=0,ymax=0,zmin=0,zmax=0;
int tmaxdepth=0;
double txm=1,tym=1,tzm=1;
template<class T>
void find(OctreeNode<T> *& p,double x,double y,double z)
{
    double xm=(p->xmax-p->xmin)/2;
    double ym=(p->ymax-p->ymin)/2;
    double zm=(p->zmax-p->zmin)/2;
    times++;
    if(x>xmax || x<xmin || y>ymax || y<ymin || z>zmax || z<zmin)
    {
        cout<<"该点不在场景中!"<<endl;
        return;
    }
    if(x<=p->xmin+txm && x>=p->xmax-txm && y<=p->ymin+tym && y>=p->ymax-tym && z<=p->zmin+tzm && z>=p->zmax-tzm )
    {
        cout<<endl<<"找到该点!"<<"该点位于"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<"节点内!"<<endl;
        cout<<"共经过"<<times<<"次递归!"<<endl;
    }
    else if(x<(p->xmax-xm) && y<(p->ymax-ym) && z<(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->bottom_left_back,x,y,z);
    }
    else if(x<(p->xmax-xm) && y<(p->ymax-ym) && z>(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->top_left_back,x,y,z);
    }
    else if(x>(p->xmax-xm) && y<(p->ymax-ym) && z<(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->bottom_right_back,x,y,z);
    }
    else if(x>(p->xmax-xm) && y<(p->ymax-ym) && z>(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->top_right_back,x,y,z);
    }
    else if(x<(p->xmax-xm) && y>(p->ymax-ym) && z<(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->bottom_left_front,x,y,z);
    }
    else if(x<(p->xmax-xm) && y>(p->ymax-ym) && z>(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->top_left_front,x,y,z);
    }
    else if(x>(p->xmax-xm) && y>(p->ymax-ym) && z<(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->bottom_right_front,x,y,z);
    }
    else if(x>(p->xmax-xm) && y>(p->ymax-ym) && z>(p->zmax-zm))
    {
        cout<<"当前经过节点坐标:"<<endl;
        cout<<" xmin: "<<p->xmin<<" xmax: "<<p->xmax;
        cout<<" ymin: "<<p->ymin<<" ymax: "<<p->ymax;
        cout<<" zmin: "<<p->zmin<<" zmax: "<<p->zmax;
        cout<<endl;
        find(p->top_right_front,x,y,z);
    }
}
//main函数
int main ()
{
    OctreeNode<double> * rootNode = NULL;
    int choiced = 0;
    while(true)
    {
        system("cls");
        cout<<"请选择操作:\n";
        cout<<"1.创建八叉树 2.先序遍历八叉树\n";
        cout<<"3.查看树深度 4.查找节点   \n";
        cout<<"0.退出\n\n";
        cin>>choiced;
        if(choiced == 0)
            return 0;
        else if(choiced == 1)
        {
            system("cls");
            cout<<"请输入最大递归深度:"<<endl;
            cin>>maxdepth;
            cout<<"请输入外包盒坐标,顺序如下:xmin,xmax,ymin,ymax,zmin,zmax"<<endl;
            cin>>xmin>>xmax>>ymin>>ymax>>zmin>>zmax;
            if(maxdepth>=0 || xmax>xmin || ymax>ymin || zmax>zmin || xmin>0 || ymin>0 ||zmin>0)
            {
                tmaxdepth=cal(maxdepth);
                txm=(xmax-xmin)/tmaxdepth;
                tym=(ymax-ymin)/tmaxdepth;
                tzm=(zmax-zmin)/tmaxdepth;
                createOctree(rootNode,maxdepth,xmin,xmax,ymin,ymax,zmin,zmax);
            }
            else
            {
                cout<<"输入错误!";
                return 0;
            }
        }
        else if(choiced == 2)
        {
            system("cls");
            cout<<"先序遍历八叉树结果:\n";
            i=1;
            preOrder(rootNode);
            cout<<endl;
            system("pause");
        }
        else if(choiced == 3)
        {
            system("cls");
            int dep = depth(rootNode);
            cout<<"此八叉树的深度为"<<dep+1<<endl;
            system("pause");
        }
        else if(choiced == 4)
        {
            system("cls");
            cout<<"请输入您希望查找的点的坐标,顺序如下:x,y,z\n";
            double x,y,z;
            cin>>x>>y>>z;
            times=0;
            cout<<endl<<"开始搜寻该点……"<<endl;
            find(rootNode,x,y,z);
            system("pause");
        }
        else
        {
            system("cls");
            cout<<"\n\n错误选择!\n";
            system("pause");
        }
    }
}

此代码来自网上;我还没怎么理解;一眼看上去和二叉树的区别明显,二叉树的节点结构体包含2个节点结构体指针,这个则包括8个节点结构体指针;

跑了看一下;

 

 

输入一个外包围盒,长宽高都为2,

 

 

 

查找(1,1,1),直接退出,

 

再查找几个,输出如下,

 

 

 

遍历,输出非常多;

 

还不怎么理解,跑一下代码理解要多一些,下回继续;

猜你喜欢

转载自blog.csdn.net/bcbobo21cn/article/details/133308066