范围树(C++实现,两种建树方法,截窗查询)

范围树它长什么样子

文字描述

              网上和书上说的都比较抽象,根据我自己的理解,其实它就类似于一个多维的线段树,它的每一个维度是一个线段树,并且带有一个指向下一维度线段树的指针。这样子,如果是二维的范围树(第一个维度带有k个节点),它就是一个带有k个结点的线段树和k棵另一维度树的线段树的集合,其中第一维度的线段树,每个结点都带有指向以本结点为范围的下一维度线段树的指针。

例图

这里以取中点的线段树为例

范围树(C++实现,两种建树方法,截窗查询)_第1张图片

怎么建树

思路1

         这个问题只看为伪代码是没有任何卵用的。比如要建一个二维的范围树,第一个维度其实只需要建一个普通的线段树,维度指针指向下一个维度的线段树即可,难点在于第二个维度的线段树要怎么建。在这个过程中,xy方向,对于x方向我们可以对点集按x从小到大进行排序,然后建树,但是,对于y方向,我们却不能直接对点集按y从小到大进行排序,原因在于x小的点,它的y不一定小,如果直接对所有点按y从小到大排序,那么点的x方向的分布就会出问题,所以应该把x限制在一个范围内的时候,再对y方向进行排序,也就是说,如果用递归的方式来构建范围树,它的第二维y需要每次都对区间在x方向上进行递归,每次x方向的界限定在l和r之间,然后,对x介于l和r之间的点进行一次y从小到大的排序,然后用这些点来构建x介于l和r之间的一个点的维度指针所指向的第二维的线段树即可。

例如2,8    1,5      3,3      4,4     5,7       6,2      7,1     8,6,如果直接对y方向从小到大排序,得到的是7,1  6,2   3,3  4,4   1,5    8,6    5,7    2,8,这样子叶子2,8对应y维度的树就成了7,1了,其实叶子结点,两个维度肯定是完全相同的。

代码1

对于x方向,我们直接进行构建线段树,对于y方向,先用一个辅助函数定界并对点按x从小到大排序,再构建线段树,maxN是为了确定辅助函数的递归出口

//构建范围树 
void RangeTree::BuildTree(RangeTree* rangeTreeX,RangeTree** rangeTreeY,Point* xSortList,Point* ySortList,int l,int r,int index,int indexY,Direction dir)
{
	maxN = max(maxN,index);
	//maxIndex = max(index,maxIndex);
	int m = (l + r)/2;
	switch(dir)
	{
		case X:
			//递归到了叶结点 
			if(l == r)
			{
			    rangeTreeX[index].eleXDir = xSortList[m];
			    rangeTreeX[index].dir = dir;
			    rangeTreeX[index].nextDimension = rangeTreeY[index];
			    rangeTreeX[index].l = l;
			    rangeTreeX[index].r = r;
			    return;
			}
			//递归构建左子树 
			rangeTreeX[index].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,l,m,2*index,indexY,X);
            //递归构建右子树 
			rangeTreeX[index].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,m+1,r,2*index+1,indexY,X);
			//为了截窗查询,这里取中点 
			Point point;
			point.x = (rangeTreeX[2*index].eleXDir.x + rangeTreeX[2*index+1].eleXDir.x)/2;
			point.y = (rangeTreeX[2*index].eleXDir.y + rangeTreeX[2*index+1].eleXDir.y)/2; 
			//父结点设置为两个子结点的中点 
			rangeTreeX[index].eleXDir = point;
			//x方向的维度指针指向y方向的树 
			rangeTreeX[index].nextDimension = rangeTreeY[index]; 
			rangeTreeX[index].l = l;
			rangeTreeX[index].r = r;
			break;
		case Y:
			
			if(l == r)
			{
				//找到了叶结点,注意这里的l和r指的是x方向的l和r,为了方便用索引建树与x方向一一对应 
			    rangeTreeY[index][indexY].eleYDir = ySortList[m];
			    rangeTreeY[index][indexY].dir = dir;
			    rangeTreeY[index][indexY].nextDimension = NULL;
			    rangeTreeY[index][indexY].l = l;
			    rangeTreeY[index][indexY].r = r;
			    return;
		    }
		    
		    //递归构建左子树	
			rangeTreeY[index][indexY].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,l,m,index,2*indexY,Y);
			//递归构建右子树	
		    rangeTreeY[index][indexY].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,m+1,r,index,2*indexY + 1,Y);
		    
		    //为了截窗查询,这里取中点 
			point.y = (rangeTreeY[index][2*indexY].eleYDir.y + rangeTreeY[index][2*indexY+1].eleYDir.y)/2;
			point.x = (rangeTreeY[index][2*indexY].eleYDir.x + rangeTreeY[index][2*indexY+1].eleYDir.x)/2;
		    rangeTreeY[index][indexY].eleYDir = point;
		    
		    rangeTreeY[index][indexY].dir = dir;
		    //因为是二维的,y方向的维度指针没有指向任何树 
			rangeTreeY[index][indexY].nextDimension = NULL;
		    rangeTreeY[index][indexY].l = l;
			rangeTreeY[index][indexY].r = r;
			break;
	}
}

辅助函数

void RangeTree::HelpBuildYTree(int rBound,int i,RangeTree** rangeTreeY,Point* xSortList,int l,int r)
{
	if(i > rBound)
	    return;
	//获取l到r之间的point 
	Point* list = new Point[r+1];
	for(int j = l; j <= r; ++j)
	{
	    list[j] = xSortList[j];
	}
	//对l到r之间的point排序 
	sort(list + l,list + r + 1,YSortFunc);
	//对y方向建树 
	rangeTreeY[i][1].BuildTree(NULL,rangeTreeY,list,list,l,r,i,1,Y);

	int m = (l + r)/2;
	//帮助构建左子树和右子树 
	if(m >= l)
	    HelpBuildYTree(rBound,i*2,rangeTreeY,xSortList,l,m);
	if(m+1 <= r)
	    HelpBuildYTree(rBound,i*2+1,rangeTreeY,xSortList,m+1,r);
}

思路2

但是这样子建这棵树的y维度,其实每一次都需要进行排序,效率比较低,感觉不太好,但是思路1中出问题的地方是直接对y排序后,会和x的界限对不上,于是一种神奇的想法产生了,如果每一次排序,先排单个叶结点,然后保证叶结点上一层两两有序,然后再上一层四四有序,。。。,这样下去,其实在这个过程中构建y维度的树就不会有什么问题了,也不用每次都进行排序,也就是利用归并排序,在每次合并的过程中进行排序顺便构建对应x范围的子树。

代码2

void RangeTree::DivideAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index)
{
	if(l < r)
	{
	    int m = (l + r)/2;
	    DivideAndBuildYTree(rangeTreeY,xSortList,l,m,2*index);
	    DivideAndBuildYTree(rangeTreeY,xSortList,m + 1,r,2*index + 1);
	    MergeAndBuildYTree(rangeTreeY,xSortList,l,r,index);
    }
	
}

void RangeTree::MergeAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index)
{
	int m = (l + r)/2;
	Point* list = new Point[r - l + 1];
	int pl = l,pr = m + 1;
	int p = 0;
	while(pl <= m && pr <=r)
	{
		list[p++] = xSortList[pl].y < xSortList[pr].y? xSortList[pl++]:xSortList[pr++];
	}
	while(pl <= m)
	{
	    list[p++] = xSortList[pl++];
	}
	while(pr <= r)
	{

	    list[p++] = xSortList[pr++];
	}
	
	if(r - l == 1)
	{
		//分到倒数第二层,需要把单个的结点建树 
		BuildTree(NULL,rangeTreeY,xSortList,xSortList,l,l,2*index,1,Y);
		BuildTree(NULL,rangeTreeY,xSortList,xSortList,r,r,2*index + 1,1,Y);
	}
	
	for(int i = 0; i < p; ++i)
	{
		xSortList[i + l] = list[i]; 
	}
	//建非单个结点的树
	BuildTree(NULL,rangeTreeY,xSortList,xSortList,l,r,index,1,Y);
}

