#include // for std::pair #include template class TreeNode { public: T key; TreeNode* left; TreeNode* right; // one way to allow implementation of iterator increment & decrement TreeNode* parent; // constructor TreeNode(const T& k) { key = k; left = NULL; right = NULL; } }; // ------------------------------------------------------------------- // TREE NODE ITERATOR CLASS template class tree_iterator { public: // default constructor tree_iterator() = default; // including = default explicitly tells someone reading the code that // you intend for the compiler to generate the default constructor. // It makes your intent clearer. // constructor tree_iterator(std::vector*> p) : ptrs(p) {} // copy constructor tree_iterator(const tree_iterator& other) : ptrs(other.ptrs) {} // destructor ~tree_iterator() {} // assignment operator tree_iterator& operator=(const tree_iterator& other) { ptrs = other.ptrs; return *this; } // operator* gives constant access to the value at the pointer const T& operator*() const { return (ptrs.back())->key; } // comparison operators are straightforward bool operator== (const tree_iterator& other) { return ptrs == other.ptrs; } bool operator!= (const tree_iterator& other) { return ptrs != other.ptrs; } // increment & decrement operators tree_iterator & operator++() { // prefix ++ if (ptrs.empty()) return *this; // end() reached // ptrs.back() gives us the pointer points to the current node, i.e, that's where we are now. TreeNode* curr = ptrs.back(); // case 1: has right subtree if (curr->right) { curr = curr->right; ptrs.push_back(curr); // go as left as possible while (curr->left) { curr = curr->left; ptrs.push_back(curr); } } // case 2: no right subtree, go up else { TreeNode* last = ptrs.back(); ptrs.pop_back(); // keep going up as long as I am (here I means last) the right child of my parent. while (!ptrs.empty() && ptrs.back()->right == last) { last = ptrs.back(); ptrs.pop_back(); } // if ptrs empty now, we hit end() } // when we return, ptrs.back() should point to the destination node. return *this; } tree_iterator operator++(int) { tree_iterator temp(*this); ++(*this); return temp; } tree_iterator & operator--() { /* implementation omitted */ } tree_iterator operator--(int) { tree_iterator temp(*this); --(*this); return temp; } private: // representation std::vector*> ptrs; // store pointers to every node which is on the path from root to current node }; // internally it's a binary search tree template class ds_set { public: ds_set(){ root = NULL; m_size = 0; } typedef tree_iterator iterator; int size() { return m_size; } iterator find(const T& key){ std::vector*> ptrs; return find(key, root, ptrs); } std::pair insert(const T& key){ std::pair temp; std::vector*> ptrs; temp = insertHelper(key, root, ptrs); if(temp.second == true){ ++m_size; } return temp; } void erase(const T& key){ eraseHelper(key, root); } // ITERATORS // return an iterator to the first (leftmost) node of the binary search tree, // which can be found by traversing to the leftmost node starting from the root. tree_iterator begin() const { std::vector*> ptrs; TreeNode* p = root; while (p) { ptrs.push_back(p); p = p->left; // go left } return tree_iterator(ptrs); } tree_iterator end() const { return tree_iterator(); } private: TreeNode* root; int m_size; iterator find(const T& key, TreeNode* root, std::vector*>& ptrs); // as there are multiple templated classes involved, writing this function outside of the class definition may be too complicated. // helper function to perform the insertion std::pair insertHelper(const T& key, TreeNode*& node, std::vector*>& ptrs) { // if node is nullptr, insert the key here if (!node) { node = new TreeNode(key); ptrs.push_back(node); // add the new node to the path return {iterator(ptrs), true}; // return an iterator to the new node, and true for success } // if key is already present in the tree, no insertion occurs if (key == node->key) { return {iterator(ptrs), false}; // return an iterator to the existing node, and false for failure } // add the current node to the path ptrs.push_back(node); // recursively insert into the left or right subtree if (key < node->key) { return insertHelper(key, node->left, ptrs); // Traverse left } else { return insertHelper(key, node->right, ptrs); // Traverse right } } // must pass root by reference here because we might change it. void eraseHelper(const T& key, TreeNode*& root){ if (root == NULL) return; if (root->key == key) { if (root->left == NULL && root->right == NULL){ // no child, just delete delete root; root = NULL; } else if (root->left == NULL){ // doesn't have a left, let the right child take over TreeNode* temp = root; root = root->right; delete temp; } else if (root->right == NULL){ // doesn't have a right, let the left child take over TreeNode* temp = root; root = root->left; delete temp; } else { // has both left and right // let the leftmost node of the right subtree take over TreeNode* tmp = root->right; while (tmp->left) { tmp = tmp->left; } root->key = tmp->key; // but then remove that leftmost node of the right subtree. eraseHelper(tmp->key, root->right); } } else if (root->key > key) { // search on the left subtree and erase eraseHelper(key, root->left); } else { // search on the right subtree and erase eraseHelper(key, root->right); } } }; template typename ds_set::iterator ds_set::find(const T& key, TreeNode* root, std::vector*>& ptrs){ // base case (if root doesn't even exist) if(root == NULL){ return end(); } // add current node to the path ptrs.push_back(root); // general case if(key < root->key){ return find(key, root->left, ptrs); }else if(key > root->key){ return find(key, root->right, ptrs); }else{ return tree_iterator(ptrs); } }