用AVL树实现Bentley-Ottmann算法

时间:2014-10-07 19:44:11

标签: java algorithm computational-geometry avl-tree

我在java中实现此方法时遇到问题。我在计算几何第3版中使用AVL BST树专门实现了算法FINDINTERSECTIONS的状态。本书的描述如下:

enter image description here

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

2 个答案:

答案 0 :(得分:3)

首先使用平衡二进制搜索树的叶版本,无论是红黑还是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认为这样做的方式。

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

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

更重要的一点:

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

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

当检查段是属于同一束还是三组中的成员时,您有疑问(即,扫描线上的开始,结束或交互点和事件点),请勿使用比较器。

相反,使用属于同一个包的段可以拥有一些"信息属性"在列表中说,当段与事件点相交时指向事件队列,或者如果段与后继者重叠则指向列表中的后继项,否则指向null。因此,您需要在事件队列与sweepline状态结构之间进行一些交叉链接。您的套装和套装非常快速且易于查找。使用状态树转到链接列表关联的开头或结尾,并通过非常简单的测试逐项完成。

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

这真的是关键,本书并没有指出这一点,但不幸的是,这并不是因为这个实现很棘手。另请注意,本书通过树的内部节点中的额外链接来增加导航树,该链接指向关联的叶节点。这只会使查找速度提高一些,但如果您不熟悉叶树,则可能并不明显。叶子树中的密钥经常在叶子节点和树的内部节点中的其他位置找到两次。

最后

像LEDA / CGAL这样的软件包使用精确算术来使事情顺利运行。 LEDA开发人员用了10年的时间才把事情做好,这主要是因为使用了精确算法。您可以使用基本的跨产品测试进行定位,但如果您需要精确版本,那么您可以在他的网站上找到Jonathan Shewchuk教授的精确算术包。

我想你的书只是把这一切都留作了读者/学生的练习。" LOL。

答案 1 :(得分:0)

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

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