截窗查询

其实就是输入一个矩形框,输出位于这个框内所有点的集合。

思路

利用刚刚建好的树,先从x方向二分找到所有x全部位于矩形框内的区间(l,r),然后对于(l,r)区间,去y方向二分找到所有y坐标位于框内的点即可。

代码

void RangeTree::BoxSearch(RangeTree* rangeTreeX,RangeTree** rangeTreeY,int l,int r,Box box,vector& points,Direction dir,int index,int indexY)
{
	int m = (l + r)/2;
	switch(dir)
	{
		case X:
			//树中的x完全在要被查找的框内 
		    if(l > box.xMin && l < box.xMax && r > box.xMin && r < box.xMax)
		    {
		    	BoxSearch(rangeTreeX,rangeTreeY,l,r,box,points,Y,index,1);
		    	return ;
			}
			
			//到了叶结点,并且叶结点不在框内
			if(l == r)
			{
				return;
			} 
			
			//树中的x左半边有可能有包含在框内的点 
			if(m >= box.xMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,X,2*index,1);
			}
			//树中的x右半边可能有包含在框内的点 
			if(m + 1 <= box.xMin)
			{
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,X,2*index+1,1);
			}
			//两半边都可能存在符合条件的点 
			if(m >= box.xMin && m + 1 <= box.xMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,X,2*index,1);
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,X,2*index+1,1);
			}
			break;
			
		case Y:
			//走到了这里相当于是X方向是完全在框内的,只需要考虑y方向是不是在框内即可

			//到了叶结点 
			if(l == r)
			{
				//找到了一个符合条件的点 
				if(rangeTreeY[index][indexY].eleYDir.y > box.yMin && rangeTreeY[index][indexY].eleYDir.y < box.yMax)
				{
					points.push_back(rangeTreeY[index][indexY].eleYDir);
				}
				return;
			}
			
			if(rangeTreeY[index][indexY].eleYDir.y >= box.yMax)
			{
				//递归查找左子树 
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,dir,index,2*indexY);
			}
			
			if(rangeTreeY[index][indexY].eleYDir.y <= box.yMin)
			{
				//递归查找右子树 
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,dir,index,2*indexY + 1);
			}
			//左右子树都可能出现符合条件的点 
			if(rangeTreeY[index][indexY].eleYDir.y >= box.yMin && rangeTreeY[index][indexY].eleYDir.y <= box.yMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,dir,index,2*indexY);
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,dir,index,2*indexY + 1);
			}
			
			break;
	}
}

完整的代码

#include
using namespace std;
enum Direction
{
	X,
	Y
};

class Box
{
   public:
       int xMin;
       int yMin;
       int xMax;
       int yMax;
       Box();
	   Box(int xMin,int yMin,int xMax,int yMax);
};

class Point
{
	public:
        int x;
	    int y;
	    friend bool XSortFunc(Point a,Point b);
		friend bool YSortFunc(Point a,Point b);
		Point operator+(const Point& other);
		friend ostream& operator<<(ostream& out,Point point);
};

bool XSortFunc(Point a,Point b);
bool YSortFunc(Point a,Point b);

class RangeTree
{
	public:
        RangeTree* nextDimension;
        Point eleXDir;
        Point eleYDir;
        Direction dir;
        int l;
        int r;
        int depth;
        void BuildTree(RangeTree* rangeTreeX,RangeTree** rangeTreeY,Point* xSortList,Point* ySortList,int l,int r,int index,int indexY,Direction dir);
		void HelpBuildYTree(int rBound,int i,RangeTree** rangeTreeY,Point* xSortList,int l,int r);
		void Bfs(RangeTree* root,int l,int r,Direction dir);
		void DivideAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index);
		void MergeAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index);
		void BoxSearch(RangeTree* rangeTreeX,RangeTree** rangeTreeY,int l,int r,Box box,vector& points,Direction dir,int index,int indexY);
		RangeTree(RangeTree* nextDimension,Point eleXDir,Point eleYDir,Direction dir);
        RangeTree();
};


