【问题标题】:Implementing Bentley–Ottmann Algorithm with an AVL tree使用 AVL 树实现 Bentley-Ottmann 算法
【发布时间】:2014-10-07 19:44:10
【问题描述】:

我在 java 中实现此方法时遇到问题。我在 Computational Geometry 3rd Edition 中专门使用 AVL BST 树来实现算法FINDINTERSECTIONS 的状态。书中的描述如下:

我遇到的问题是在HANDLEEVENTPOINT 中实施第 5 步。当事件点是一个交点时,那里的状态不再是完全有序的,因为对于交点线,它们在它们的交点处相交,需要在状态中进行交换。由于我使用的 BST 是 AVLTree,delete 方法失败,因为重新平衡方法需要元素的正确排序(即 delete 方法假定树是正确排序的,并根据顺序执行轮换以维护日志(n) 高度)。此外,我使用的状态将数据存储在节点而不是叶子中,如图所示。如果我理解正确的话,书上说任何一种树都可以使用。

【问题讨论】:

  • 按常规方式编写时,旋转代码甚至不会检查键。您能否更具体地说明问题所在?除非您使用的是不灵活的库,否则重复键应该不是问题(这是在事件点可能发生的最糟糕的情况)。
  • @DavidEisenstat 我认为您可能正在做某事,但我使用的删除方法需要首先找到元素,它通过将输入数据与树中的节点进行比较来找到它。 (即看remove方法here)。
  • 我会简单地使用来自java.util 包的现有数据结构,而不是使用第 3 方 AVL 树。例如,您的事件队列T 可以保存TreeMap<Point, List<Event>>Event 将包含(至少)一个线段和一个类型(START、END、INTERSECTION 之一)。
  • @BartKiers 出于调试目的,也许我会按照您的建议使用现有的数据结构。现在我正在使用我在java中实现的贫困AVLTree,因为我正在尝试了解更多关于数据结构的信息。我认为大卫可能让我走上了正确的道路,因为再平衡不应该检查关键值(它纯粹基于我认为的树的结构)。我需要重新检查我的代码。
  • 酷,祝你好运@jucestain。

标签: java algorithm computational-geometry avl-tree


【解决方案1】:

首先使用平衡二叉搜索树的叶子版本,无论是红黑还是 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 和 in think CGAL 的做法。

重复项在事件队列中的处理方式与扫描线状态结构不同。对于事件队列,只需将项目的链接列表添加到叶子(参见 Brass 的书)。这里每个叶子对应于一个事件点,并有一个所有片段的列表,其起点与事件点相同。所以有些会有空列表,如交叉事件点和结束事件点。至少有些实现是这样做的。

用于扫描状态结构。重叠的平行段由段 id 区分。他们不会在您正在阅读/参考的书中谈论这些。但是,LEDA 书会告诉您如何处理这些问题。因此,即使扫描状态树比较器表示两个段具有相同的端点和方向,比较器通过使用段数据库、数组或其他任何内容中的段索引来打破平局。

一些更重要的点:

台球积分!这个公共点池是基本的,然后构成段并用于所有数据结构。使用池可以通过仅测试身份来测试点相等性!这样可以避免使用会减慢速度并可能引入错误的比较器。

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

当检查段是否属于同一个包或是否属于您有疑问的三个集合的成员时(即开始、结束或与扫描线上的事件点相交),请勿使用比较器。

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

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

这确实是关键,那本书根本没有指出这一点,但不幸的是,这不是它的意图,因为这个实现很棘手。另外,请注意,本书在导航树的内部节点中增加了一个额外的链接,指向相关的叶节点。这只会使查找速度更快,但如果您不熟悉叶子树,则可能不明显。叶树中的一个键经常被发现两次,在叶节点和树的内部节点的其他地方。

终于

像 LEDA/CGAL 这样的包使用精确的算法来让事情正常工作。 LEDA 开发人员花了 10 年时间才把事情做好,这主要是由于使用了精确的算术。您可能对用于定向的基本交叉产品测试没问题,但如果您需要一个精确版本,那么您可以在他的网站上找到 Jonathan Shewchuk 教授的精确算术包。

我猜你的书只是将所有这些作为“读者/学生的练习”而忽略了。哈哈。

【讨论】:

  • 谢谢,我会调查的。
  • 应该是 LEDA 书的第 10 章而不是第 9 章。
  • ** 也许这与/回答您的问题有关。** LEDA 中扫描线状态树的比较器适用于节点键值,这些节点键值与当前事件点一起进行排序。所以相同的键值(即段)会因为事件点的变化而有不同的排序。在两个线段的交点事件点之前以及稍后在该事件点处,这些键值保持不变,但由于事件点的变化而排序不同。请参阅第 10 章,了解 LEDA 中的比较器是如何构建的。
  • 顺便说一句,没有删除问题!该树的顺序正确,因为您的状态尚未更新以考虑该顺序更改。在下一行重新插入时会发生这种情况。无论如何,您的图书算法建议还存在其他实施问题。
  • 是的,我实际上是自己编写了 AVL 树,并意识到我是根据它们的键值而不是树的结构来进行旋转的。我修复了它,最终解决了问题,但您的建议对我仍然遇到的双精度错误非常有帮助。
【解决方案2】:

更新:在您从该书中发布的算法中,用于反转相交段顺序的交换是通过删除然后重新插入完成的。 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-tree 在 LEDA** 中使用的搜索树的速度比较中表现最好。

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

【讨论】:

    猜你喜欢
    • 2011-12-28
    • 1970-01-01
    • 2012-10-25
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多