1.四叉树定义
定义:除叶子节点外,每个节点都有四个子结点。每个节点每有一个对应的矩形区域。
如图所示。
2.四叉树节点类
由上图编写四叉树节点类
// 四叉树节点类型结构 class Quadnode { public: Quadnode(Quadrect theRect, int theLevel); bool Insert(double x, double y, int depth); //函数功能:矩形区域查询 //查询矩形queryRect 查询结果 resultPts void Query(Quadrect queryRect, std::vector<QPointF> & resultPts); void Traverse(std::vector<QPointF> & resultNode); Quadrect rect; //节点所代表的矩形区域 std::vector<QPointF> pts; Quadnode *sub[4]; //指向节点的四个孩子 };
四叉树类
class PointQuadtree { public: //指定四叉树的最大范围和四叉树的深度 PointQuadtree(Quadrect theRect, int theDepth = 4); bool Insert(double x, double y); //函数功能:矩形区域查询 //查询矩形queryRect 查询结果 resultNodes bool Query(Quadrect queryRect, std::vector<QPointF> & resultPts); Quadrect GetBoundRect(); protected: Quadrect maxRect; // 四叉树的最大范围 Quadnode *root; int depth; // 四叉树的深度 };
3.构造四叉树
此文采用逐个插入点,构建四叉树。这里,构造一颗四叉树,首先要指定根节点的最大矩形。只为落在这个矩形范围内的点编制索引。
首先确定插入方法。存存两种插入方法:
(1)只要某个节点对应的矩形包含的点多于一个,就要递归地对其进行分割。这种方法, 将点插入到树的不同层次的节点中。
(2)所有插入点,存放在叶子结点中。
本文采用第二种插入方法。采用这种方法,构造四叉树非常简单。只要简单的递归就行了。具体描述如下:
先判断插入点是否在节点对应的矩形中。如果不在,则返回。如果在,则这个点一定在此节点的某个子节点中,就递归调用其子节点的插入算法。
插入算法代码如下:
bool Quadnode::Insert(double x, double y, int depth) { //判断是否在这个节点中 if (!rect.IsInRect(x, y)) return false; if(0 == depth) { pts.push_back(QPointF(x,y)); return true; } /* 一个矩形区域的象限划分: UL(1) | UR(0) ----------|----------- LL(2) | LR(3) */ --depth; if (sub[0] == 0) { sub[0] = new Quadnode(rect.GetUL(), depth); sub[1] = new Quadnode(rect.GetUR(), depth); sub[2] = new Quadnode(rect.GetLL(), depth); sub[3] = new Quadnode(rect.GetLR(), depth); } if(sub[0]->Insert(x, y, depth)) return true; if(sub[1]->Insert(x, y, depth)) return true; if(sub[2]->Insert(x, y, depth)) return true; if(sub[3]->Insert(x, y, depth)) return true; return false; }
4.矩形查询算法
定义:找出落在矩形查询范围内的点,即是正交区域查询问题。
4.1算法分析
设查询矩形为RecFind,节点的矩形为RecNode。
RecFind与RecNode有四种关系
C1:RecNode包含RecFind
C2:RecFind包含RecNode
C3:RecFind与RecNode相交
C4:RecFind与RecNode相离
分别讨论这四种情况。
C1时,没有必要遍历RecNode同级的其它三个节点了。只需在此节点对应的四个子节点中查找即可。所图C1所示,查找子节点n1,n2,n3,n4。
C2时,RecNode对应的叶子节点中的点都在RecFind内,递归查找RecNode所有点。
C3时,如果RecFind是叶子节点,就检查叶子节点中储存的点是否在RecFind中。如果RecFind不是叶子节点,就在此节点中继续查找,而后在同级的其它三个节点中查找。因为RecFind也可能也其它三个节点相交或包含。
C4时,就不用查了。
只有这四种情况,可按此四种分类编写递归算法,代码如下
void Quadnode::Query(Quadrect queryRect, std::vector<QPointF> & resultPts) { if(sub[0] == 0) { //查询叶子节点中的点是否矩形中 for (int i = 0; i < pts.size(); ++i) { if(queryRect.IsInRect(pts[i].x(), pts[i].y())) { resultPts.push_back(pts[i]); } } return; } for(int i = 0; i<4; ++i) { //[1]节点完全包含查询矩形 //查询这个节点子节点 if(sub[i]->rect.Contain(queryRect)) { sub[i]->Query(queryRect, resultPts); break; //没有必须查询其它同级节点了 } //[2]查询矩形完全包含节点 //则遍历这个节点以下的所有节点,记录在resultNodes中 if(queryRect.Contain(sub[i]->rect)) { sub[i]->Traverse(resultPts); continue; } //[3]节点与查询矩形相交 //查询这个节点的子节点 if(queryRect.IsInterSect(sub[i]->rect)) { sub[i]->Query(queryRect, resultPts); } } }
5.四叉树的优缺点
四叉树的优点:(1)矩形查找仅与树的规模和查询矩形有关,与点的数量无关。
四叉树的缺点:(1)事先指定根节点矩形的最大范围,即索引的最大范围。
(2)当对象分布很不均匀时,索引效率低。