Generic binary search tree in C++

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite
2












Introduction



This is yet another data structure I'm going over again for the algorithms course. This time it is binary search tree.



Implemented operations:



  • Insert


  • Exists


  • Remove


  • Clear


  • Move constructor and assignment, destructor


There are some tests for remove function below the data structure itself. I tested other functions, but the tests got overridden in the interim. They did pass though. I believe automated ones are not possible until I make the tree somehow traversable, but that's adventure for another time.



Concerns




  • Extreme code duplication



    The 4 case functions (nullptr, equal, less, greater) share the same control flow statements. I believe abstracting that away would make it worse though.




  • Extremely complicated remove function



    This one took around an hour of my time to get correct, from starting to write it to debugging the three cases I found and tested for.




  • Overall just feels bad



    It just gives that feeling. Or may be most of the algorithms I've seen are much more elegant than this.




Code



#include <stdexcept>
#include <type_traits>
#include <ostream>
#include <utility>

template <typename ValueType>
class binary_search_tree

struct node

const ValueType value;
node* left;
node* right;
;

enum class direction

is_root,
left,
right
;

struct search_result

node* parent;
node* target_child;
direction parent_to_child;
;

node* root;
public:
binary_search_tree() :
root(nullptr)


binary_search_tree(const binary_search_tree& other) = delete;
binary_search_tree& operator=(const binary_search_tree& other) = delete;

binary_search_tree(binary_search_tree&& other) :
root(std::exchange(other.root, nullptr))


binary_search_tree& operator=(binary_search_tree&& other) noexcept

std::swap(root, other.root);
return *this;


bool try_insert(const ValueType& value)

return try_insert_helper(value, root);


bool exists(const ValueType& value)

return find_node(value, nullptr, root, direction::is_root).target_child != nullptr;


bool delete_if_exists(const ValueType& value)

auto [parent_node, node_with_value, parent_to_child] =
find_node(value, nullptr, root, direction::is_root);

if (node_with_value == nullptr)
return false;

if (node_with_value->left == nullptr)

auto old = node_with_value;
switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
break;
case direction::is_root:
root = root->right;

delete old;
return true;


if (node_with_value->left->right == nullptr)

switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::is_root:
root->left->right = root->right;
root = root->left;

delete node_with_value;
return true;


auto [suitable_parent, suitable_node] =
find_suitable_node(node_with_value->left->right, node_with_value->left);
switch (parent_to_child)

case direction::left:
parent_node->left = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;

break;
case direction::is_root:
suitable_node->right = root->right;
suitable_node->left = root->left;
root = suitable_node;

suitable_parent->right = nullptr;
delete node_with_value;

return true;


void clear()

clear_helper(root);


void inorder_print(std::ostream& os)

if (root == nullptr)
return;

inorder_print_helper(os, root);


~binary_search_tree()

clear();


private:
std::pair<node*, node*> find_suitable_node(node* start_position, node* parent)

if (start_position->right == nullptr)
return parent, start_position;
return find_suitable_node(start_position->right, start_position);


void clear_helper(node* start_position)

if (start_position == nullptr)
return;
clear_helper(start_position->left);
clear_helper(start_position->right);

delete start_position;


search_result find_node(const ValueType& value,
node* parent,
node* current_node,
direction parent_to_child)

if (current_node == nullptr)
return nullptr, nullptr, direction::is_root;

if (current_node->value == value)
return parent, current_node, parent_to_child;

if (value < current_node->value)
return find_node(value, current_node, current_node->left, direction::left);
else
return find_node(value, current_node, current_node->right, direction::right);


bool exists_helper(const ValueType& value,
node* current_node)

if (current_node == nullptr)
return false;
if (current_node->value == value)
return true;

if (value < current_node->value)
return exists_helper(value, current_node->left);
else
return exists_helper(value, current_node->right);


void inorder_print_helper(std::ostream& os,
node*& current_node)

if (current_node == nullptr)
return;

inorder_print_helper(os, current_node->left);
os << current_node->value << ' ';
inorder_print_helper(os, current_node->right);


bool try_insert_helper(const ValueType& value,
node*& current_node)

if (current_node == nullptr)

current_node = new nodevalue;
return true;


if (current_node->value == value)
return false;

if (current_node->value > value)
return try_insert_helper(value, current_node->left);
else
return try_insert_helper(value, current_node->right);

;

#include <iostream>
#include <sstream>

void test_remove_case_one()

binary_search_tree<int> tree;
tree.try_insert(2);
tree.try_insert(3);
tree.try_insert(1);
tree.try_insert(4);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(3);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 2 4 ")
throw std::logic_error("remove case one fails");


void test_remove_case_two()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 7 11 ")
throw std::logic_error("remove case two fails");


//almost like case 2, but has three added in it
void test_remove_case_three()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);
tree.try_insert(3);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 3 7 11 ")
throw std::logic_error("remove case two fails");


int main()
std::cout << "running remove case 1...n";
test_remove_case_one();
std::cout << "remove case 1 passed successfulyn";
std::cout << "running remove case 2...n";
test_remove_case_two();
std::cout << "remove case 2 passed successfulyn";
std::cout << "running remove case 3...n";
test_remove_case_three();
std::cout << "remove case 3 passed successfulyn";




Explanations



I believe the implementation guidelines are very important, so I decided to keep them here, as the safest place to keep notes for me is SE posts (I know it is quite weird). I included pictures for remove, as it is not as straightforward as others (pictures go over the same examples as in the code).



insert



Quite easy. Launch a recursion. If the current node is nullptr, insert at this node and return true (keep in mind that all pointers are passed by reference, thus the change will be reflected in the data structure itself. Also they always exist, no dangling references). If the value to-be-inserted is less than value in the node (IN THIS ORDER!), search right location to insert in the left subtree. If greater, search the location in the right subtree. If the value is equal, then return false.



exists



Almost the same like insertion, but the true/false cases are reversed. If value is equal to the value in the node, return true. If node is nullptr, return true (nowhere else to search).



remove



While searching for the node-to-remove, return these important three values: parent node of the target node, target node itself, the direction from parent to target child. The suitable child to replace with the to-be-removed node is the one that is greater than left child and less than right child. If the value is in the tree, there are 3 cases (others are covered by these):



  • to-be-removed node doesn't have left child (doesn't have suitable child)

Easy case. Relink parent of the to-be-removed node to the right child of the to-be-removed node (keep in mind DIRECTION!). Delete the to-be-removed node. Don't forget to update root if the to-be-removed node is root.



case 1



  • to-be-removed node has left child, but the child doesn't have right child (has suitable child which is left child of to-be-removed node)

Somewhat easy too. Relink parent to the suitable child, change the right child of suitable child to the right child of to-be-removed node. Delete to-be-removed node. Update root if it is affected.



case 2



  • to-be-removed node has suitable child which is far (> 1 depth) away

Find the rightmost child of the left child of to-be-removed node. Make sure the parent of the suitable child is no longer linked to the suitable child. Relink parent of the to-be-removed node to the suitable child. Relink left and right of the to-be-removed node the left and right of the suitable child, respectively. Delete to-be-removed node.



case 3_1



enter image description here



clear



If current node is nullptr, return. Go to the left, then to the right. Finally delete the current node.







share|improve this question





















  • Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
    – Incomputable
    Jun 3 at 22:13






  • 1




    I was actually going to comment on how nice your handwriting is!
    – erip
    Jun 3 at 23:46










  • @erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
    – Incomputable
    Jun 3 at 23:56
















up vote
7
down vote

favorite
2












Introduction



This is yet another data structure I'm going over again for the algorithms course. This time it is binary search tree.



Implemented operations:



  • Insert


  • Exists


  • Remove


  • Clear


  • Move constructor and assignment, destructor


There are some tests for remove function below the data structure itself. I tested other functions, but the tests got overridden in the interim. They did pass though. I believe automated ones are not possible until I make the tree somehow traversable, but that's adventure for another time.



Concerns




  • Extreme code duplication



    The 4 case functions (nullptr, equal, less, greater) share the same control flow statements. I believe abstracting that away would make it worse though.




  • Extremely complicated remove function



    This one took around an hour of my time to get correct, from starting to write it to debugging the three cases I found and tested for.




  • Overall just feels bad



    It just gives that feeling. Or may be most of the algorithms I've seen are much more elegant than this.




Code



#include <stdexcept>
#include <type_traits>
#include <ostream>
#include <utility>

template <typename ValueType>
class binary_search_tree

struct node

const ValueType value;
node* left;
node* right;
;

enum class direction

is_root,
left,
right
;

struct search_result

node* parent;
node* target_child;
direction parent_to_child;
;

node* root;
public:
binary_search_tree() :
root(nullptr)


binary_search_tree(const binary_search_tree& other) = delete;
binary_search_tree& operator=(const binary_search_tree& other) = delete;

binary_search_tree(binary_search_tree&& other) :
root(std::exchange(other.root, nullptr))


binary_search_tree& operator=(binary_search_tree&& other) noexcept

std::swap(root, other.root);
return *this;


bool try_insert(const ValueType& value)

return try_insert_helper(value, root);


bool exists(const ValueType& value)

return find_node(value, nullptr, root, direction::is_root).target_child != nullptr;


bool delete_if_exists(const ValueType& value)

auto [parent_node, node_with_value, parent_to_child] =
find_node(value, nullptr, root, direction::is_root);

if (node_with_value == nullptr)
return false;

if (node_with_value->left == nullptr)

auto old = node_with_value;
switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
break;
case direction::is_root:
root = root->right;

delete old;
return true;


if (node_with_value->left->right == nullptr)

switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::is_root:
root->left->right = root->right;
root = root->left;

delete node_with_value;
return true;


auto [suitable_parent, suitable_node] =
find_suitable_node(node_with_value->left->right, node_with_value->left);
switch (parent_to_child)

case direction::left:
parent_node->left = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;

break;
case direction::is_root:
suitable_node->right = root->right;
suitable_node->left = root->left;
root = suitable_node;

suitable_parent->right = nullptr;
delete node_with_value;

return true;


void clear()

clear_helper(root);


void inorder_print(std::ostream& os)

if (root == nullptr)
return;

inorder_print_helper(os, root);


~binary_search_tree()

clear();


private:
std::pair<node*, node*> find_suitable_node(node* start_position, node* parent)

if (start_position->right == nullptr)
return parent, start_position;
return find_suitable_node(start_position->right, start_position);


void clear_helper(node* start_position)

if (start_position == nullptr)
return;
clear_helper(start_position->left);
clear_helper(start_position->right);

delete start_position;


search_result find_node(const ValueType& value,
node* parent,
node* current_node,
direction parent_to_child)

if (current_node == nullptr)
return nullptr, nullptr, direction::is_root;

if (current_node->value == value)
return parent, current_node, parent_to_child;

if (value < current_node->value)
return find_node(value, current_node, current_node->left, direction::left);
else
return find_node(value, current_node, current_node->right, direction::right);


bool exists_helper(const ValueType& value,
node* current_node)

if (current_node == nullptr)
return false;
if (current_node->value == value)
return true;

if (value < current_node->value)
return exists_helper(value, current_node->left);
else
return exists_helper(value, current_node->right);


void inorder_print_helper(std::ostream& os,
node*& current_node)

if (current_node == nullptr)
return;

inorder_print_helper(os, current_node->left);
os << current_node->value << ' ';
inorder_print_helper(os, current_node->right);