int maxN = 0;
int main()
{
	int l,r,lY,rY;
	cin>>l>>r>>lY>>rY;
	Point* pointList = new Point[r+1];
	for(int i = l; i <= r; ++i)
	{
		Point point;
		cin>>point.x>>point.y;
		pointList[i] = point;
	}
	sort(pointList+l,pointList+r+1,XSortFunc);
	RangeTree* rangeTreeX = new RangeTree[4*r];
	//第二个维度应该需要开一个二维数组,因为第一个维度每个元素需要指向一棵第二维度的树,
	//如果整体用一棵树,排序后就会有问题,比如(1,4),(2,8),(3,12),(4,1),对于只是两个点的父节点结果应该是
	//(1,4),(2,8),但是如果对是这四个结点的父节点来说,结果就成了(4,1) (1,4) (2,8) (3,12)
	//所以必须每个结点指向单独的树 
	RangeTree** rangeTreeY = new RangeTree*[4*r];

	for(int i = 1; i <= 4*r; ++i)
	{
		rangeTreeY[i] = new RangeTree[4*r];
	}
	
	rangeTreeX[1].BuildTree(rangeTreeX,rangeTreeY,pointList,pointList,l,r,1,1,X);
	
	int powCnt = 0;
	
	//rangeTreeY[1][1].HelpBuildYTree(maxN,1,rangeTreeY,pointList,l,r);
	rangeTreeY[1][1].DivideAndBuildYTree(rangeTreeY,pointList,l,r,1);
	
	
	
	for(int i = 1; i <= 15; ++i)
	{
	
    	rangeTreeY[2][1].Bfs(rangeTreeY[i],l,r,Y);
    	cout< tarPoints;
    rangeTreeX[1].BoxSearch(rangeTreeX,rangeTreeY,l,r,*box,tarPoints,X,1,1);
	for(vector::iterator i = tarPoints.begin(); i < tarPoints.end();++i)
	{
		cout<<*i<nextDimension = nextDimension;
	this->eleXDir = eleXDir;
	this->eleYDir = eleYDir;
	this->dir = dir;
}

Point Point::operator+(const Point& other)
{
	Point point;
	point.x = this->x + other.x;
	point.y = this->y + other.y;
	return point;
}

Box::Box()
{
}

Box::Box(int xMin,int yMin,int xMax,int yMax)
{
    this->xMin = xMin;
	this->yMin = yMin;
	this->xMax = xMax;
	this->yMax = yMax;	
}

//构建范围树 
void RangeTree::BuildTree(RangeTree* rangeTreeX,RangeTree** rangeTreeY,Point* xSortList,Point* ySortList,int l,int r,int index,int indexY,Direction dir)
{
	maxN = max(maxN,index);
	//maxIndex = max(index,maxIndex);
	int m = (l + r)/2;
	switch(dir)
	{
		case X:
			//递归到了叶结点 
			if(l == r)
			{
			    rangeTreeX[index].eleXDir = xSortList[m];
			    rangeTreeX[index].dir = dir;
			    rangeTreeX[index].nextDimension = rangeTreeY[index];
			    rangeTreeX[index].l = l;
			    rangeTreeX[index].r = r;
			    return;
			}
			//递归构建左子树 
			rangeTreeX[index].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,l,m,2*index,indexY,X);
            //递归构建右子树 
			rangeTreeX[index].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,m+1,r,2*index+1,indexY,X);
			//为了截窗查询,这里取中点 
			Point point;
			point.x = (rangeTreeX[2*index].eleXDir.x + rangeTreeX[2*index+1].eleXDir.x)/2;
			point.y = (rangeTreeX[2*index].eleXDir.y + rangeTreeX[2*index+1].eleXDir.y)/2; 
			//父结点设置为两个子结点的中点 
			rangeTreeX[index].eleXDir = point;
			//x方向的维度指针指向y方向的树 
			rangeTreeX[index].nextDimension = rangeTreeY[index]; 
			rangeTreeX[index].l = l;
			rangeTreeX[index].r = r;
			break;
		case Y:
			
			if(l == r)
			{
				//找到了叶结点,注意这里的l和r指的是x方向的l和r,为了方便用索引建树与x方向一一对应 
			    rangeTreeY[index][indexY].eleYDir = ySortList[m];
			    rangeTreeY[index][indexY].dir = dir;
			    rangeTreeY[index][indexY].nextDimension = NULL;
			    rangeTreeY[index][indexY].l = l;
			    rangeTreeY[index][indexY].r = r;
			    return;
		    }
		    
		    //递归构建左子树	
			rangeTreeY[index][indexY].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,l,m,index,2*indexY,Y);
			//递归构建右子树	
		    rangeTreeY[index][indexY].BuildTree(rangeTreeX,rangeTreeY,xSortList,ySortList,m+1,r,index,2*indexY + 1,Y);
		    
		    //为了截窗查询,这里取中点 
			point.y = (rangeTreeY[index][2*indexY].eleYDir.y + rangeTreeY[index][2*indexY+1].eleYDir.y)/2;
			point.x = (rangeTreeY[index][2*indexY].eleYDir.x + rangeTreeY[index][2*indexY+1].eleYDir.x)/2;
		    rangeTreeY[index][indexY].eleYDir = point;
		    
		    rangeTreeY[index][indexY].dir = dir;
		    //因为是二维的,y方向的维度指针没有指向任何树 
			rangeTreeY[index][indexY].nextDimension = NULL;
		    rangeTreeY[index][indexY].l = l;
			rangeTreeY[index][indexY].r = r;
			break;
	}
}
//debug用 
void RangeTree::Bfs(RangeTree* root,int l,int r,Direction dir)
{
	queue q;
	q.push(1);
	int index = 1;
	root[1].depth = 1;
	while(!q.empty())
	{
		int a = q.front();
		q.pop(); 
		if(index < root[a].depth)
		{
		    index = root[a].depth;
			cout< root[a].l)
		{ 
		    root[2*a].depth = root[a].depth + 1;
		    root[2*a+1].depth = root[a].depth + 1;
		    q.push(2*a);
		    q.push(2*a+1);
		}
	}
}

