Red Black Tree
Abstract
In computer science, Red-Black Trees are a type of self-balancing binary search tree that are used to store and retrieve elements in sorted order with O(log n) time complexity for all major operations. They are designed to be memory-efficient and balanced, making them more efficient than other balanced trees like AVL trees. In this article, we have discussed the implementation of Red-Black Trees in Rust, a modern programming language designed for performance and safety. We have covered the structure of the tree, as well as the functions for insertion, deletion, and searching. Additionally, we have covered the fix functions that are used to restore the height balance and color balance properties of the tree after each insertion and deletion. Finally, we have discussed the left and right rotation functions that are used to maintain the balance properties of the Red-Black Tree. By using Rust to implement Red-Black Trees, we can take advantage of the language’s performance and safety features to create an efficient and reliable data structure.
What is Red Black Tree (RBT)?
Red-Black Trees are binary search trees where each node is colored either red or black. The color of the node is used to balance the tree so that the longest path from the root to any leaf node is no more than twice as long as the shortest path from the root to any other leaf node. This property is known as the height balance property.
Red-Black Trees are constructed with the following rules
- Each node is either red or black.
- The root node is always black.
- Every leaf (NIL) node is black.
- If a node is red, then both its children must be black.
- Every path from a given node to any of its descendant NIL nodes must contain the same number of black nodes.
By assigning each node with a color and by limiting the number of consecutive colored nodes, RBT ensures that the longest branches will never exceed twice the size of the shortest branches, thus providing a more stable and effective tree. For reference, here’s a visual representation of an RBT:
Here’s a comparison between BST and RBT when we insert “1, 2, 3, 4, 5” in order, demonstrating why RBT is far more efficient than BST when sorted elements are inserted:
Obviously, BST will become extremely low effective when receiving a sorted sequence. And RBT can become more balanced under this situation.
Red-Black Tree Functions
A Red-Black Tree supports the following functions:
Insertion: To insert a new node into the Red-Black Tree, we first create a new node with the given key and value. We then traverse the tree to find the correct location to insert the new node. If the tree is empty, we simply make the new node the root of the tree. If the tree is not empty, we compare the new node’s key to the key of the current node we are examining. If the new node’s key is less than the current node’s key, we move to the left child of the current node. If the new node’s key is greater than the current node’s key, we move to the right child of the current node. We repeat this process until we find an empty location in the tree where we can insert the new node.
Deletion: To delete a node from the Red-Black Tree, we first search for the node with the given key. If the node is not found, we simply return without doing anything. If the node is found, we replace it with its successor, which is the node with the smallest key in the node’s right subtree. We then delete the successor node from the tree using a similar process.
Searching: Searching for a node in the Red-Black Tree is similar to searching in a binary search tree. We start at the root node and compare the given key with the key of the current node. If the keys are equal, we return the value of the current node. If the given key is less than the key of the current node, we move to the left child. If the given key is greater than the key of the current node, we move to the right child. We repeat this process until we find the node with the given key or reach a leaf node.
Rust Code for Red-Black Trees
To implement Red-Black Trees in Rust, we first define a Node struct to represent each node in the tree. The Node struct contains fields for the node’s key, value, color, and left and right children.
1 | enum Color { // Defines an enum called 'Color' |
We also define an RBTree struct to represent the Red-Black Tree as a whole. The RBTree struct contains a root node and methods to insert, delete, and search for elements in the tree.
1 | pub struct RBTree<K: Ord, V> { // Defines a struct called 'RBTree' with generic parameters 'K' and 'V'; 'K' must implement 'Ord' trait |
Left and Right Rotate
These functions perform a left or right rotation of a given node and its children. The rotate_left function takes in a mutable reference to a node and rotates its right child to the left, while the rotate_right function takes in a mutable reference to a node and rotates its left child to the right.
During each rotation, the appropriate pointers are updated to reflect the new structure of the tree. The size of each node is also updated to reflect any changes that occurred during the rotation.
These rotation functions are used by the fix_after_insertion and fix_after_deletion methods to maintain the balance properties of the Red-Black Tree.
1 | impl<K: Ord, V> RBTree<K, V> { |
Insertion Function in Rust
The insert function in Rust follows the same logic as the insertion function described earlier. We create a new node with the given key and value and traverse the tree to find the correct location to insert the new node. If the tree is empty, we simply make the new node the root of the tree. If the tree is not empty, we compare the new node’s key to the key of the current node we are examining. If the new node’s key is less than the current node’s key, we move to the left child of the current node. If the new node’s key is greater than the current node’s key, we move to the right child of the current node. We repeat this process until we find an empty location in the tree where we can insert the new node.
1 | impl<K: Ord, V> RBTree<K, V> { |
Deletion Function in Rust
The delete function in Rust also follows the same logic as the deletion function described earlier. We search for the node with the given key and replace it with its successor, which is the node with the smallest key in the node’s right subtree. We then delete the successor node from the tree using a similar process.
1 | // This function fixes the Red-Black Tree violations that may arise after a node deletion |
Searching Function in Rust
The search
function in Rust is also similar to the search function described earlier. We start at the root node and compare the given key with the key of the current node. If the keys are equal, we return the value of the current node. If the given key is less than the key of the current node, we move to the left child. If the given key is greater than the key of the current node, we move to the right child. We repeat this process until we find the node with the given key or reach a leaf node.
1 | impl<K: Ord, V> RBTree<K, V> { |
Conclusion
Red-Black Trees are a type of self-balancing binary search tree that are used to store and retrieve elements in sorted order with O(log n) time complexity for all major operations. They are designed to be memory efficient and balanced, making them more efficient than other balanced trees like AVL trees. Rust is a modern programming language that is designed for performance and safety, making it a great choice for implementing data structures like the Red-Black Tree.