I am implementing the LLRB package, which should work in either of two modes: Bottom-Up 2-3 or Top-Down 2-3-4 described by Sedgewick (the code is an improved code, although it only deals with 2-3 trees here , thanks RS for pointer).
Sedgewick gives a very clear description of tree operations for mode 2-3, although he spends a lot of time about mode 2-3-4. It also shows how a simple change in the order of color flipping during insertion can change the behavior of the tree (either split along the path down by 2-3-4, or split along the path up by 2-3):
private Node insert(Node h, Key key, Value value) { if (h == null) return new Node(key, value); // Include this for 2-3-4 trees if (isRed(h.left) && isRed(h.right)) colorFlip(h); int cmp = key.compareTo(h.key); if (cmp == 0) h.val = value; else if (cmp < 0) h.left = insert(h.left, key, value); else h.right = insert(h.right, key, value); if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h); if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h); // Include this for 2-3 trees if (isRed(h.left) && isRed(h.right)) colorFlip(h); return h; }
However, he suppresses the removal in 2-3-4 LLRB with the following:
The code on the next page is a complete implementation of delete () for LLRB 2-3 trees. It is based on the reverse approach used to insert 2-3-4 trees from top to bottom: we perform turns and color transitions along the path down the search path to ensure that the search does not end at 2-node, so that we can simply remove the node at the bottom. We use the fixUp () method to share code to flip and rotate color after recursive calls in insert () code. With fixUp (), we can leave the right red links and unbalanced 4-nodes along the search path so that these conditions are corrected along the path up the tree. ( The approach is also effective with 2-3-4 trees, but requires additional rotation when the right node from the search path is 4-node. )
Its delete () function:
private Node delete(Node h, Key key) { if (key.compareTo(h.key) < 0) { if (!isRed(h.left) && !isRed(h.left.left)) h = moveRedLeft(h); h.left = delete(h.left, key); } else { if (isRed(h.left)) h = rotateRight(h); if (key.compareTo(h.key) == 0 && (h.right == null)) return null; if (!isRed(h.right) && !isRed(h.right.left)) h = moveRedRight(h); if (key.compareTo(h.key) == 0) { h.val = get(h.right, min(h.right).key); h.key = min(h.right).key; h.right = deleteMin(h.right); } else h.right = delete(h.right, key); } return fixUp(h); }
My implementation correctly supports LLRB 2-3 invariants for all tree operations on 2-3 trees, but is not suitable for a subclass of right-handed deletions on 2-3-4 trees (these unsuccessful deletions lead to the right red vertices, but a snowball to imbalance the trees and finally dereferencing null pointers). From a review of sample code that discusses LLRB trees and includes options for building trees in any of the modes, it seems that none of them correctly removes from 2-3-4 LLRBs (i.e., none of them have additional rotation, for example, Sedgewick java above and here ).
I am having trouble figuring out what it means by โextra rotation when the right node from the search path is 4-nodeโ; apparently this rotation to the left, but where and when?
If I rotate the left side passing by the 4-node equivalent (i.e. RR node) or the right inclined 3-node equivalent (BR node) either before calling fixUp () or at the end of the fixUp function, I still get the same invariant contradiction.
Here are the states of the tree of the smallest unsuccessful examples that I found (generated by sequentially inserting elements from 0 to the corresponding maximum value).
The first pair of trees shows the transition from a state compatible with the invariant to the removal of element 15 to a clearly broken state after.


The second is essentially the same as above, but with the removal of 16 from 0..16 (removing 15 results in the same topology). Note that the invariant contradiction allows us to cross the root of the node.


The key will be understanding how to reverse the violations generated while walking through the tree to the target node. The next two trees show how the first tree looks from the top after walking down and left and right (without deleting and before going back with fixUp ()).
After trying to remove '-1' without fixUp: 
After trying to remove '16' without fixUp: 
Trying to turn left for a walk backwards when the node has only the red right child seems to be part of the solution, but it does not work correctly with two red right children in a row, before using flipColor when both children are red, it seems to improve the situation further. but still leave some invariants violated.
If I additionally check whether the right child of the right child is red when his brother is black and rotates to the left, if it is true, I only fail once, but at this moment I feel that I need a new theory, not a new one epicycle.
Any ideas?
For reference, my implementation is available here (No, this is not Java).
Subsequent:
Part of the reason I was interested in this was to confirm many of the claims that 2-3 LLRB trees are more efficient than 2-3-4 LLRB trees. My benchmarking confirmed this for insertion and deletion (2-3 is about 9% faster), but I find that searching is very slightly faster for 2-3-4 trees.
The following points are representative and consistent across all runs:
BU23: BenchmarkInsert 1000000 1546 ns/op BenchmarkDelete 1000000 1974 ns/op BenchmarkGet 5000000 770 ns/op TD234: BenchmarkInsert 1000000 1689 ns/op BenchmarkDelete 1000000 2133 ns/op BenchmarkGet 5000000 753 ns/op
The first column is the name of the bench, the second is the number of operations, the third is the result. Test for i5M 2.27.
I looked at the branch lengths for 2-3 trees and 2-3-4 trees, and little can be explained by the difference in search (average distance from root to node and SD of 1000 trees each with 10,000 random inserts):
Means: TD234 leafs BU23 leafs 12.88940 12.84681 TD234 all BU23 all 11.79274 11.79163 StdDev: TD234 leafs BU23 leafs 1.222458 1.257344 TD234 all BU23 all 1.874335 1.885204