I'm implementing several data structures in an attempt to learn C++. Below is a binary search tree that I've implemented to learn about pointers, dangling pointers, and memory leaks. I was hoping someone could critique my code, point out any problems that they may find, or any inconsistencies. Please, be as harsh you feel necessary.
Note: As far as I know this implementation works well. Although, I feel as if the remove function could be simplified some how.
BinarySearchTree.h
//
// BinarySearchTree.h
// Data Structures
#ifndef __Data_Structures__BinarySearchTree__
#define __Data_Structures__BinarySearchTree__
#include <stdio.h>
#include <functional>
#pragma mark - Enumerations
typedef enum : int
{
TraversalTypeInOrder,
TraversalTypePreOrder,
TraversalTypePostOrder
} TraversalType;
#pragma mark -
#pragma mark - Class Definition
template<class T>
class BinarySearchTree
{
#pragma mark - Structures
template<typename Key>
struct Node
{
Key key;
Node<Key> * left = nullptr;
Node<Key> * right = nullptr;
Node<Key> * parent = nullptr;
};
#pragma mark -
#pragma mark - Private Member Variables
Node<T> * root = nullptr;
#pragma mark -
#pragma mark - Private Helper Functions
T minimum(Node<T> * node)
{
if (node->left == nullptr)
return node->key;
return minimum(node->left);
}
T maximum(Node<T> * node)
{
if (node->right == nullptr)
return node->key;
return maximum(node->right);
}
#pragma mark -
#pragma mark - Private Action Functions
void insert(const T &key, Node<T> * node)
{
if (key < node->key)
{
if (node->left)
{
insert(key, node->left);
}
else {
Node<T> * left = new Node<T>();
left->key = key;
left->parent = node;
node->left = left;
}
}
else {
if (node->right)
{
insert(key, node->right);
}
else {
Node<T> * right = new Node<T>();
right->key = key;
right->parent = node;
node->right = right;
}
}
}
Node<T> * search(const T &key, Node<T> * node)
{
if (node == nullptr)
return nullptr;
if (key == node->key)
{
return node;
}
else if (key < node->key)
{
return search(key, node->left);
}
else {
return search(key, node->right);
}
}
void traverse(TraversalType traversalType, std::function<void(T key)> printFunctor, Node<T> * node)
{
if (node == nullptr)
return;
if (traversalType == TraversalTypePreOrder)
printFunctor(node->key);
traverse(traversalType, printFunctor, node->left);
if (traversalType == TraversalTypeInOrder)
printFunctor(node->key);
traverse(traversalType, printFunctor, node->right);
if (traversalType == TraversalTypePostOrder)
printFunctor(node->key);
}
#pragma mark -
public:
#pragma mark - Life Cycle Methods
~BinarySearchTree()
{
removeAll();
}
#pragma mark -
#pragma mark - Public Helper Functions
T minimum()
{
return minimum(root);
}
T maximum()
{
return maximum(root);
}
#pragma mark -
#pragma mark - Public Actions Functions
void insert(const T &key)
{
if (root == nullptr)
{
root = new Node<T>();
root->key = key;
}
else {
insert(key, root);
}
}
void remove(const T &key)
{
Node<T> * node = search(key);
if (node == nullptr)
return;
if (node->left != nullptr && node->right != nullptr)
{
T successorKey = minimum(node->right);
remove(successorKey);
node->key = successorKey;
}
else if (node->left == nullptr && node->right == nullptr)
{
if (node == root)
{
root = nullptr;
}
else {
if (node == node->parent->left)
node->parent->left = nullptr;
else
node->parent->right = nullptr;
}
delete node;
}
else {
if (node == root)
{
if (node->left != nullptr)
root = node->left;
else
root = node->right;
}
else {
T successorKey;
if (node->left != nullptr)
{
successorKey = maximum(node->left);
remove(successorKey);
node->key = successorKey;
}
else {
successorKey = minimum(node->right);
remove(successorKey);
node->key = successorKey;
}
}
}
}
Node<T> * search(const T &key)
{
return search(key, root);
}
void traverse(TraversalType traversalType, std::function<void(T key)> printFunctor)
{
traverse(traversalType, printFunctor, root);
}
void removeAll()
{
if (root == nullptr)
return;
remove(root->key);
removeAll();
}
#pragma mark -
};
#pragma mark -
#endif /* defined(__Data_Structures__BinarySearchTree__) */
main.cpp
//
// main.cpp
// Data Structures
#include <iostream>
#include <cstdlib>
#include "BinarySearchTree.h"
int main(int argc, const char * argv[])
{
srand((unsigned)time(NULL));
BinarySearchTree<int> binarySearchTree;
binarySearchTree.insert(11);
binarySearchTree.insert(9);
binarySearchTree.insert(8);
binarySearchTree.insert(10);
binarySearchTree.insert(14);
binarySearchTree.insert(13);
binarySearchTree.insert(15);
auto printNode = [](int key) -> void { printf("%d ", key); };
binarySearchTree.traverse(TraversalTypePreOrder, printNode);
printf("\n");
binarySearchTree.remove(10);
printf("\n");
binarySearchTree.traverse(TraversalTypePreOrder, printNode);
return 0;
}
typedef enum
ortypedef struct
in C++. That's done on C code to avoid having to qualify each usage withenum/struct
. In C++, the name you give to the enum or struct is already a first class name, so the extratypedef
in is unnecessary. – glampert Jul 16 '15 at 2:39