点定位:如何拆分更新梯形图和二分搜索结构 · (二):处理P和Q
1. 处理P(线段左端点)
1.1 处理思路
我们在总思路分析里面已经讲过处理的方法了,这里给大家总结归纳一下:
1)无论是横跨一个或多个梯形,我们都把原梯形拆分成左右部分(左梯形保留为原梯形,即它的指针不改变),然后将右梯形传递给下一个事件处理方法。
2)如果横跨一个梯形,则传给Q事件方法处理,反之横跨多个梯形,则传给S事件方法处理。
3)如果发生Pi和之前线段端点重合的情况,我们不拆分原梯形,直接将其传给下一个事件处理方法。
我们可以把对应的拆分更新思路归纳成下图:
1.2 代码解析
首先我们先来处理P事件方法的总体代码:
/**
* handle p when adding a new segment,
* this operation will partition
* the original trapezoid into two parts ( vertical separation, left separation ):
* |
* left(origin) | right
* |
*
* and pass the right into handleQ or handleS
* */
public static
SearchVertex handleP( SearchVertex d, Line line, Stack<SearchVertex> de ) {
assert d.type == SearchVertex.NodeType.TRAPEZOID;
Vector p = line.startPoint;
Vector q = line.endPoint;
assert Vectors.sortByX( p, q ) <= 0;
Trapezoid left = d.trapezoid;
SearchVertex XNodeP = null;
// normal case: p -> s or p -> q
if ( !left.leftP.equals( p ) ) {
// left separation
Trapezoid right = new Trapezoid( p, left.rightP, left.top, left.bottom );
Trapezoid.connectRights( right, left );
left.rightP = p;
// initialize the x node of P
XNodeP = new SearchVertex( SearchVertex.NodeType.X_POINT_P, p, line );
XNodeP.left = d;
assert d.trapezoid.check();
XNodeP.right = de == null ? handleQ( right, line, null ) : handleS( right, line, de );
redirectParents( XNodeP, d );
return XNodeP;
}
// degenerate case, p(x) -> s, or p(x) -> q
// the left endPoint of the new segment and
// the old one( the one already added into the map)
// is the same
XNodeP = de == null ? handleQ( left, line, null ) : handleS( left, line, de );
redirectParents( XNodeP, d );
return XNodeP;
}
我们先判断是不是Pi的退化情况,即Pi是不是已经存在于SS中:
if ( !left.leftP.equals( p ) )
如果是,我们直接将原梯形传到给下一个事件处理方法,并在最后返回这个事件处理得到的结点:
// degenerate case, p(x) -> s, or p(x) -> q
// the left endPoint of the new segment and
// the old one( the one already added into the map)
// is the same
XNodeP = de == null ? handleQ( left, line, null ) : handleS( left, line, de );
redirectParents( XNodeP, d );
return XNodeP;
如果不是,那先将原梯形左拆分:
// left separation
Trapezoid right = new Trapezoid( p, left.rightP, left.top, left.bottom );
Trapezoid.connectRights( right, left );
left.rightP = p;
之后创建一个X-Node存储Pi,设置其左孩子为左梯形(原梯形的指针),并递归处理右梯形,将它传给下一个事件处理方法:
// initialize the x node of P
XNodeP = new SearchVertex( SearchVertex.NodeType.X_POINT_P, p, line );
XNodeP.left = d;
assert d.trapezoid.check();
XNodeP.right = de == null ? handleQ( right, line, null ) : handleS( right, line, de );
最后将原梯形结点的父亲全部设置为新结点的父亲,所有父亲都包含这个新结点作为其中一个孩子:
redirectParents( XNodeP, d );
为高效地完成父子结点的重定向,我们规定叶结点存储其父亲结点的数据成员,也就是说,在叶结点层级,父子之间双向指向对方,不是父亲单向指向孩子,大家可以在SearchVertex类中找到这个数据成员:
public class SearchVertex extends Vertex {
// have parent pointer point to its parent nodes,
// use list, public final List<Vertex> neighbours in class Vertex,
// since may have more than one parents; merge vertex with fewer parents
public final List<Vertex> parents = super.neighbours;
// other code
}
2. 处理Q(线段右端点)
2.1 处理思路
处理完P端点之后,就是处理Q端点,这里的处理思路和P是对称的:处理Q端点,我们把原梯形保留为右梯形,把左梯形传递给后面的S事件处理方法。同样,我们给到思路归纳的图例:
2.2 代码解析
同样,处理Q事件的代码逻辑和P刚好对称,所以这里我们递归处理左梯形:
/**
* handle q when adding a new segment,
* this operation will partition
* the original trapezoid into two parts( vertical separation, right separation ):
* |
* left | right(origin)
* |
*
* and pass the left into handleQ or handleS
* */
// This method is only called when the segment crosses multiple trapezoids
// 这个公用方法是横跨多个梯形时,调用的
public static
void handleQ( SearchVertex d, Line line, Stack<SearchVertex> de ) {
SearchVertex newer = handleQ( d.trapezoid, line, de );
redirectParents( newer, d );
}
// code to do the separation and update
// 具体的拆分代码
static
SearchVertex handleQ( Trapezoid right, Line line, Stack<SearchVertex> de ) {
Vector p = line.startPoint;
Vector q = line.endPoint;
assert Vectors.sortByX( p, q ) <= 0;
SearchVertex XNodeQ = null;
// normal case: q -> s
if ( !right.rightP.equals( q ) ) {
// right separation
Trapezoid middle = new Trapezoid( p, q, right.top, right.bottom );
Trapezoid.connectLefts( middle, right );
right.leftP = q;
// initialize the x node of Q
XNodeQ = new SearchVertex( SearchVertex.NodeType.X_POINT_Q, q, line );
XNodeQ.left = handleS( middle, line, de );
XNodeQ.right = new SearchVertex( right );
XNodeQ.right.parents.add( XNodeQ );
assert XNodeQ.right.trapezoid.check();
return XNodeQ;
}
// degenerate case, q(x) -> s
// the right endPoint of the new segment and
// the old one( the one already added into the map)
// is the same
XNodeQ = handleS( right, line, de );
return XNodeQ;
}
接下来就剩下处理S事件,我们把这个内容留到下一节进行讲解。
上一节:(一):总思路分析
下一节:(三):处理S
3. 参考资料
- Computational Geometry: Algorithms and Applications
- 计算几何 ⎯⎯ 算法与应用, 邓俊辉译,清华大学出版社
4. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;