//这样子建Y方向的树不太好,帮助x为l和r之间的结点建一个y方向的范围树 
void RangeTree::HelpBuildYTree(int rBound,int i,RangeTree** rangeTreeY,Point* xSortList,int l,int r)
{
	if(i > rBound)
	    return;
	//获取l到r之间的point 
	Point* list = new Point[r+1];
	for(int j = l; j <= r; ++j)
	{
	    list[j] = xSortList[j];
	}
	//对l到r之间的point排序 
	sort(list + l,list + r + 1,YSortFunc);
	//对y方向建树 
	rangeTreeY[i][1].BuildTree(NULL,rangeTreeY,list,list,l,r,i,1,Y);

	int m = (l + r)/2;
	//帮助构建左子树和右子树 
	if(m >= l)
	    HelpBuildYTree(rBound,i*2,rangeTreeY,xSortList,l,m);
	if(m+1 <= r)
	    HelpBuildYTree(rBound,i*2+1,rangeTreeY,xSortList,m+1,r);
}

//更好的方法应该是利用递归的归并排序的过程中分和合,保证组内有序,先进行递归,到叶结点的时候再开始建树,这样就不需要对每个结点刻意进行排序了,我这样子弄好像不太好 
void RangeTree::DivideAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index)
{
	if(l < r)
	{
	    int m = (l + r)/2;
	    DivideAndBuildYTree(rangeTreeY,xSortList,l,m,2*index);
	    DivideAndBuildYTree(rangeTreeY,xSortList,m + 1,r,2*index + 1);
	    MergeAndBuildYTree(rangeTreeY,xSortList,l,r,index);
    }
	
}