bool try_insert_helper(const ValueType& value,
node*& current_node)

if (current_node == nullptr)

current_node = new nodevalue;
return true;


if (current_node->value == value)
return false;

if (current_node->value > value)
return try_insert_helper(value, current_node->left);
else
return try_insert_helper(value, current_node->right);

;

#include <iostream>
#include <sstream>

void test_remove_case_one()

binary_search_tree<int> tree;
tree.try_insert(2);
tree.try_insert(3);
tree.try_insert(1);
tree.try_insert(4);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(3);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 2 4 ")
throw std::logic_error("remove case one fails");


void test_remove_case_two()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 7 11 ")
throw std::logic_error("remove case two fails");


//almost like case 2, but has three added in it
void test_remove_case_three()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);
tree.try_insert(3);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 3 7 11 ")
throw std::logic_error("remove case two fails");


int main()
std::cout << "running remove case 1...n";
test_remove_case_one();
std::cout << "remove case 1 passed successfulyn";
std::cout << "running remove case 2...n";
test_remove_case_two();
std::cout << "remove case 2 passed successfulyn";
std::cout << "running remove case 3...n";
test_remove_case_three();
std::cout << "remove case 3 passed successfulyn";




Explanations



I believe the implementation guidelines are very important, so I decided to keep them here, as the safest place to keep notes for me is SE posts (I know it is quite weird). I included pictures for remove, as it is not as straightforward as others (pictures go over the same examples as in the code).



insert



Quite easy. Launch a recursion. If the current node is nullptr, insert at this node and return true (keep in mind that all pointers are passed by reference, thus the change will be reflected in the data structure itself. Also they always exist, no dangling references). If the value to-be-inserted is less than value in the node (IN THIS ORDER!), search right location to insert in the left subtree. If greater, search the location in the right subtree. If the value is equal, then return false.



exists



Almost the same like insertion, but the true/false cases are reversed. If value is equal to the value in the node, return true. If node is nullptr, return true (nowhere else to search).



remove



While searching for the node-to-remove, return these important three values: parent node of the target node, target node itself, the direction from parent to target child. The suitable child to replace with the to-be-removed node is the one that is greater than left child and less than right child. If the value is in the tree, there are 3 cases (others are covered by these):



  • to-be-removed node doesn't have left child (doesn't have suitable child)

Easy case. Relink parent of the to-be-removed node to the right child of the to-be-removed node (keep in mind DIRECTION!). Delete the to-be-removed node. Don't forget to update root if the to-be-removed node is root.



case 1



  • to-be-removed node has left child, but the child doesn't have right child (has suitable child which is left child of to-be-removed node)

Somewhat easy too. Relink parent to the suitable child, change the right child of suitable child to the right child of to-be-removed node. Delete to-be-removed node. Update root if it is affected.



case 2



  • to-be-removed node has suitable child which is far (> 1 depth) away

Find the rightmost child of the left child of to-be-removed node. Make sure the parent of the suitable child is no longer linked to the suitable child. Relink parent of the to-be-removed node to the suitable child. Relink left and right of the to-be-removed node the left and right of the suitable child, respectively. Delete to-be-removed node.



case 3_1



enter image description here



clear



If current node is nullptr, return. Go to the left, then to the right. Finally delete the current node.







share|improve this question





















  • Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
    – Incomputable
    Jun 3 at 22:13






  • 1




    I was actually going to comment on how nice your handwriting is!
    – erip
    Jun 3 at 23:46










  • @erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
    – Incomputable
    Jun 3 at 23:56












up vote
7
down vote

favorite
2









up vote
7
down vote

favorite
2






2





Introduction



This is yet another data structure I'm going over again for the algorithms course. This time it is binary search tree.



Implemented operations:



  • Insert


  • Exists


  • Remove


  • Clear


  • Move constructor and assignment, destructor


There are some tests for remove function below the data structure itself. I tested other functions, but the tests got overridden in the interim. They did pass though. I believe automated ones are not possible until I make the tree somehow traversable, but that's adventure for another time.



Concerns




  • Extreme code duplication



    The 4 case functions (nullptr, equal, less, greater) share the same control flow statements. I believe abstracting that away would make it worse though.




  • Extremely complicated remove function



    This one took around an hour of my time to get correct, from starting to write it to debugging the three cases I found and tested for.




  • Overall just feels bad



    It just gives that feeling. Or may be most of the algorithms I've seen are much more elegant than this.




Code



#include <stdexcept>
#include <type_traits>
#include <ostream>
#include <utility>

template <typename ValueType>
class binary_search_tree

struct node

const ValueType value;
node* left;
node* right;
;

enum class direction

is_root,
left,
right
;

struct search_result

node* parent;
node* target_child;
direction parent_to_child;
;

node* root;
public:
binary_search_tree() :
root(nullptr)


binary_search_tree(const binary_search_tree& other) = delete;
binary_search_tree& operator=(const binary_search_tree& other) = delete;

binary_search_tree(binary_search_tree&& other) :
root(std::exchange(other.root, nullptr))


binary_search_tree& operator=(binary_search_tree&& other) noexcept

std::swap(root, other.root);
return *this;


bool try_insert(const ValueType& value)

return try_insert_helper(value, root);


bool exists(const ValueType& value)

return find_node(value, nullptr, root, direction::is_root).target_child != nullptr;


bool delete_if_exists(const ValueType& value)

auto [parent_node, node_with_value, parent_to_child] =
find_node(value, nullptr, root, direction::is_root);

if (node_with_value == nullptr)
return false;

if (node_with_value->left == nullptr)

auto old = node_with_value;
switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
break;
case direction::is_root:
root = root->right;

delete old;
return true;


if (node_with_value->left->right == nullptr)

switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::is_root:
root->left->right = root->right;
root = root->left;

delete node_with_value;
return true;


auto [suitable_parent, suitable_node] =
find_suitable_node(node_with_value->left->right, node_with_value->left);
switch (parent_to_child)

case direction::left:
parent_node->left = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;

break;
case direction::is_root:
suitable_node->right = root->right;
suitable_node->left = root->left;
root = suitable_node;

suitable_parent->right = nullptr;
delete node_with_value;

return true;


void clear()

clear_helper(root);


void inorder_print(std::ostream& os)

if (root == nullptr)
return;

inorder_print_helper(os, root);


~binary_search_tree()

clear();


private:
std::pair<node*, node*> find_suitable_node(node* start_position, node* parent)

if (start_position->right == nullptr)
return parent, start_position;
return find_suitable_node(start_position->right, start_position);


void clear_helper(node* start_position)

if (start_position == nullptr)
return;
clear_helper(start_position->left);
clear_helper(start_position->right);

delete start_position;


search_result find_node(const ValueType& value,
node* parent,
node* current_node,
direction parent_to_child)

if (current_node == nullptr)
return nullptr, nullptr, direction::is_root;

if (current_node->value == value)
return parent, current_node, parent_to_child;

if (value < current_node->value)
return find_node(value, current_node, current_node->left, direction::left);
else
return find_node(value, current_node, current_node->right, direction::right);


bool exists_helper(const ValueType& value,
node* current_node)

if (current_node == nullptr)
return false;
if (current_node->value == value)
return true;

if (value < current_node->value)
return exists_helper(value, current_node->left);
else
return exists_helper(value, current_node->right);


void inorder_print_helper(std::ostream& os,
node*& current_node)

if (current_node == nullptr)
return;

inorder_print_helper(os, current_node->left);
os << current_node->value << ' ';
inorder_print_helper(os, current_node->right);


bool try_insert_helper(const ValueType& value,
node*& current_node)

if (current_node == nullptr)

current_node = new nodevalue;
return true;


if (current_node->value == value)
return false;

if (current_node->value > value)
return try_insert_helper(value, current_node->left);
else
return try_insert_helper(value, current_node->right);

;

#include <iostream>
#include <sstream>

void test_remove_case_one()

binary_search_tree<int> tree;
tree.try_insert(2);
tree.try_insert(3);
tree.try_insert(1);
tree.try_insert(4);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(3);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 2 4 ")
throw std::logic_error("remove case one fails");


void test_remove_case_two()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 7 11 ")
throw std::logic_error("remove case two fails");


//almost like case 2, but has three added in it
void test_remove_case_three()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);
tree.try_insert(3);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 3 7 11 ")
throw std::logic_error("remove case two fails");


int main()
std::cout << "running remove case 1...n";
test_remove_case_one();
std::cout << "remove case 1 passed successfulyn";
std::cout << "running remove case 2...n";
test_remove_case_two();
std::cout << "remove case 2 passed successfulyn";
std::cout << "running remove case 3...n";
test_remove_case_three();
std::cout << "remove case 3 passed successfulyn";




Explanations



I believe the implementation guidelines are very important, so I decided to keep them here, as the safest place to keep notes for me is SE posts (I know it is quite weird). I included pictures for remove, as it is not as straightforward as others (pictures go over the same examples as in the code).



insert



Quite easy. Launch a recursion. If the current node is nullptr, insert at this node and return true (keep in mind that all pointers are passed by reference, thus the change will be reflected in the data structure itself. Also they always exist, no dangling references). If the value to-be-inserted is less than value in the node (IN THIS ORDER!), search right location to insert in the left subtree. If greater, search the location in the right subtree. If the value is equal, then return false.



exists



Almost the same like insertion, but the true/false cases are reversed. If value is equal to the value in the node, return true. If node is nullptr, return true (nowhere else to search).



remove



While searching for the node-to-remove, return these important three values: parent node of the target node, target node itself, the direction from parent to target child. The suitable child to replace with the to-be-removed node is the one that is greater than left child and less than right child. If the value is in the tree, there are 3 cases (others are covered by these):



  • to-be-removed node doesn't have left child (doesn't have suitable child)

Easy case. Relink parent of the to-be-removed node to the right child of the to-be-removed node (keep in mind DIRECTION!). Delete the to-be-removed node. Don't forget to update root if the to-be-removed node is root.



case 1



  • to-be-removed node has left child, but the child doesn't have right child (has suitable child which is left child of to-be-removed node)

Somewhat easy too. Relink parent to the suitable child, change the right child of suitable child to the right child of to-be-removed node. Delete to-be-removed node. Update root if it is affected.



case 2



  • to-be-removed node has suitable child which is far (> 1 depth) away

Find the rightmost child of the left child of to-be-removed node. Make sure the parent of the suitable child is no longer linked to the suitable child. Relink parent of the to-be-removed node to the suitable child. Relink left and right of the to-be-removed node the left and right of the suitable child, respectively. Delete to-be-removed node.



case 3_1



enter image description here



clear



If current node is nullptr, return. Go to the left, then to the right. Finally delete the current node.







share|improve this question













Introduction



This is yet another data structure I'm going over again for the algorithms course. This time it is binary search tree.



Implemented operations:



  • Insert


  • Exists


  • Remove


  • Clear


  • Move constructor and assignment, destructor


There are some tests for remove function below the data structure itself. I tested other functions, but the tests got overridden in the interim. They did pass though. I believe automated ones are not possible until I make the tree somehow traversable, but that's adventure for another time.



Concerns




  • Extreme code duplication



    The 4 case functions (nullptr, equal, less, greater) share the same control flow statements. I believe abstracting that away would make it worse though.




  • Extremely complicated remove function



    This one took around an hour of my time to get correct, from starting to write it to debugging the three cases I found and tested for.




  • Overall just feels bad



    It just gives that feeling. Or may be most of the algorithms I've seen are much more elegant than this.




