前提::假设我在该子集中有一个2D空间的矩形子集和一个点集合,这些点都具有不同的x
值。为了优化尚未编写的算法,我想按照以下过程将框拆分为单元格:我将矩形沿x
轴分成两等份。然后,我将每个子矩形重复减半,直到每个单元格包含1个点或根本不包含任何点。
在此示例中,垂直线表示“减半过程”,并且这些线是按黑暗排序的(较暗的是较新的)。
首先,我将定义两个基本类:
class Point{
private:
double x;
double y;
public:
// [...]
// the relevant constructor and getter
// overloaded operators +, -, * for vector calculations
};
class Box{
private:
Point bottom_left_point;
double width;
double height;
public:
Box(Point my_point, double my_x, double my_y) : // constructor
bottom_left_point(my_point), width(my_x), height(my_y){}
bool contains(const Point& p); // returns true iff the box contains p in the geometric sense
Box halve(bool b) const; // takes a boolean as input and returns the left half-rectangle for false, and the right half-rectangle for true
};
现在要实现“减半算法”,我需要一个类似于二叉树的结构。每个节点将代表矩形的一个子单元(根节点代表整个矩形)。一个节点可能有两个孩子,在这种情况下,孩子代表其左右两半。节点还可以具有指向单元中存在的粒子的指针。最终的想法是从一棵空树开始,然后使用方法insert(Point* to_be_inserted)
一一插入点。
因此,我将定义以下递归类,其private
属性相当不言自明:
class Node;
class Node{
private:
enum node_type{ INT, EXT, EMPTY };
node_type type;
// type == INT means that it is an interior node, i.e. has children
// type == EXT means that it is an exterior node, i.e. has no children but contains a point
// type == EMPTY means that it has no children and no point
std::array<Node*,2> children;
Box domain; // the geometric region which is being represented
Point* tenant; // address of the particle that exists in this cell (if one such exists)
public:
Node(Box my_domain) :
type(EMPTY), children({nullptr}), domain(my_domain){}
//
// to be continued...
首要任务是定义一个subdivide()
方法,该方法赋予我的节点两个孩子:
void Node::subdivide(void){
type = INT;
children[0] = new Node(domain.halve(false));
children[1] = new Node(domain.halve(true));
}
现在一切就绪,可以写出整个问题的症结,insert
方法。由于将以递归方式编写,因此最简单的解决方案是使用布尔返回类型,该类型告诉我们插入是成功还是失败。考虑到这一点,下面是代码:
bool Node::insert(Point* to_be_inserted){
if(not domain.contains(*to_be_inserted)) return false;
switch(type){
case INT:{
for(Node* child : children) if(child->insert(to_be_inserted)) return true;
return false;
}
case EXT:{
subdivide();
for(Node* child : children) if(child->insert(to_be_inserted)) break;
tenant = nullptr;
for(Node* child : children) if(child->insert(to_be_inserted)) return true;
break;
}
case EMPTY:{
type = EXT;
tenant = to_be_inserted;
return true;
}
}
throw 1; // this line should not, in, theory ever be reached
}
(请注意,为了抽象和通用起见,当我可以简单地写出这两种情况时,就在数组for
上使用了children
循环。)
说明:
首先,我们检查to_be_inserted
是否在this
表示的几何区域中。如果不是,请返回false
。
如果this
是内部节点,则将点传递给每个子节点,直到成功插入为止。
如果this
是一个外部节点,则意味着我们必须将该节点分为两部分,以便能够将to_be_inserted
与当前存在于该节点中的点正确隔离
multiply()
。insert
租给其中一个孩子(请原谅这听起来多么淫秽,我向您保证这不是故意的)。to_be_inserted
执行相同的操作并返回结果。 (请注意,由于对box::contains
的初步调用,因此先验插入此时将是成功的。最后,如果this
是EMPTY
节点,我们只需要将tenant
分配给*to_be_inserted
并将type
更改为{{1 }},我们就完成了。
好吧,让我们用一个简单的EXT
来尝试一下:
main
这可以编译,但是在运行几次之后,在运行int main(void){
Box my_box(ORIGIN, 1.0, 1.0); // rectangle of vertices (0,0),(0,1),(1,0),(1,1)
Node tree(box); // initializes an empty tree representing the region of my_box
Point p(0.1, 0.1);
Point q(0.6, 0.7);
tree.insert(&p);
tree.insert(&q);
return 0;
}
底部的异常时会抛出该异常。假设在任何时候都没有insert
值构造Node
,这怎么可能?
编辑:除了这一点,我还注意到了一些可能的错误,这些错误也可能由于代码的少量更改而发生:
无法解释的对type
通过地址nullptr->insert(something)
到insert
的调用,该地址未指向已初始化的0x0000000000000018
。
可以在https://github.com/raphael-vock/phantom-call上找到整个代码,包括带有相关调试标志的Node
。