0

I'm having a problem implementing this method in java. I'm specifically implementing the algorithm FINDINTERSECTIONS in Computational Geometry 3rd Edition using an AVL BST tree for the status. The description from the book is shown below:

enter image description here

The problem I'm having is implementing step 5 in HANDLEEVENTPOINT. When the event point is an intersection, the status is no longer totally ordered there, because for intersection lines, they cross at their intersection point and need to be swapped in the status. Since the BST I'm using is an AVLTree, the delete method fails because the rebalancing method requires proper ordering of the elements (i.e. the delete method assumes the tree is properly ordered, and performs rotations with respect to the order in order to maintain log(n) height). Also, the status I'm using stores the data in the nodes instead of the leaves as shown in the figure. If I understand correctly, the book says that either kind of tree can be used.

JustinBlaber
  • 4,629
  • 2
  • 36
  • 57
  • When written the usual way, the code to rotate doesn't even examine the keys. Could you be more specific about what the problem is? Unless you're working with an inflexible library, duplicate keys shouldn't be a problem (which is the worst that can happen at event points). – David Eisenstat Oct 07 '14 at 19:50
  • @DavidEisenstat I think you may be on to something, but the delete method I'm using needs to find the element first, and it finds it by comparing the input data to the nodes in the tree. (i.e. look at the `remove` method [here](http://cs.nyu.edu/courses/spring07/V22.0102-002/BinarySearchTree.java)). – JustinBlaber Oct 07 '14 at 19:55
  • I would simply use existing data structures from the `java.util` package instead of a 3rd party AVL tree. Your event queue, `T`, could hold a `TreeMap>`, for example. An `Event` would hold (at least) a line segment and a type (one of START, END, INTERSECTION). – Bart Kiers Oct 07 '14 at 20:03
  • @BartKiers For debugging purposes maybe I'll use an existing data structure as you suggest. Right now I'm using a poverty AVLTree I implemented in java since I'm trying to learn more about data structures. I think David might have set me on the right path since the rebalancing shouldnt be checking key values anyway (it's purely based on the structure of the tree I think). I need to recheck my code. – JustinBlaber Oct 07 '14 at 20:08
  • With floating-point inaccuracies, you may actually have wrongly ordered keys. – David Eisenstat Oct 07 '14 at 21:01
  • @DavidEisenstat, Yea, that's a good point. I've been using `Math.abs(x-y) < EPSILON` for equality tests but that's also been giving me separate issues. I was going to ask another question about how to go about this in a question in the future after I fix this deletion issue first. – JustinBlaber Oct 07 '14 at 21:18
  • @DavidEisenstat I added a follow up question on testing for equality of double precision numbers [here](http://stackoverflow.com/questions/26638786/general-strategies-for-testing-for-equality-of-double-precision-numbers-in-compu). If you could comment that would be greatly appreciated. – JustinBlaber Oct 29 '14 at 19:22

2 Answers2

4

First off use a leaf version of a balanced binary search tree whether red-black or AVL. I used red-black.

Get Peter Brass's book on advanced data structures because you will have trouble finding anything on these leaf trees in virtually all the standard algorithm / data structure books. I believe they are also called exogenous trees.

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

Also, you can look at "Algorithms and Data Structures: The Basic Toolbox" by Mehlhorn and Sanders which goes into the "sorted sequence" data structure. They create these with the help of only leaf trees when trees are used. These are also some of the folks that developed LEDA.

Also look at the LEDA book online bc it has a chapter on how to implement this algorithm and how to handle ALL the "problem cases." I think this is chapter 9 and is a bit hard to follow maybe because English is not the native tongue of the authors ... PITA!!

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

You can doubly link the leaf nodes data items together and you have created a sorted sequence with the tree as a navigation structure to the linked list of items. That is how LEDA and in think CGAL do this.

Duplicate items are handled differently in the event queue than the sweep line status structure. For the event queue, just add to a leaf a linked list of items (see Brass's book). Here each leaf corresponds to an event point and has a list of all segments with a starting-end-point this that same as the event point. So some will have empty lists like intersection-event-points and ending-event-points. At least that is how some implementations do this.

For the sweep status structure. Overlapping parallel segments are differentiated by say segment ids. They do not talk about these in the book you are reading/referencing. However, the LEDA book tells you how to handle these. So even though sweep status trees comparator says two segments have the same end-point and orientation, the comparator breaks the tie by using the segments indexes in the segments database, array or whatever.

Some more important points:

Pool points! This common pool of points are basic and then make up the segments and are used in all the data structures. Using the pool allows one to test for point equality by just testing for identity! This avoids using a comparator which slows things down and can introduce errors.

It is key that you avoid using the tree comparators as much as possible.

When checking if segments belong to the same bundle or are members of the three sets you are having a question about (i.e, start, end or interesect with and event point on the sweep-line), DO NOT USE THE COMPARATOR.

Instead, use that fact that segments belonging to the same bundle can have some "information property" say in the list that either points to the event queue when a segment intersects an event point, or points to the successor item in the list if the segment overlaps the successor, or points to null otherwise. So you will need some cross-linking between the event queue with the sweepline status structure. Your sets and bundles are the very fast and easy to find. Go to the start or end of the linked-list associate with the status tree and go through it item by item with a very simple test.

BOTTOM LINE. Get the sorted sequence / balanced binary tree data structure right and work on that a lot before implementing the rest of Bentley-Ottmann.

This is really the key and that book does not point that out at all but unfortunately that isn't it's intent since this implementation is tricky. Also, note that the book augments the navigation tree with an extra link in the internal nodes of the tree that point to associated leaf nodes. This just makes find a bit faster but may not be apparent if you are not familiar with leaf trees. A key in a leaf tree is often found twice, at the leaf node and elsewhere in an internal node of the tree.

FINALLY

Packages like LEDA/CGAL use exact arithmetic for things to work well. It took LEDA developers 10 years to get things right and that was mostly was due to using exact arithmetic. You maybe OK with a basic cross-product test used for orientation but if you need an exact version then you can find Prof. Jonathan Shewchuk exact arithmetic package on his site.

I guess your book just left all this out as an "exercise for the reader/student." LOL.

  • Should be chapter 10 not 9 of the LEDA book. – Mark Szlazak Nov 30 '14 at 17:57
  • ** Maybe this relates/answers your question.** The comparator for the sweep line status tree in LEDA works on node key values that are segments along with the current event point to do ordering. So the same key values (i.e. segments) will have different ordering because of changes of the event point. Before the intersection event-point of two segments and later at the that event-point, these key values remain the same but get ordered differently because of the changed event-point. See chapter 10 on how the comparator is constructed in LEDA. – Mark Szlazak Nov 30 '14 at 18:09
  • BTW there is no deletion issue! The tree is ordered correctly bc your status has not yet been updated to take that change of order into account. That happens with the re-insert in the next line. In any case, there are other implementation issues with your books algorithm advice. – Mark Szlazak Dec 02 '14 at 19:08
  • Yea, I actually wrote the AVL tree myself and realized I did the rotations based on their key value instead of by the structure of the tree. I fixed it which ended up fixing the problem, but your suggestions have been very helpful with the double precision errors I'm still getting. – JustinBlaber Dec 03 '14 at 20:58
  • See is Julian Smith's work helps if you use plain already in the box inexact computation. His technical report, "Towards Robust Inexact Geometric Computation": http://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-766.pdf – Mark Szlazak Dec 04 '14 at 18:01
0

UPDATE: In your posted algorithm from that book, the swap for reversing intersecting segment order is done via delete and then with a re-insert. LEDA uses reverse_items() for these swaps. It's a more efficient way of doing sub-sequence reversals of nodes and items without the use of the comparator. Search for _rs_tree.c to see LEDA source or see below.

// 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 ;  
  }
}

ADDITIONALLY: Sorted sequence data structures can use AVL trees, ab-trees, red-black trees, splay trees, or skip lists. An ab-tree with a = 2, b = 16 fared best in speed comparison of search-trees used in LEDA**.

** S. Naber. Comparison of search-tree data structures in LEDA. Personal communication.

mszlazak
  • 59
  • 1
  • 6