void RangeTree::MergeAndBuildYTree(RangeTree** rangeTreeY,Point* xSortList,int l,int r,int index)
{
	int m = (l + r)/2;
	Point* list = new Point[r - l + 1];
	int pl = l,pr = m + 1;
	int p = 0;
	while(pl <= m && pr <=r)
	{
		list[p++] = xSortList[pl].y < xSortList[pr].y? xSortList[pl++]:xSortList[pr++];
	}
	while(pl <= m)
	{
	    list[p++] = xSortList[pl++];
	}
	while(pr <= r)
	{

	    list[p++] = xSortList[pr++];
	}
	
	if(r - l == 1)
	{
		//分到倒数第二层,需要把单个的结点建树 
		BuildTree(NULL,rangeTreeY,xSortList,xSortList,l,l,2*index,1,Y);
		BuildTree(NULL,rangeTreeY,xSortList,xSortList,r,r,2*index + 1,1,Y);
	}
	
	for(int i = 0; i < p; ++i)
	{
		xSortList[i + l] = list[i]; 
	}
	//建非单个结点的树
	BuildTree(NULL,rangeTreeY,xSortList,xSortList,l,r,index,1,Y);
}

//这个是找位于box开区间内的点 
void RangeTree::BoxSearch(RangeTree* rangeTreeX,RangeTree** rangeTreeY,int l,int r,Box box,vector& points,Direction dir,int index,int indexY)
{
	int m = (l + r)/2;
	switch(dir)
	{
		case X:
			//树中的x完全在要被查找的框内 
		    if(l > box.xMin && l < box.xMax && r > box.xMin && r < box.xMax)
		    {
		    	BoxSearch(rangeTreeX,rangeTreeY,l,r,box,points,Y,index,1);
		    	return ;
			}
			
			//到了叶结点,并且叶结点不在框内
			if(l == r)
			{
				return;
			} 
			
			//树中的x左半边有可能有包含在框内的点 
			if(m >= box.xMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,X,2*index,1);
			}
			//树中的x右半边可能有包含在框内的点 
			if(m + 1 <= box.xMin)
			{
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,X,2*index+1,1);
			}
			//两半边都可能存在符合条件的点 
			if(m >= box.xMin && m + 1 <= box.xMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,X,2*index,1);
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,X,2*index+1,1);
			}
			break;
			
		case Y:
			//走到了这里相当于是X方向是完全在框内的,只需要考虑y方向是不是在框内即可

			//到了叶结点 
			if(l == r)
			{
				//找到了一个符合条件的点 
				if(rangeTreeY[index][indexY].eleYDir.y > box.yMin && rangeTreeY[index][indexY].eleYDir.y < box.yMax)
				{
					points.push_back(rangeTreeY[index][indexY].eleYDir);
				}
				return;
			}
			
			if(rangeTreeY[index][indexY].eleYDir.y >= box.yMax)
			{
				//递归查找左子树 
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,dir,index,2*indexY);
			}
			
			if(rangeTreeY[index][indexY].eleYDir.y <= box.yMin)
			{
				//递归查找右子树 
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,dir,index,2*indexY + 1);
			}
			//左右子树都可能出现符合条件的点 
			if(rangeTreeY[index][indexY].eleYDir.y >= box.yMin && rangeTreeY[index][indexY].eleYDir.y <= box.yMax)
			{
				BoxSearch(rangeTreeX,rangeTreeY,l,m,box,points,dir,index,2*indexY);
				BoxSearch(rangeTreeX,rangeTreeY,m + 1,r,box,points,dir,index,2*indexY + 1);
			}
			
			break;
	}
}

 

你可能感兴趣的:(几何体数据结构,数据结构)