Code



#include <stdexcept>
#include <type_traits>
#include <ostream>
#include <utility>

template <typename ValueType>
class binary_search_tree

struct node

const ValueType value;
node* left;
node* right;
;

enum class direction

is_root,
left,
right
;

struct search_result

node* parent;
node* target_child;
direction parent_to_child;
;

node* root;
public:
binary_search_tree() :
root(nullptr)


binary_search_tree(const binary_search_tree& other) = delete;
binary_search_tree& operator=(const binary_search_tree& other) = delete;

binary_search_tree(binary_search_tree&& other) :
root(std::exchange(other.root, nullptr))


binary_search_tree& operator=(binary_search_tree&& other) noexcept

std::swap(root, other.root);
return *this;


bool try_insert(const ValueType& value)

return try_insert_helper(value, root);


bool exists(const ValueType& value)

return find_node(value, nullptr, root, direction::is_root).target_child != nullptr;


bool delete_if_exists(const ValueType& value)

auto [parent_node, node_with_value, parent_to_child] =
find_node(value, nullptr, root, direction::is_root);

if (node_with_value == nullptr)
return false;

if (node_with_value->left == nullptr)

auto old = node_with_value;
switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
break;
case direction::is_root:
root = root->right;

delete old;
return true;


if (node_with_value->left->right == nullptr)

switch (parent_to_child)

case direction::left:
parent_node->left = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::right:
parent_node->right = node_with_value->right;
node_with_value->right->left = node_with_value->left;
break;
case direction::is_root:
root->left->right = root->right;
root = root->left;

delete node_with_value;
return true;


auto [suitable_parent, suitable_node] =
find_suitable_node(node_with_value->left->right, node_with_value->left);
switch (parent_to_child)

case direction::left:
parent_node->left = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;
break;
case direction::right:
parent_node->right = suitable_node;
suitable_node->right = node_with_value->right;
suitable_node->left = node_with_value->left;

break;
case direction::is_root:
suitable_node->right = root->right;
suitable_node->left = root->left;
root = suitable_node;

suitable_parent->right = nullptr;
delete node_with_value;

return true;


void clear()

clear_helper(root);


void inorder_print(std::ostream& os)

if (root == nullptr)
return;

inorder_print_helper(os, root);


~binary_search_tree()

clear();


private:
std::pair<node*, node*> find_suitable_node(node* start_position, node* parent)

if (start_position->right == nullptr)
return parent, start_position;
return find_suitable_node(start_position->right, start_position);


void clear_helper(node* start_position)

if (start_position == nullptr)
return;
clear_helper(start_position->left);
clear_helper(start_position->right);

delete start_position;


search_result find_node(const ValueType& value,
node* parent,
node* current_node,
direction parent_to_child)

if (current_node == nullptr)
return nullptr, nullptr, direction::is_root;

if (current_node->value == value)
return parent, current_node, parent_to_child;

if (value < current_node->value)
return find_node(value, current_node, current_node->left, direction::left);
else
return find_node(value, current_node, current_node->right, direction::right);


bool exists_helper(const ValueType& value,
node* current_node)

if (current_node == nullptr)
return false;
if (current_node->value == value)
return true;

if (value < current_node->value)
return exists_helper(value, current_node->left);
else
return exists_helper(value, current_node->right);


void inorder_print_helper(std::ostream& os,
node*& current_node)

if (current_node == nullptr)
return;

inorder_print_helper(os, current_node->left);
os << current_node->value << ' ';
inorder_print_helper(os, current_node->right);


bool try_insert_helper(const ValueType& value,
node*& current_node)

if (current_node == nullptr)

current_node = new nodevalue;
return true;


if (current_node->value == value)
return false;

if (current_node->value > value)
return try_insert_helper(value, current_node->left);
else
return try_insert_helper(value, current_node->right);

;

#include <iostream>
#include <sstream>

void test_remove_case_one()

binary_search_tree<int> tree;
tree.try_insert(2);
tree.try_insert(3);
tree.try_insert(1);
tree.try_insert(4);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(3);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 2 4 ")
throw std::logic_error("remove case one fails");


void test_remove_case_two()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 7 11 ")
throw std::logic_error("remove case two fails");


//almost like case 2, but has three added in it
void test_remove_case_three()

binary_search_tree<int> tree;
tree.try_insert(4);
tree.try_insert(7);
tree.try_insert(11);
tree.try_insert(1);
tree.try_insert(-2);
tree.try_insert(0);
tree.try_insert(3);

tree.delete_if_exists(4);
std::ostringstream oss;
tree.inorder_print(oss);
if (oss.str() != "-2 0 1 3 7 11 ")
throw std::logic_error("remove case two fails");


int main()
std::cout << "running remove case 1...n";
test_remove_case_one();
std::cout << "remove case 1 passed successfulyn";
std::cout << "running remove case 2...n";
test_remove_case_two();
std::cout << "remove case 2 passed successfulyn";
std::cout << "running remove case 3...n";
test_remove_case_three();
std::cout << "remove case 3 passed successfulyn";




Explanations



I believe the implementation guidelines are very important, so I decided to keep them here, as the safest place to keep notes for me is SE posts (I know it is quite weird). I included pictures for remove, as it is not as straightforward as others (pictures go over the same examples as in the code).



insert



Quite easy. Launch a recursion. If the current node is nullptr, insert at this node and return true (keep in mind that all pointers are passed by reference, thus the change will be reflected in the data structure itself. Also they always exist, no dangling references). If the value to-be-inserted is less than value in the node (IN THIS ORDER!), search right location to insert in the left subtree. If greater, search the location in the right subtree. If the value is equal, then return false.



exists



Almost the same like insertion, but the true/false cases are reversed. If value is equal to the value in the node, return true. If node is nullptr, return true (nowhere else to search).



