![]() |
AuD
Lecture 'Algorithmen und Datenstrukturen' (code examples)
|
Base class for a binary search tree. More...
Base class for a binary search tree.
Please keep in mind that this is an educational implementation. It is not optimized for a particular application!
The internal node (nested class aud.BinarySearchTree.Node) representation is based on aud.BinaryTree!
On the one hand, this choice enables us to reuse functionality. On the other hand, there are some disadvantages due to extra data that is stored or extra indirections for method calls.
BinarySearchTree stores head as a dummy node (member head_), which has has by definition a smaller key than any other node.
This implementation does not use dummy nodes as leaves!
Note that aud.BinaryTree#getLeft and aud.BinaryTree#getRight also update links to parents! While this is convenient in many cases, it requires special care in other cases! (Explicitly setting null references, order of operations matters "even more".)
Each aud.BinarySearchTree.Node stores a key-value pair of type aud.BinarySearchTree.Entry as node data.
BinarySearchTree is a generic class, and Key and Value are provided as type parameters.
The Key type must be comparable, i.e., extend Comparable<key>.
The rationale of storing key-value pairs is that this way the search tree can be used to implement a set or a map:
find or insert). A typical application for maps are dictionaries. Keys are compared by compareKeys, which treats null keys as smallest possible keys. Then head's key equals null by definition.
The method find searches a an entry for a given key.
The core function used for finding (find) a node by key and also for finding an insertion point (for insert) is findLowerBound). It returns a aud.BinarySearchTree.LowerBound instance (which stores the node and some additional information).
The method findEntry (and similarly getMinimum and getMaximum) returns a aud.BinarySearchTree.Cursor rather than a node.
aud.BinarySearchTree.Cursor provides a reference to nodes in the tree, i.e., allows access to its key and value.
The iterator() method implements Iterable<Cursor>, and this inorder traversal iterator provides Cursor instances. (With some more work, we could alternatively provide different iterators over keys, values, and key-value pairs/entries.)
Although this is not a balanced tree, BinarySearchTree provides methods for local rebalancing. These are
restructure based on the implementation in Goodrich and Tamassia (2nd ed, chapter 7.4, p. 269).rotateLeft single rotation, right-right case rotateRight single rotation, left-left case rotateLeftRight double rotation, left-right case rotateRightLeft double rotation, right-left case All of these call the auxiliary method setupBalancedSubtree (as does restructure) to perform the rotations. Note that an implementation that works exclusively with rotations (not restructuring), would probably implement the double rotations as a combination of two single rotations.
Especially restructure is used for balancing by sub-classes, e.g., aud.AVLTree.
The method restructure(char,Key) is provided for testing the above methods interactively (see aud.example.BinarySearchTreeExample).
Note that we excessively use the fact that an extra reference to the node's parent is stored. In fact, this allows a simple bottom-up processing as by restructure. A more (memory) efficient implementation would avoid these references at the cost of a more complex implementation!
insert provides a callback onInsert to notify a subclass of a change of the tree. Similarly, remove invokes onRemove.
These callback can be used by sub-classes for balancing and visualization/output.
Definition at line 137 of file BinarySearchTree.java.