Advanced Search Trees
Table of Contents
1 Reading
After watching the video, read Bailey 14.5 for an overview of one advanced variation of a binary search tree: the splay tree. Optionally read 14.6 for splay-tree implementation details and 14.7 for a description of a type of self-balancing tree: the red-black tree.
2 AVL Trees
An AVL tree (named after inventors Adelson-Velsky and Landis) is a self-balancing binary search tree.
- Structural properties
- Binary search tree property (nodes to the left are smaller, nodes to the right are larger)
- NEW: balance property
- The difference in heights between the left subtree and the right subtree must be at most 1
- Result:
- Worst-case height is \(O(\log n)\)
A valid AVl tree (blue numbers are the height at each node):
Not a valid AVL tree (red height numbers indicate nodes with children out of balance)
How do we enforce the balance property?
- As we insert and delete elements, we need to:
- Track balance
- Detect imbalance
- Restore balance
We will need to store height at each node:
Searching the tree works exactly the same as it does for a BST For adding
- we first add as normal for a BST
- then fix any resulting imbalance
More specifically,
- Insert the new node as in a BST (a new leaf)
- For each node on the path from the root to the new leaf, the insertion may (or may not) have changed the node's height
- So after recursive insertion in a subtree, detect height imbalance and perform a rotation to restore balance at that node
All the action is in defining the correct rotations to restore balance!
- Single rotation: The basic operation we'll use to rebalance
- Move child of unbalanced node into parent position
- Parent becomes the "other" child (always okay in a BST!)
- Other subtrees move in only way BST allows.
A rotation is a constant time (\(O(1)\)) operation! Like removing from a BST, there are several different cases to handle when restoring balance to an AVL Tree. We won't go through them in detail, but we'll look at some animations to get a sense of what's going on.
There are also other kinds of balanced binary search trees. Two common ones, splay trees and red-black trees, are discussed in Bailey. AVL Trees provide the fastest searches because they are strictly balanced. Red-black trees and splay trees have more relaxed invariants, and so are faster at adding and removing nodes with fewer rotations. Java's TreeMap class uses a red-black tree.
2.1 Animations
https://visualgo.net/en/bst (select AVL Tree in the bar at the top of the page)
2.2 Analysis
Now that we have guaranteed that a tree with \(n\) nodes will have height \(\log n\), our search tree performance is looking pretty good! Not as efficient as a hash table for some operations, but able to efficiently provide operations related to the sorted order of the keys that a hash table is unsuited for. Here's a chart of the big-O running time for various structures on different Map operations:
Operation | Unsorted array | Sorted array | Hash table | Balanced BST (e.g., AVL Tree) |
---|---|---|---|---|
contains(Key key) |
\(O(n)\) | \(O(\log n)\) | \(O(1)\) | \(O(\log n)\) |
get(Key key) |
\(O(n)\) | \(O(\log n)\) | \(O(1)\) | \(O(\log n)\) |
put(Key key, Value val) |
\(O(1)\) | \(O(n)\) | \(O(1)\) | \(O(\log n)\) |
minKey() |
\(O(n)\) | \(O(1)\) | \(O(n)\) | \(O(\log n)\) |
maxKey() |
\(O(n)\) | \(O(1)\) | \(O(n)\) | \(O(\log n)\) |
iterate over the keys in order | \(O(n\log n)\) | \(O(n)\) | \(O(n\log n)\) | \(O(n)\) |
3 Tries
Also called a prefix tree, a trie is a tree used for location specific keys within a set.
package student; import java.util.HashMap; import java.util.List; public class Trie { private TrieNode overallRoot; private class TrieNode { String content; HashMap<Character, TrieNode> children; boolean isWord; public TrieNode(String v) { content = v; children = new HashMap<>(); } } public Trie() { overallRoot = new TrieNode(""); } /** * Insert a new word into the Trie, creating any necessary nodes. * @param word The word to be inserted. */ public void insert(String word) { TrieNode current = overallRoot; for (char l: word.toCharArray()) { if (!current.children.containsKey(l)) { current.children.put(l, new TrieNode(Character.toString(l))); } current = current.children.get(l); } current.isWord = true; } public static void main(String[] args) { Trie trie = new Trie(); trie.insert("house"); trie.insert("home"); trie.insert("homeland"); trie.insert("hope"); trie.insert("hound"); trie.insert("hose"); } }
4 Practice Problems1
- Give five orderings of the keys A X C S E R H that, when inserted into an initially empty BST, produce the best-case (i.e., balanced) tree.
- Give nonrecursive implementations of
get
andput
for a BST. - Write a method
isBST(Node node)
that takes aNode
as argument and returnstrue
if the argumentnode
is the root of a binary search tree,false
otherwise. - How many nodes are in a full binary tree of height \(x\)?
Which of the following trees are valid AVL trees?
(A)
(B)
(C)
(D)
(E)
Footnotes:
Solutions:
- Any sequence that inserts H first; C before A and E; S before R and X.
// Insert key-value pair into symbol table (nonrecursive version). public void put(Key key, Value val) { Node z = new Node(key, val); if (root == null) { root = z; return; } Node parent = null; Node x = root; while (x != null) { parent = x; int cmp = key.compareTo(x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else { x.value = val; return; } } int cmp = key.compareTo(parent.key); if (cmp < 0) parent.left = z; else parent.right = z; } // Search BST for given key, nonrecursive version. public Value get(Key key) { Node x = root; while (x != null) { int cmp = key.compareTo(x.key); if (cmp < 0) x = x.left; else if (cmp > 0) x = x.right; else return x.value; } return null; }
public boolean isBST(Node node) { if (node == null) return true; // an empty tree is a valid BST // each child must be empty or have an appropriate key and itself be a valid BST boolean leftCheck = node.left == null || (node.left.key < node.key && isBST(node.left)); boolean rightCheck = node.right == null || (node.right.key > node.key && isBST(node.right)); return leftCheck && rightCheck; }
- A full tree of height \(x\) will have \(2^{x+1} - 1\) nodes
- (B) and (E) are AVL trees, as they follow the balance property (all five follow the BST ordering property)