remove



While searching for the node-to-remove, return these important three values: parent node of the target node, target node itself, the direction from parent to target child. The suitable child to replace with the to-be-removed node is the one that is greater than left child and less than right child. If the value is in the tree, there are 3 cases (others are covered by these):



  • to-be-removed node doesn't have left child (doesn't have suitable child)

Easy case. Relink parent of the to-be-removed node to the right child of the to-be-removed node (keep in mind DIRECTION!). Delete the to-be-removed node. Don't forget to update root if the to-be-removed node is root.



case 1



  • to-be-removed node has left child, but the child doesn't have right child (has suitable child which is left child of to-be-removed node)

Somewhat easy too. Relink parent to the suitable child, change the right child of suitable child to the right child of to-be-removed node. Delete to-be-removed node. Update root if it is affected.



case 2



  • to-be-removed node has suitable child which is far (> 1 depth) away

Find the rightmost child of the left child of to-be-removed node. Make sure the parent of the suitable child is no longer linked to the suitable child. Relink parent of the to-be-removed node to the suitable child. Relink left and right of the to-be-removed node the left and right of the suitable child, respectively. Delete to-be-removed node.



case 3_1



enter image description here



clear



If current node is nullptr, return. Go to the left, then to the right. Finally delete the current node.









share|improve this question












share|improve this question




share|improve this question








edited Jun 3 at 22:12
























asked Jun 3 at 21:30









Incomputable

5,99721144




5,99721144











  • Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
    – Incomputable
    Jun 3 at 22:13






  • 1




    I was actually going to comment on how nice your handwriting is!
    – erip
    Jun 3 at 23:46










  • @erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
    – Incomputable
    Jun 3 at 23:56
















  • Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
    – Incomputable
    Jun 3 at 22:13






  • 1




    I was actually going to comment on how nice your handwriting is!
    – erip
    Jun 3 at 23:46










  • @erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
    – Incomputable
    Jun 3 at 23:56















Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
– Incomputable
Jun 3 at 22:13




Let me know if pictures are too big. I'll try to do something about it tomorrow. Sorry for the terrible handwriting.
– Incomputable
Jun 3 at 22:13




1




1




I was actually going to comment on how nice your handwriting is!
– erip
Jun 3 at 23:46




I was actually going to comment on how nice your handwriting is!
– erip
Jun 3 at 23:46












@erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
– Incomputable
Jun 3 at 23:56




@erip, thanks! I believe I should work on the style consistency (e.g. same height of lower case letters and same width of the same letters). I've got 20 more lectures to go through on youtube, so I'll probably be taking more notes.
– Incomputable
Jun 3 at 23:56










2 Answers
2






active

oldest

votes

















up vote
4
down vote













The first of your problems, namely code duplication is stemmed from the wrong interface. exists_helper returning a boolean violates a very important (and unfortunately little known) principle: do not throw away the information you computed. In case of exists_helper computed is an insertion point for the value you did not find. Returning that makes insert_helper, and the find, and delete thin wrappers around exists_helper.



Along the same line, insert shall return an indication of success/failure along with the node itself, STL style. It doesn't really matter in case of integers, but for more complex ValueType we are usually interested in what did prevent insertion.




I don't approve recursive nature of exists and insert. They are tail recursive, and better be written as iterative.




Regarding deletion, I think you over engineered it. Consider a pseudocode:



 if left child exists,
find the rightmost descendant
rightmost descendant's->right = right child
return left child as new root
else
return right child as new root


Note that your case 2 doesn't need to be special cased: left child itself is the rightmost descendant.






share|improve this answer























  • I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
    – Incomputable
    Jun 4 at 0:26










  • I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
    – Cris Luengo
    Jun 4 at 1:33

















up vote
3
down vote













binary_search_tree() :
root(nullptr)



Use default initialization of the members, in-line in the class. If you have:



node* root = nullptr;


then your default constructor will be generated automatically.



enum class direction

is_root,
left,
right
;


Last time I implemented a type of binary tree (the outer layer of a “T Tree”, with auto-balancing inserts and deletes) I took advantage of the symmetry between left and right to not write all the code twice. Instead of separately named left and right child nodes, I made an array of 2 children, so I have child[0] and child[1].



All the logic that deals with left vs. right is mirrored with the key less vs greater. So I implement it once using the abstract S and D instead of Left and Right, and switch which is which in the key comparison. That is, on the less-than case, S is left and D is right; on the greater-than case, the other way around. Since their values are 0 and 1, the conditional code just sets S and then D is always 1-S. child[S] will be either left or right, depending on the choice. Follow me?



Although, it is the balancing code that is most of the work (and this eliminated many test cases too since it was all the same). If you do AVL tree or the like next, keep that in mind!




parent_to_child is unusual. It means you need the follow-up switch statement in delete_if_exists and the extra state in direction (or maybe the whole enum at all, in your case). I think it is more normal to return a pointer to the parent as well as a pointer to the found node (if any). That also works when you do not have a parent pointer in each node (the purest form of binary tree does not).




 if (node_with_value == nullptr)

if (node_with_value->left == nullptr)


Don’t compare directly against nullptr. Use the contextual conversion to bool as a truth value that is meant for this purpose. This is especially when you start using smart pointers, as they implement an efficient operator bool rather than needing to do a comparison directly.






