点定位:如何拆分更新梯形图和二分搜索结构 · (三):处理S(Update trapezoidal map and search structure in point location)

点定位:如何拆分更新梯形图和二分搜索结构 ·(三):处理S

1. 处理S(线段)

1.1 思路分析

S的处理相对比较简单,我们对其进行水平拆分,原梯形保留为上梯形,且没有退化的情况:

点定位:如何拆分更新梯形图和二分搜索结构 · (三):处理S(Update trapezoidal map and search structure in point location)

这里我们着重讨论的是,合并梯形(Trim Wall),笔者的拆分思路如下:

从左往右,依次进行合并;也就是左边吞并右边的梯形;

如果当前梯形的rightP在Si的上面,则合并下梯形;如果在Si的下面,则合并上梯形;如果刚好在Si上面,则什么都不做(即为处理Qi的情况);

点定位:如何拆分更新梯形图和二分搜索结构 · (三):处理S(Update trapezoidal map and search structure in point location)

上图这个例子,如果我们经过A梯形,它的rightP在Si的上方,所以裁剪Si下面的蓝色虚线。同样当我们经过梯形B,它的rightP在Si的下方,所以裁剪Si上面的蓝色虚线。

1.2 代码解析

1.2.1 拆分

代码解析部分,我们同样还是先来看看整体的处理S的代码,然后重点看看拆分合并的代码逻辑:

/**
 * handle S when adding a new segment,
 * this operation will partition the original trapezoid into two parts( horizontal separation ):
 * top(origin)
 * ----------
 *   bottom
 *
 * this method will also handle trimming walls:
 * first, store trapezoids, top and bottom, to be merged later.
 * secondly, merge current top and bottom into previously stored ones.
 * */

// This method is only called when the segment crosses multiple trapezoids
public static
void handleS( SearchVertex d, Line line, Stack<SearchVertex> de ) {
    SearchVertex newer = handleS( d.trapezoid, line, de );
    redirectParents( newer, d );
}

// code to do the separation and update
public static
SearchVertex handleS( Trapezoid middle, Line line, Stack<SearchVertex> de ) {
    Vector p = line.startPoint;
    Vector q = line.endPoint;
    assert Vectors.sortByX( p, q ) <= 0;
    Vector rightP = middle.rightP;

    // horizontal partition
    Trapezoid top = new Trapezoid( p, q, middle.top, line );
    Trapezoid bottom = new Trapezoid( p, q, line, middle.bottom );
    Trapezoid.mergeUppers( top, middle );
    Trapezoid.mergeLowers( bottom, middle );

    // p -> q -> s, no need to trim wall,
    // p -> s, p -> q, s -> s, q -> s need,
    if ( de != null ) {
        // store trapezoids, top and bottom, to be merged later.
        addTrims( top, bottom, rightP, line, de );

        // merge current top and bottom into previously stored ones.
        if ( de.size() > 1 ) {
            SearchVertex trim = trim( de );
            // get trimmed top and bottom
            top = trim.top;
            bottom = trim.bottom;
        }
    }

    // initialize the y node
    SearchVertex YNode = new SearchVertex( line );
    // top trapezoid has been added before?
    if ( top.vertex == null )
        // no, create a new one
        YNode.left = new SearchVertex( top );
    else  {
        // yes, the y node points to it
        assert top.vertex.trapezoid == top;
        YNode.left = top.vertex;
    }
    YNode.left.parents.add( YNode );

    // top trapezoid has been added before?
    if ( bottom.vertex == null )
        // no, create a new one
        YNode.right = new SearchVertex( bottom );
    else {
        // yes, the y node points to it
        assert bottom.vertex.trapezoid == bottom;
        YNode.right = bottom.vertex;
    }
    YNode.right.parents.add( YNode );

    return YNode;
}

首先我们进行水平拆分:

// horizontal partition
Trapezoid top = new Trapezoid( p, q, middle.top, line );
Trapezoid bottom = new Trapezoid( p, q, line, middle.bottom );
Trapezoid.mergeUppers( top, middle );
Trapezoid.mergeLowers( bottom, middle );

然后保存当前需要合并的梯形,如果有两个待合并的梯形,则进行合并操作:

// p -> q -> s, no need to trim wall,
// p -> s, p -> q, s -> s, q -> s need,
if ( de != null ) {
    // store trapezoids, top and bottom, to be merged later.
    addTrims( top, bottom, rightP, line, de );

    // merge current top and bottom into previously stored ones.
    if ( de.size() > 1 ) {
        SearchVertex trim = trim( de );
        // get trimmed top and bottom
        top = trim.top;
        bottom = trim.bottom;
    }
}

最后生成相应的叶结点,但是注意这里的叶结点可能在之前已经初始化了,因为合并的关系,会有多个父结点指向同一个叶结点的情况:

// initialize the y node
SearchVertex YNode = new SearchVertex( line );
// top trapezoid has been added before?
if ( top.vertex == null )
    // no, create a new one
    YNode.left = new SearchVertex( top );
else  {
    // yes, the y node points to it
    assert top.vertex.trapezoid == top;
    YNode.left = top.vertex;
}
YNode.left.parents.add( YNode );

// bottom trapezoid has been added before?
if ( bottom.vertex == null )
    // no, create a new one
    YNode.right = new SearchVertex( bottom );
else {
    // yes, the y node points to it
    assert bottom.vertex.trapezoid == bottom;
    YNode.right = bottom.vertex;
}
YNode.right.parents.add( YNode );

1.2.2 合并

最后我们来看看合并(trim wall)的代码,首先是判断是合并上梯形还是下梯形,我们使用toLeft测试进行判断:

/**
 * First process of trimming walls:
 * store trapezoids, top and bottom, to be merged later.
 * */

public static
void addTrims( Trapezoid top, Trapezoid bottom, Vector rightP,
               Line line, Stack<SearchVertex> de  ) {
    SearchVertex trim = new SearchVertex( SearchVertex.NodeType.TRIMMING );

    double res = Triangles.areaTwo( line.startPoint, line.endPoint, rightP );
    // if rightP lies above s, trim lower wall
    if ( MyMath.isPositive( res ) ) {
        bottom.rightP = null;
        top.rightP = rightP;
        trim.isTrimmingTop = false;
    }
    // if rightP lies below s, trim upper wall
    else if ( MyMath.isNegative( res ) ) {
        // assert rightP lies below s
        bottom.rightP = rightP;
        top.rightP = null;
        trim.isTrimmingTop = true;
    }

    // if rightP lies on s, do nothing
    // this happens when handling Q,
    // just trim wall, no need to add ones to be trimmed
    trim.top = top;
    trim.bottom = bottom;
    de.add( trim );
}

我们根据当前梯形rightP在Si的方位,进行相应的处理。这里的代码和我们之前的讲解是基本一致的,大家可以结合注释进行理解。接下来就是合并的代码逻辑:

/**
 * Second process of trimming walls:
 * secondly, merge current top and bottom into previously stored ones.
 *
 * */

public static
    SearchVertex trim( Stack<SearchVertex> de ) {
    assert de.size() == 2;
    SearchVertex cur = de.pop();
    SearchVertex prev = de.pop();
    assert cur.type == SearchVertex.NodeType.TRIMMING;
    assert prev.type == SearchVertex.NodeType.TRIMMING;

    // trimming top
    if ( prev.isTrimmingTop ) {
        assert check( prev.top, cur.top );
        // merge tops
        Trapezoid.mergeRights( prev.top, cur.top );
        // current top was eaten by previous top
        cur.top = prev.top;
        // link bottoms
        Trapezoid.setUppers( prev.bottom, cur.bottom );
        cur.bottom.leftP = prev.bottom.rightP;
        // at this point, prev.bottom has been all set up
        assert prev.bottom.check();
    }
    // trimming bottom
    else {
        assert check( prev.bottom, cur.bottom );
        // merge bottoms
        Trapezoid.mergeRights( prev.bottom, cur.bottom );
        // current bottom was eaten by previous bottom
        cur.bottom = prev.bottom;
        // link tops
        Trapezoid.setLowers( prev.top, cur.top );
        cur.top.leftP = prev.top.rightP;
        // at this point, prev.top has been all set up
        assert prev.top.check();
    }

    de.push( cur );
    return cur;
}

