$19
Approved Includes
<cstddef>
<iostream>
<sstream>
<stdexcept>
<utility>
<tuple>
"red_black_tree.h"
Code Coverage
You must submit a test suite for each task that, when run, covers at least 90% of your code. You should, at a minimum, invoke every function at least once. Best practice is to also check the actual behavior against the expected behavior, e.g. verify that the result is correct. You should be able to do this automatically, i.e. write a program that checks the actual behavior against the expected behavior.
Your test suite should include ALL tests that you wrote and used, including tests you used for debugging.
You should have MANY tests.
Starter Code
compile_test.cpp
Makefile
red_black_tree.h
red_black_tree_tests.cpp
Files to Submit
red_black_tree.h
red_black_tree_tests.cpp
CSCE 221
Task
Implement a Red-Black tree (auto-balancing binary search tree).
You can implement a top-down or bottom-up Red-Black tree. The top-down tree is more efficient but, since it must be done iteratively as opposed to recursively, the code will be more complex and therefore more prone to errors.
Requirements
Files
red_black_tree.h - contains the template definitions
red_black_tree_tests.cpp - contains the test cases and test driver (main)
Class
template <typename Comparable>
class RedBlackTree;
Member types (public)
enum Color {RED,BLACK} - member constants for node color, e.g. RedBlackTree<T>::RED struct Node - must have attributes Comparable value, Node* left, Node* right, and int color (in any order). The name of the class does not need to be Node.
Functions (public)
Constructors & Rule of Three
RedBlackTree() - makes an empty tree
RedBlackTree(const RedBlackTree&) - constructs a copy of the given tree ~RedBlackTree() - destructs this tree
RedBlackTree& operator=(const RedBlackTree&) - assigns a copy of the given tree
Element Access
N/A
Iterators
Optional
Capacity
Optional
CSCE 221
Modifiers
void insert(const Comparable&) - insert the given lvalue reference into the tree
void remove(const Comparable&) - remove the specified value from the tree (use minimum of right child tree when value has two children)
Lookup
bool contains(const Comparable&) const - returns Boolean true if the specified value is in the tree
const Comparable& find_min() const - return the minimum value in the tree or throw std::invalid_argument if the tree is empty
const Comparable& find_max() const - return the maximum value in the tree or throw std::invalid_argument if the tree is empty
Visualization
Optional, but strongly recommended.
For Testing
int color(const Node* node) const - return the color of the specified node (the null pointer is black). This function is required to be public by the instructor’s tests.
const Node* get_root() const - return the root of the tree or nullptr if the tree is empty. This function is required by the instructor’s tests.
Optional
RedBlackTree(RedBlackTree&&) - move constructs a copy of the given (rvalue) tree RedBlackTree& operator=(RedBlackTree&&) - move assigns a copy of the given (rvalue) tree bool is_empty() const - returns Boolean true if the tree is empty
void insert(Comparable&&) - insert the given rvalue reference into the tree using move semantics void make_empty() - remove all values from the tree
void print_tree(std::ostream&=std::cout) const - pretty print the tree
CSCE 221
Example
• make an empty tree RedBlackTree<int> tree;
• insert 5 values into the tree
std::cout << "insert 6, 4, 2, 8, 10 " << std::endl;
tree.insert(6);
tree.insert(4);
tree.insert(2);
tree.insert(8);
tree.insert(10);
• print the tree (this is not executed on Gradescope) std::cout << "tree: " << std::endl;
{
std::ostringstream ss; tree.print_tree(ss);
std::cout << ss.str() << std::endl;
}
std::cout << "contains 4? " << std::boolalpha <<tree.contains(4) << std::endl; std::cout << "contains 7? " << std::boolalpha << tree.contains(7) << std::endl;
// remove the root
std::cout << "remove 4" << std::endl;
tree.remove(4);
// find the minimum element
std::cout << "min: " << tree.find_min() << std::endl;
// find the maximum element
std::cout << "max: " << tree.find_max() << std::endl;
• print the tree (this is not executed on Gradescope) std::cout << "tree: " << std::endl;
{
std::ostringstream ss; tree.print_tree(ss);
std::cout << ss.str() << std::endl;
}
CSCE 221
Example Output
insert 6, 4, 2, 8, 10
tree:
R:10
8
R:6
4
2
contains 4? true
contains 7? false
remove 4
min: 2
max: 10
tree:
R:10
8
6
2
CSCE 221
Red-Black Tree Insert and Remove Help
1. The textbook has the cases. The rules are different for bottom-up and top-down trees, so it depends on which way you go. Assuming you go top-down (which is recommended for efficiency), we have the following rules for insert:
1. on the way down, a black parent with 2 red kids → flip the colors
1. if the grandparent is also red, do the appropriate AVL rotation and re-color (new "root" is black, kids are red)
2. after insert, if the parent is red, do the appropriate AVL rotation around grandparent and re-color (new "root" is black, kids are red)
2. For remove, the idea is to make sure the node we eventually remove (which will be a leaf because removing an internal node is just a value replacement) will be red
1. turn the parent of the root red (pretend the root has a parent and it is red)
2. on the way down
1. the parent is always red (this is the property we maintain)
2. therefore the current node and its sibling are black
3. if the current node has 2 black kids, there are 4 cases:
1. sibling also has 2 black kids → flip the colors
2. sibling has a red kid on the inside (near the current node) → AVL double rotation to bring that red kid up to the root of the subtree (where parent is now) and re-color (red kid stays red, parent and sibling are black, current node is red)
3. sibling has a red kid on the outside (away from the current node) → AVL single rotation to bring the sibling up to the root of the subtree (where parent is now) and re-color (sibling turns red, parent and red kid turn black, current node turns red)
4. if the sibling has two red kids, then you can do either rotation (i.e. choose case 2 or 3).
4. else, the current node has a red kid so we fall through to the next level and hope we land on the red kid so we can continue (fall through again because we want the parent to be red)
1. if we land on the black kid, we know the parent is black and sibling is red, so we rotate the parent with the sibling to make the current node's new parent red and its grandparent black. then we continue from the first main case (parent is red, current and sibling are black)