share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195767%2fgeneric-binary-search-tree-in-c%23new-answer', 'question_page');

    );

    Post as a guest






























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    4
    down vote













    The first of your problems, namely code duplication is stemmed from the wrong interface. exists_helper returning a boolean violates a very important (and unfortunately little known) principle: do not throw away the information you computed. In case of exists_helper computed is an insertion point for the value you did not find. Returning that makes insert_helper, and the find, and delete thin wrappers around exists_helper.



    Along the same line, insert shall return an indication of success/failure along with the node itself, STL style. It doesn't really matter in case of integers, but for more complex ValueType we are usually interested in what did prevent insertion.




    I don't approve recursive nature of exists and insert. They are tail recursive, and better be written as iterative.




    Regarding deletion, I think you over engineered it. Consider a pseudocode:



     if left child exists,
    find the rightmost descendant
    rightmost descendant's->right = right child
    return left child as new root
    else
    return right child as new root


    Note that your case 2 doesn't need to be special cased: left child itself is the rightmost descendant.






    share|improve this answer























    • I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
      – Incomputable
      Jun 4 at 0:26










    • I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
      – Cris Luengo
      Jun 4 at 1:33














    up vote
    4
    down vote













    The first of your problems, namely code duplication is stemmed from the wrong interface. exists_helper returning a boolean violates a very important (and unfortunately little known) principle: do not throw away the information you computed. In case of exists_helper computed is an insertion point for the value you did not find. Returning that makes insert_helper, and the find, and delete thin wrappers around exists_helper.



    Along the same line, insert shall return an indication of success/failure along with the node itself, STL style. It doesn't really matter in case of integers, but for more complex ValueType we are usually interested in what did prevent insertion.




    I don't approve recursive nature of exists and insert. They are tail recursive, and better be written as iterative.




    Regarding deletion, I think you over engineered it. Consider a pseudocode:



     if left child exists,
    find the rightmost descendant
    rightmost descendant's->right = right child
    return left child as new root
    else
    return right child as new root


    Note that your case 2 doesn't need to be special cased: left child itself is the rightmost descendant.






    share|improve this answer























    • I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
      – Incomputable
      Jun 4 at 0:26










    • I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
      – Cris Luengo
      Jun 4 at 1:33












    up vote
    4
    down vote










    up vote
    4
    down vote









    The first of your problems, namely code duplication is stemmed from the wrong interface. exists_helper returning a boolean violates a very important (and unfortunately little known) principle: do not throw away the information you computed. In case of exists_helper computed is an insertion point for the value you did not find. Returning that makes insert_helper, and the find, and delete thin wrappers around exists_helper.



    Along the same line, insert shall return an indication of success/failure along with the node itself, STL style. It doesn't really matter in case of integers, but for more complex ValueType we are usually interested in what did prevent insertion.




    I don't approve recursive nature of exists and insert. They are tail recursive, and better be written as iterative.




    Regarding deletion, I think you over engineered it. Consider a pseudocode:



     if left child exists,
    find the rightmost descendant
    rightmost descendant's->right = right child
    return left child as new root
    else
    return right child as new root


    Note that your case 2 doesn't need to be special cased: left child itself is the rightmost descendant.






    share|improve this answer















    The first of your problems, namely code duplication is stemmed from the wrong interface. exists_helper returning a boolean violates a very important (and unfortunately little known) principle: do not throw away the information you computed. In case of exists_helper computed is an insertion point for the value you did not find. Returning that makes insert_helper, and the find, and delete thin wrappers around exists_helper.



    Along the same line, insert shall return an indication of success/failure along with the node itself, STL style. It doesn't really matter in case of integers, but for more complex ValueType we are usually interested in what did prevent insertion.




    I don't approve recursive nature of exists and insert. They are tail recursive, and better be written as iterative.




    Regarding deletion, I think you over engineered it. Consider a pseudocode:



     if left child exists,
    find the rightmost descendant
    rightmost descendant's->right = right child
    return left child as new root
    else
    return right child as new root


    Note that your case 2 doesn't need to be special cased: left child itself is the rightmost descendant.







    share|improve this answer















    share|improve this answer



    share|improve this answer








    edited Jun 4 at 11:27









    Incomputable

    5,99721144




    5,99721144











    answered Jun 3 at 23:05









    vnp

    36.4k12890




    36.4k12890











    • I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
      – Incomputable
      Jun 4 at 0:26










    • I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
      – Cris Luengo
      Jun 4 at 1:33
















    • I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
      – Incomputable
      Jun 4 at 0:26










    • I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
      – Cris Luengo
      Jun 4 at 1:33















    I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
    – Incomputable
    Jun 4 at 0:26




    I remember the principle being mentioned by Marshall Clow on one of the CppCon talks. I agree that I could make some functions iterative. I'll add input iterator support, lower_bound and upper_bound and then will post again with the suggestions from this post.
    – Incomputable
    Jun 4 at 0:26












    I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
    – Cris Luengo
    Jun 4 at 1:33




    I once wrote a tail-recursive algorithm for binary trees, then rewrote it as an iterative algorithm. The recursive version was faster. Maybe it’s me... :)
    – Cris Luengo
    Jun 4 at 1:33












    up vote
    3
    down vote













    binary_search_tree() :
    root(nullptr)



    Use default initialization of the members, in-line in the class. If you have:



    node* root = nullptr;


    then your default constructor will be generated automatically.



    enum class direction

    is_root,
    left,
    right
    ;


    Last time I implemented a type of binary tree (the outer layer of a “T Tree”, with auto-balancing inserts and deletes) I took advantage of the symmetry between left and right to not write all the code twice. Instead of separately named left and right child nodes, I made an array of 2 children, so I have child[0] and child[1].



    All the logic that deals with left vs. right is mirrored with the key less vs greater. So I implement it once using the abstract S and D instead of Left and Right, and switch which is which in the key comparison. That is, on the less-than case, S is left and D is right; on the greater-than case, the other way around. Since their values are 0 and 1, the conditional code just sets S and then D is always 1-S. child[S] will be either left or right, depending on the choice. Follow me?



    Although, it is the balancing code that is most of the work (and this eliminated many test cases too since it was all the same). If you do AVL tree or the like next, keep that in mind!




    parent_to_child is unusual. It means you need the follow-up switch statement in delete_if_exists and the extra state in direction (or maybe the whole enum at all, in your case). I think it is more normal to return a pointer to the parent as well as a pointer to the found node (if any). That also works when you do not have a parent pointer in each node (the purest form of binary tree does not).




     if (node_with_value == nullptr)

    if (node_with_value->left == nullptr)


    Don’t compare directly against nullptr. Use the contextual conversion to bool as a truth value that is meant for this purpose. This is especially when you start using smart pointers, as they implement an efficient operator bool rather than needing to do a comparison directly.






    share|improve this answer

























      up vote
      3
      down vote













      binary_search_tree() :
      root(nullptr)



      Use default initialization of the members, in-line in the class. If you have:



      node* root = nullptr;


      then your default constructor will be generated automatically.



      enum class direction

      is_root,
      left,
      right
      ;


      Last time I implemented a type of binary tree (the outer layer of a “T Tree”, with auto-balancing inserts and deletes) I took advantage of the symmetry between left and right to not write all the code twice. Instead of separately named left and right child nodes, I made an array of 2 children, so I have child[0] and child[1].



      All the logic that deals with left vs. right is mirrored with the key less vs greater. So I implement it once using the abstract S and D instead of Left and Right, and switch which is which in the key comparison. That is, on the less-than case, S is left and D is right; on the greater-than case, the other way around. Since their values are 0 and 1, the conditional code just sets S and then D is always 1-S. child[S] will be either left or right, depending on the choice. Follow me?



      Although, it is the balancing code that is most of the work (and this eliminated many test cases too since it was all the same). If you do AVL tree or the like next, keep that in mind!




      parent_to_child is unusual. It means you need the follow-up switch statement in delete_if_exists and the extra state in direction (or maybe the whole enum at all, in your case). I think it is more normal to return a pointer to the parent as well as a pointer to the found node (if any). That also works when you do not have a parent pointer in each node (the purest form of binary tree does not).




       if (node_with_value == nullptr)

      if (node_with_value->left == nullptr)


      Don’t compare directly against nullptr. Use the contextual conversion to bool as a truth value that is meant for this purpose. This is especially when you start using smart pointers, as they implement an efficient operator bool rather than needing to do a comparison directly.






      share|improve this answer























        up vote
        3
        down vote










        up vote
        3
        down vote









        binary_search_tree() :
        root(nullptr)



        Use default initialization of the members, in-line in the class. If you have:



        node* root = nullptr;


        then your default constructor will be generated automatically.



        enum class direction

        is_root,
        left,
        right
        ;


        Last time I implemented a type of binary tree (the outer layer of a “T Tree”, with auto-balancing inserts and deletes) I took advantage of the symmetry between left and right to not write all the code twice. Instead of separately named left and right child nodes, I made an array of 2 children, so I have child[0] and child[1].



        All the logic that deals with left vs. right is mirrored with the key less vs greater. So I implement it once using the abstract S and D instead of Left and Right, and switch which is which in the key comparison. That is, on the less-than case, S is left and D is right; on the greater-than case, the other way around. Since their values are 0 and 1, the conditional code just sets S and then D is always 1-S. child[S] will be either left or right, depending on the choice. Follow me?



        Although, it is the balancing code that is most of the work (and this eliminated many test cases too since it was all the same). If you do AVL tree or the like next, keep that in mind!




        parent_to_child is unusual. It means you need the follow-up switch statement in delete_if_exists and the extra state in direction (or maybe the whole enum at all, in your case). I think it is more normal to return a pointer to the parent as well as a pointer to the found node (if any). That also works when you do not have a parent pointer in each node (the purest form of binary tree does not).




         if (node_with_value == nullptr)

        if (node_with_value->left == nullptr)


        Don’t compare directly against nullptr. Use the contextual conversion to bool as a truth value that is meant for this purpose. This is especially when you start using smart pointers, as they implement an efficient operator bool rather than needing to do a comparison directly.






        share|improve this answer













        binary_search_tree() :
        root(nullptr)



        Use default initialization of the members, in-line in the class. If you have:



        node* root = nullptr;


        then your default constructor will be generated automatically.



        enum class direction

        is_root,
        left,
        right
        ;


        Last time I implemented a type of binary tree (the outer layer of a “T Tree”, with auto-balancing inserts and deletes) I took advantage of the symmetry between left and right to not write all the code twice. Instead of separately named left and right child nodes, I made an array of 2 children, so I have child[0] and child[1].



        All the logic that deals with left vs. right is mirrored with the key less vs greater. So I implement it once using the abstract S and D instead of Left and Right, and switch which is which in the key comparison. That is, on the less-than case, S is left and D is right; on the greater-than case, the other way around. Since their values are 0 and 1, the conditional code just sets S and then D is always 1-S. child[S] will be either left or right, depending on the choice. Follow me?



        Although, it is the balancing code that is most of the work (and this eliminated many test cases too since it was all the same). If you do AVL tree or the like next, keep that in mind!




        parent_to_child is unusual. It means you need the follow-up switch statement in delete_if_exists and the extra state in direction (or maybe the whole enum at all, in your case). I think it is more normal to return a pointer to the parent as well as a pointer to the found node (if any). That also works when you do not have a parent pointer in each node (the purest form of binary tree does not).




         if (node_with_value == nullptr)

        if (node_with_value->left == nullptr)


        Don’t compare directly against nullptr. Use the contextual conversion to bool as a truth value that is meant for this purpose. This is especially when you start using smart pointers, as they implement an efficient operator bool rather than needing to do a comparison directly.







        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered Jun 3 at 23:03









        JDługosz

        5,047731




        5,047731






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195767%2fgeneric-binary-search-tree-in-c%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Greedy Best First Search implementation in Rust

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            C++11 CLH Lock Implementation