我们依据之前的判断,来合并两个上梯形,或两个下梯形。这里需要注意两点:

  1. 左梯形吞并右梯形,无一例外;
  2. 邻居重定向;

推荐大家根据代码的顺序,自己画一个例子进行理解,代码不难,但是需要费一番功夫才能弄懂究竟是如何进行合并操作的,特别需要注意合并和重定向方向,方向问题在计算几何里面尤为重要。

2. 二分搜索结构(Search Structure)

SS基本没有太多可以讨论,它基本就是一个二叉搜索树(但不是严格意义上的BST,是类BST的DAG),所以更新、搜索操作和BST都非常相似,熟悉BST的同学理解起来应该没有什么太大的难度。这里我们提一下对于退化情况的处理,也就是Pi或Qi已经存在于SS中。如果Pi位于X-Node的垂直线上,我们认为Pi在X-Node的右边:

case X_POINT_Q, X_POINT_P -> {
    if ( Vectors.isLeft( root.point, p ) )
        return get( root.left, line );

    // whenever p lies on the vertical line of an x-node,
    // we decide that it lies to the right.
    return get( root.right, line );
}

另一种情况,如果Pi位于某条线段上面(S,Y-Node),我们则比较S和Si的斜率,如果Si的斜率更大,则认为Pi在S的上方,反之则在S的下方:

case SEGMENT -> {
    double res = Triangles.areaTwo( root.line.startPoint, root.line.endPoint, p );

    // whenever p lies on a segment s of a y-node
    // (this can only happen if si shares its left endpoint, p, with s)
    // we compare the slopes of s and si; if the slope of si is larger,
    // we decide that p lies above s, otherwise we decide that it is below s.
    if ( MyMath.isEqualZero( res ) ) {
        if ( Lines.compareBySlope( line, root.line ) > 0 )
            return get( root.left, line );
        else return get( root.right, line );
    }
    else if ( MyMath.isPositive( res ) )
        return get( root.left, line );

    return get( root.right, line );
}

这些处理退化情况的代码,大家可以在SearchStructure类里面找到。到此,我们就基本讲解完毕怎么去拆分更新梯形图和SS,如果大家有任何的疑问,可以留言或者联系我哒~

上一节:(二):处理P和Q

3. 附录:代码

3.1 算法

Description Entry method\File
Build trapezoidal Map and search structure(创建梯形图和二分搜索结构) SearchStructure trapezoidalMap( List<Line> lines, SearchVertex box )
Point Locatoin(点定位) public SearchVertex get( Line line )
Program ( including visualization, 可视化程序 ) Programming Assignment 3 - Trapezoidal Map and Planar Point Location

3.2 数据结构

Description Entry File/Package
Trapezoidal Map( 梯形图 ) public class Trapezoid
Search Structure( Tree-like DAG, 二分搜索结构,树状DAG ) public class SearchStructure

4. 参考资料

  1. Computational Geometry: Algorithms and Applications
  2. 计算几何 ⎯⎯ 算法与应用, 邓俊辉译,清华大学出版社

5. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


点定位:如何拆分更新梯形图和二分搜索结构 · (三):处理S(Update trapezoidal map and search structure in point location)

上一篇:Python的环境搭建与语法入门_2


下一篇:springboot log4j2