繁体   English   中英

用AVL树实现Bentley-Ottmann算法

[英]Implementing Bentley–Ottmann Algorithm with an AVL tree

我在用Java实现此方法时遇到问题。 我正在使用AVL BST树作为状态, FINDINTERSECTIONS在Computational Geometry 3rd Edition中实现算法FINDINTERSECTIONS 这本书的描述如下所示:

在此处输入图片说明

我遇到的问题是在HANDLEEVENTPOINT实现步骤5。 当事件点是相交点时,状态不再在那里完全排序,因为对于相交线,它们在相交点处相交并且需要交换状态。 由于我使用的BST是AVLTree,所以删除方法失败,因为重新平衡方法要求元素的正确排序(即删除方法假定树的排序正确,并相对于该顺序执行旋转以维护日志(n)高度)。 另外,我正在使用的状态将数据存储在节点中,而不是如图所示的叶子中。 如果我理解正确的话,这本书说这两种树都可以使用。

首先,使用平衡的二进制搜索树的叶版本,无论是红黑色还是AVL。 我用红黑的。

获得Peter Brass的有关高级数据结构的书,因为您几乎无法在所有标准算法/数据结构书中的这些叶子树上找到任何内容。 我相信它们也被称为外来树。

http://www-cs.engr.ccny.cuny.edu/~peter/

此外,您还可以查看Mehlhorn和Sanders撰写的“算法和数据结构:基本工具箱”,它进入了“排序序列”数据结构。 使用树木时,它们仅在叶树的帮助下创建这些树。 这些也是开发LEDA的一些人。

还要看在线上的LEDA书,它有一章关于如何实现该算法以及如何处理所有“问题案例”。 我认为这是第9章,很难理解,可能是因为英语不是作者的母语... PITA!

http://people.mpi-inf.mpg.de/~mehlhorn/LEDAbook.html

您可以将叶节点数据项双重链接在一起,并且已经创建了一个排序树序列,并使用树作为导航结构链接到项的链接列表。 这就是LEDA和CGAL认为的方法。

在事件队列中,重复项的处理方式与清除行状态结构的处理方式不同。 对于事件队列,只需将链接的项目列表添加到叶子(请参阅Brass的书)。 在这里,每个叶子都对应一个事件点,并具有所有段的列表,其起点与事件点相同。 因此,有些将具有空列表,例如交集事件点和结束事件点。 至少这是某些实现的方式。

用于扫描状态结构。 重叠的并行段通过“段ID”来区分。 他们不会在您正在阅读/参考的书中谈论这些。 但是,LEDA书告诉您如何处理这些内容。 因此,即使扫描状态树比较器说两个段具有相同的端点和方向,但比较器还是通过使用段数据库,数组或其他内容中的段索引来破坏联系。

一些更重要的要点:

池点! 这个通用的点池是基本的,然后构成了这些段,并在所有数据结构中使用。 使用该池仅通过测试身份就可以测试点是否相等! 这样可以避免使用比较器,因为它会使速度变慢并且可能引入错误。

关键是要避免尽可能多地使用树比较器。

在检查线段是否属于同一束或是否为三个线组的成员时(例如,扫描线上的起点,终点或与之交叉,并且与事件点有交集),请不要使用比较器。

取而代之的是,使用以下事实:属于同一个包的段可以在列表中具有一些“信息属性”,即当段与事件点相交时指向事件队列,或者如果该段指向列表中的后继项与后继重叠,否则指向null。 因此,您将需要事件队列与扫掠线状态结构之间的一些交叉链接。 您的套装和套装非常容易找到。 转到与状态树关联的链表的开头或结尾,并通过一个非常简单的测试逐项进行检查。

底线。 正确实现排序后的序列/平衡二叉树数据结构,并在实施Bentley-Ottmann其余部分之前进行大量工作。

这确实是关键,而那本书根本没有指出这一点,但是不幸的是,这并不是意图,因为这种实现是棘手的。 另外,请注意,该书在导航树的内部节点中指向关联的叶节点的额外链接增强了导航树。 这样只会使查找速度更快,但是如果您不熟悉叶树,则可能看不到。 叶树中的一个关键字通常在树的叶节点和内部节点的其他位置被发现两次。

最后

诸如LEDA / CGAL之类的软件包使用精确的算法才能正常工作。 LEDA开发人员花了10年的时间才把事情弄对,这主要是由于使用了精确的算法。 您可能会接受用于定向的基本跨产品测试,但是如果您需要一个精确的版本,则可以在他的网站上找到Jonathan Shewchuk教授的精确算术软件包。

我想您的书只是把所有这些作为“读者/学生的锻炼”而忽略了。 大声笑。

更新:在那本书的发布算法中,通过删除然后重新插入来完成相交段顺序的交换。 LEDA使用reverse_items()进行这些交换。 这是在不使用比较器的情况下执行节点和项的子序列反转的更有效方法。 搜索_rs_tree.c以查看LEDA源或参见下文。

// reverse a subsequence of items, assuming that all keys are
// in the correct order afterwards
//
void rs_tree::reverse_items( rst_item pl, rst_item pr )
{
  int prio ;
  register rst_item ppl = p_item(pl),  // pred of pl
  ppr = s_item(pr),  // succ of pr
  ql, qr ;

  while( (pl!=pr) && (pl!=ppl) ) {  // pl and pr didnt't
// met up to now
// swap all of pl and pr except the key
// swap parents
    ql = parent(pl) ;  qr = parent(pr) ;  
    if( pl==r_child(ql) )
      r_child(ql) = pr ;
    else
      l_child(ql) = pr ;
    if( pr==r_child(qr) )
      r_child(qr) = pl ;
    else
      l_child(qr) = pl ;
    parent(pl ) = qr ; parent(pr) = ql ;  
// swap left children
    ql = l_child(pl) ;  qr = l_child(pr) ;
    if( ql != qr ) {    // at least one exists
      l_child(pl) = qr ; parent(qr) = pl ;
      l_child(pr) = ql ; parent(ql) = pr ;
    }
// swap right children
    ql = r_child(pl) ;  qr = r_child(pr) ;
    if( ql != qr ) {    // at least one exists
      r_child(pl) = qr ; parent(qr) = pl ;
      r_child(pr) = ql ; parent(ql) = pr ;
    }
// swap priorities
    prio = pl->prio ;  pl->prio = pr->prio ;  
    pr->prio = prio ;
// swap pred-succ-ptrs
    s_item(ppl) = pr ;  p_item(ppr) = pl ;
    ql = pl ;  pl = s_item(pl) ;   // shift pl and pr
    qr = pr ;  pr = p_item(pr) ;
    s_item(ql) = ppr ;  p_item(qr) = ppl ;
    ppl = qr ;  ppr = ql ;  // shift ppl and ppr
  }
  // correct "inner" pred-succ-ptrs
  p_item(ppr) = pl ;  s_item(ppl) = pr ;
  if( pl==pr ) {    // odd-length subseq.
    p_item(pl) = ppl ;  s_item(pr) = ppr ;  
  }
}

另外:排序的序列数据结构可以使用AVL树,ab树,红黑树,展开树或跳过列表。 a = 2,b = 16的ab树在LEDA **中使用的搜索树的速度比较中表现最好。

** S.纳伯 LEDA中搜索树数据结构的比较。 个人交流。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM