/// <summary> /// Perform rebalancing on the red-black tree for a deletion. O(lg n). /// If we broke the tree by moving a black node, we need to reorder /// and/or recolor part of it. This is not pretty, and is basically /// derived from the algorithm in Cormen/Leiserson/Rivest, just like /// every other red-black tree implementation. We have, however, gone /// to the necessary work to drop out the required sentinel value in /// their version, so this is a little more flexible. /// </summary> /// <param name="child">The child that was removed.</param> /// <param name="parent">The parent it was removed from.</param> /// <param name="rightSide">Which side of the parent it was removed from.</param> private void DetachFixup(WeightedRedBlackTreeNode <K, V> child, WeightedRedBlackTreeNode <K, V> parent, bool rightSide) { while (child != Root && child.IsBlack()) { if (!rightSide) { WeightedRedBlackTreeNode <K, V> other = parent.Right; if (other.IsRed()) { other.MakeNodeBlack(); parent.MakeNodeRed(); RotateLeft(parent); other = parent.Right; } if (other == null || (other.Left.IsBlack() && other.Right.IsBlack())) { other.MakeNodeRed(); child = parent; } else { if (other.Right.IsBlack()) { other.Left.MakeNodeBlack(); other.MakeNodeRed(); RotateRight(other); other = parent.Right; } if (parent.IsRed()) { other.MakeNodeRed(); } else { other.MakeNodeBlack(); } parent.MakeNodeBlack(); other.Right.MakeNodeBlack(); RotateLeft(parent); child = Root; } } else { WeightedRedBlackTreeNode <K, V> other = parent.Left; if (other.IsRed()) { other.MakeNodeBlack(); parent.MakeNodeRed(); RotateRight(parent); other = parent.Left; } if (other == null || (other.Right.IsBlack() && other.Left.IsBlack())) { other.MakeNodeRed(); child = parent; } else { if (other.Left.IsBlack()) { other.Right.MakeNodeBlack(); other.MakeNodeRed(); RotateLeft(other); other = parent.Left; } if (parent.IsRed()) { other.MakeNodeRed(); } else { other.MakeNodeBlack(); } parent.MakeNodeBlack(); other.Left.MakeNodeBlack(); RotateRight(parent); child = Root; } } parent = child.Parent; if (child != Root) { rightSide = (child == parent.Right); } } child.MakeNodeBlack(); }
/// <summary> /// Validate the correctness of the red-black tree according to the four /// red-black properties [1]: /// /// 1. Every node is either red or black. /// 2. Every leaf (null) is black. /// 3. If a node is red, then both its children are black. /// 4. Every simple path from a node to a descendant leaf contains the /// same number of black nodes. /// /// In addition, we also test that: /// /// 5. Only the Root node may have a null Parent pointer. /// 6. For all non-null Left and Right pointers, Left.Parent == parent, /// and Right.Parent == parent. /// 7. The weight of each node equals the weight of its Left child plus /// the weight of its Right child plus one. /// /// The first three are trivial to test. Properties 5 and 6 are also /// easy, and ensure that the tree is a valid binary search tree. Property /// 4 is the only one that's interesting; to test it, we recursively walk /// the tree inorder after everything else has been checked, and compare /// the count of black nodes each time we reach 'null' against the count of /// black nodes from a strict walk of Left pointers. /// </summary> /// <throws cref="InvalidOperationException">Thrown if the tree is invalid /// or damaged.</throws> public override void Validate() { void Assert(bool result) { if (!result) { throw new InvalidOperationException("Tree is invalid."); } } // Test #2. Every leaf (null) is black. Assert(((WeightedRedBlackTreeNode <K, V>)null).IsBlack()); Assert(!((WeightedRedBlackTreeNode <K, V>)null).IsRed()); // Count up the black nodes in a simple path from root to leaf (null). int expectedBlackCount = 0; for (WeightedRedBlackTreeNode <K, V> node = Root; node != null; node = node.Left) { if (node.IsBlack()) { expectedBlackCount++; } } expectedBlackCount++; // Count up one more for the null. // Recursively walk the entire tree, validating each node. void RecursivelyValidate(WeightedRedBlackTreeNode <K, V> node, int currentBlackCount) { // Count up an additional black node in this path, if this node is black. if (node.IsBlack()) { currentBlackCount++; } // Test #1. Every node is either red or black. Assert(node.IsRed() || node.IsBlack()); // Test #3. If a node is red, then both its children are black. if (node.IsRed()) { Assert(node.Left.IsBlack()); Assert(node.Right.IsBlack()); } // Test #5. Only the Root node may have a null Parent pointer. if (node != null && node.Parent == null) { Assert(node == Root); } // Test #6. For all non-null Left and Right pointers, // Left.Parent == parent, and Right.Parent == parent. if (node != null && node.Left != null) { Assert(node.Left.Parent == node); } if (node != null && node.Right != null) { Assert(node.Right.Parent == node); } // Test #4. Every simple path from a node to a descendant leaf // contains the same number of black nodes. if (currentBlackCount == expectedBlackCount) { Assert(node == null); } if (node != null) { // Test #7. The weight of each node equals the weight of its Left child plus // the weight of its Right child plus one. int expectedWeight = (node.Left?.Weight ?? 0) + (node.Right?.Weight ?? 0) + 1; Assert(node.Weight == expectedWeight); } // Recurse to test the rest of the tree. if (node != null) { RecursivelyValidate(node.Left, currentBlackCount); RecursivelyValidate(node.Right, currentBlackCount); } } // Recursively check the entire tree, starting at the root. RecursivelyValidate(Root, 0); }
/// <summary> /// Delete (detach) a node from the given tree. /// /// This uses standard red-black deletion, with auxiliary routine to /// reorder and recolor nodes if necessary. However, unlike most RB delete /// routines, we do this (A) without sentinel nodes and (B) by only changing /// pointers and colors --- we never copy the data, so the pointers of the /// tree remain valid. Still O(lg n). /// </summary> /// <param name="node"></param> public override void DeleteNode(WeightedRedBlackTreeNode <K, V> node) { if (node == null || Root == null) { return; } WeightedRedBlackTreeNode <K, V> next; // Will take the place of "atom" WeightedRedBlackTreeNode <K, V> child; // First non-null child of "next", if any WeightedRedBlackTreeNode <K, V> parent; // The supposed parent of "child" bool rightSide; // Which side of the parent "child" is on bool needFixup; // Do we need to call fixup()? if (node.Left == null || node.Right == null) { next = node; } else { next = node.Next(); } child = (next.Left != null ? next.Left : next.Right); parent = next.Parent; if (child != null) { child.Parent = parent; } if (parent == null) { Root = child; rightSide = false; } else { if (next == parent.Left) { parent.Left = child; rightSide = false; } else { parent.Right = child; rightSide = true; } for (WeightedRedBlackTreeNode <K, V> temp = parent; temp != null; temp = temp.Parent) { temp.Weight = (temp.Left?.Weight ?? 0) + (temp.Right?.Weight ?? 0) + 1; } } needFixup = next.IsBlack(); if (node != next) { // Surreptitiously, node.contents <- next.contents by // just changing around a lot of pointers. if (parent == node) { parent = next; // Special case } next.Parent = node.Parent; next.Left = node.Left; next.Right = node.Right; if (node.Parent == null) { Root = next; } else if (node == node.Parent.Left) { node.Parent.Left = next; } else { node.Parent.Right = next; } if (next.Left != null) { next.Left.Parent = next; } if (next.Right != null) { next.Right.Parent = next; } if (node.IsBlack()) { next.MakeNodeBlack(); } else { next.MakeNodeRed(); } int temp = node.Weight; node.Weight = next.Weight; next.Weight = temp; } // Nulling the pointers on the node ensures that any active enumerations // or traversals that still reference this node will stop. node.Left = node.Right = node.Parent = null; if (needFixup) { DetachFixup(child, parent, rightSide); } }