From f4d237e440419a72ffd1f9174580665d56003d26 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 16:05:55 -0400 Subject: [PATCH 1/6] initial commit --- DS/C++/threadedtree.h | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 DS/C++/threadedtree.h diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h new file mode 100644 index 00000000..5379e09b --- /dev/null +++ b/DS/C++/threadedtree.h @@ -0,0 +1,11 @@ +/*************************************************** + +Notes: +This an implementation of threaded tree in C++ +Threaded tree is a binary search tree that allows for easier in-order traversal +For each node, all right/ left child pointers that would normally be null instead point to the in-order successor node +This implementation supports generic typing and both forward and backward in-order traversal, but does not handle duplicate values + +Iterator implementation also included to facilitate easy traversal via operators + +***************************************************/ \ No newline at end of file From 44cbf594c893c3b1873446caeea63a15eb2e5776 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 16:40:25 -0400 Subject: [PATCH 2/6] basic threadedtree class --- DS/C++/threadedtree.h | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h index 5379e09b..f2317fc6 100644 --- a/DS/C++/threadedtree.h +++ b/DS/C++/threadedtree.h @@ -8,4 +8,27 @@ This implementation supports generic typing and both forward and backward in-ord Iterator implementation also included to facilitate easy traversal via operators -***************************************************/ \ No newline at end of file +***************************************************/ + +#include +using namespace std; + +template +class ThreadedTree { + struct Node{ + T data_; + Node* left_; + Node* right_; + int lFlag_; // Whether the node has a left thread, 0 = no, 1 = yes + int rFlag_; // Whether the node has a right thread, 0 = no, 1 = yes + + Node(const T& data, Node* lt = nullptr, Node* rt = nullptr, int lFlag = 0, int rFlag = 0) { + data_ = data; + left_ = lt; + right_ = rt; + lFlag_ = lFlag; + rFlag_ = rFlag; + } + }; + Node* root_; +}; \ No newline at end of file From 6ba433edd4b3fe5891ac48f6606e0c0823b1f260 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 17:28:48 -0400 Subject: [PATCH 3/6] const iterator --- DS/C++/threadedtree.h | 170 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h index f2317fc6..c812d1fe 100644 --- a/DS/C++/threadedtree.h +++ b/DS/C++/threadedtree.h @@ -6,7 +6,7 @@ Threaded tree is a binary search tree that allows for easier in-order traversal For each node, all right/ left child pointers that would normally be null instead point to the in-order successor node This implementation supports generic typing and both forward and backward in-order traversal, but does not handle duplicate values -Iterator implementation also included to facilitate easy traversal via operators +Const and non-const iterator implementation also included to facilitate easy traversal via operators ***************************************************/ @@ -31,4 +31,172 @@ class ThreadedTree { } }; Node* root_; + +public: + class const_iterator{ + private: + const ThreadedTree* myTree_; + Node* curr_; + + // Private constructor + const_iterator(Node* curr, const ThreadedTree* theTree) { + curr_ = curr; + myTree_ = theTree; + } + + public: + + // Constructor + const_iterator(){ + myTree_ = nullptr; + curr_ = nullptr; + } + + // Postfix ++ increments iterator and returns old value + // @return: iterator containing old value + const_iterator operator++(int){ + const_iterator old = *this; + + // If the current node has a right thread, we use that to move forward + if (curr_->rFlag_ == 1) { + curr_ = curr_->right_; + } + // If it doesn't have a right thread, we need to move to the left-most node in its right subtree + // However we only move forward if the right node isn't nullptr (i.e. we're at max value already) + else { + curr_ = curr_->right_; + + if (curr_ != nullptr) { + while (curr_->left_ != nullptr && curr_->lFlag_ != 1) { + curr_ = curr_->left_; + } + } + } + + //Return the old node + return old; + } + + // Postfix -- decrements iterator and returns old value + // @return: iterator containing old value + const_iterator operator--(int){ + const_iterator old = *this; + + // If the current node is nullptr, we're at end, move back to actual last value + // Could also implement an end sentinel, which would be more robust, but I don't want to risk breaking the tester + if (curr_ == nullptr) { + if (myTree_->root_ != nullptr) { + curr_ = myTree_->root_; + while (curr_->right_ != nullptr) { + curr_ = curr_->right_; + } + } + } + else { + // If the current node has a left thread, we use that to move backwards + if (curr_->lFlag_ == 1) { + curr_ = curr_->left_; + } + // If it doesn't have a left thread, we need to move to the right-most node in its left subtree + // However we only move backward if the left node isn't nullptr (i.e. we're at least value already) + else if (curr_->left_ != nullptr) { + curr_ = curr_->left_; + + while (curr_->right_ != nullptr && curr_->rFlag_ != 1) { + curr_ = curr_->right_; + } + } + } + //Return the old node + return old; + } + + // Prefix ++ increments iterator and returns new value + // @return: iterator containing new value + const_iterator operator++(){ + // If the current node has a right thread, we use that to move forward + if (curr_->rFlag_ == 1) { + curr_ = curr_->right_; + } + // If it doesn't have a right thread, we need to move to the left-most node in its right subtree + // However we only move forward if the right node isn't nullptr (i.e. we're at max value already) + else { + curr_ = curr_->right_; + + if (curr_ != nullptr) { + while (curr_->left_ != nullptr && curr_->lFlag_ != 1) { + curr_ = curr_->left_; + } + } + } + + //Return the new node + return *this; + } + + // Prefix -- decrements iterator and returns new value + // @return: iterator containing new value + const_iterator operator--(){ + // If the current node is nullptr, we're at end, move back to actual last value + if (this->curr_ == nullptr) { + if (myTree_->root_ != nullptr) { + curr_ = myTree_->root_; + while (curr_->right_ != nullptr) { + curr_ = curr_->right_; + } + } + } + else { + // If the current node has a left thread, we use that to move backwards + if (curr_->lFlag_ == 1) { + curr_ = curr_->left_; + } + // If it doesn't have a left thread, we need to move to the right-most node in its left subtree + // However we only move backward if the left node isn't nullptr (i.e. we're at least value already) + else if (curr_->left_ != nullptr) { + curr_ = curr_->left_; + + while (curr_->right_ != nullptr && curr_->rFlag_ != 1) { + curr_ = curr_->right_; + } + } + } + //Return the new node + return *this; + } + + // Dereference operator returns the data value stored in the current node + // @return: current node's data value + const T& operator*() const{ + return curr_->data_; + } + + // Returns whether or not the current iterator is the same as the rhs iterator + // @return: True/ False if the iterators are the same + bool operator==(const const_iterator& rhs) const{ + bool ret = false; + + //Test if RHS both the same node and in the same ThreadedTree + if (myTree_ == rhs.myTree_ && curr_ == rhs.curr_) { + ret = true; + } + + return ret; + } + + // Returns whether or not the current iterator is NOT the same as the rhs iterator + // @return: True/ False if the iterators are NOT the same + bool operator!=(const const_iterator& rhs) const{ + bool ret = false; + + //Test if either RHS is not in the same tree, or in the same tree but not the same node + if (myTree_ != rhs.myTree_ || curr_ != rhs.curr_) { + ret = true; + } + + return ret; + } + + friend class ThreadedTree; + }; }; \ No newline at end of file From 651c2a52dbca906586df8ed289a0191f9c8cf955 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 17:54:26 -0400 Subject: [PATCH 4/6] non-const iterator --- DS/C++/threadedtree.h | 130 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h index c812d1fe..4d3c58c6 100644 --- a/DS/C++/threadedtree.h +++ b/DS/C++/threadedtree.h @@ -13,6 +13,7 @@ Const and non-const iterator implementation also included to facilitate easy tra #include using namespace std; +// Generic type threaded tree template class ThreadedTree { struct Node{ @@ -33,6 +34,7 @@ class ThreadedTree { Node* root_; public: + // Const iterator member class const_iterator{ private: const ThreadedTree* myTree_; @@ -45,7 +47,6 @@ class ThreadedTree { } public: - // Constructor const_iterator(){ myTree_ = nullptr; @@ -197,6 +198,131 @@ class ThreadedTree { return ret; } - friend class ThreadedTree; + friend class ThreadedTree; // Make friend to access threaded tree members + }; + + // Non-const iterator member + class iterator:public const_iterator{ + private: + //Private constructor calls the constant iterator's private constructor + iterator(Node* curr, ThreadedTree* theTree) :const_iterator(curr, theTree) {} + + public: + iterator():const_iterator(){} + + const T& operator*() const{ + return this->curr_->data_; + } + + T& operator*(){ + return this->curr_->data_; + } + + iterator operator++(int){ + iterator old = *this; + + // If the current node has a right thread, we use that to move forward + if (this->curr_->rFlag_ == 1) { + this->curr_ = this->curr_->right_; + } + // If it doesn't have a right thread, we need to move to the left-most node in its right subtree + // However we only move forward if the right node isn't nullptr (i.e. we're at max value already) + else { + this->curr_ = this->curr_->right_; + + if (this->curr_ != nullptr) { + while (this->curr_->left_ != nullptr && this->curr_->lFlag_ != 1) { + this->curr_ = this->curr_->left_; + } + } + } + + //Return the old node + return old; + } + + iterator operator--(int){ + iterator old = *this; + + // If the current node is nullptr, we're at end, move back to actual last value + if (this->curr_ == nullptr) { + if (this->myTree_->root_ != nullptr) { + this->curr_ = this->myTree_->root_; + while (this->curr_->right_ != nullptr) { + this->curr_ = this->curr_->right_; + } + } + } + else { + // If the current node has a left thread, we use that to move backwards + if (this->curr_->lFlag_ == 1) { + this->curr_ = this->curr_->left_; + } + // If it doesn't have a left thread, we need to move to the right-most node in its left subtree + // However we only move backward if the left node isn't nullptr (i.e. we're at least value already) + else if (this->curr_->left_ != nullptr) { + this->curr_ = this->curr_->left_; + + while (this->curr_->right_ != nullptr && this->curr_->rFlag_ != 1) { + this->curr_ = this->curr_->right_; + } + } + } + //Return the old node + return old; + } + + iterator operator++(){ + // If the current node has a right thread, we use that to move forward + if (this->curr_->rFlag_ == 1) { + this->curr_ = this->curr_->right_; + } + // If it doesn't have a right thread, we need to move to the left-most node in its right subtree + // However we only move forward if the right node isn't nullptr (i.e. we're at max value already) + else { + this->curr_ = this->curr_->right_; + + if (this->curr_ != nullptr) { + while (this->curr_->left_ != nullptr && this->curr_->lFlag_ != 1) { + this->curr_ = this->curr_->left_; + } + } + } + + //Return the new node + return *this; + } + + iterator operator--(){ + // If the current node is nullptr, we're at end, move back to actual last value + if (this->curr_ == nullptr) { + if (this->myTree_->root_ != nullptr) { + this->curr_ = this->myTree_->root_; + while (this->curr_->right_ != nullptr) { + this->curr_ = this->curr_->right_; + } + } + } + else { + // If the current node has a left thread, we use that to move backwards + if (this->curr_->lFlag_ == 1) { + this->curr_ = this->curr_->left_; + } + // If it doesn't have a left thread, we need to move to the right-most node in its left subtree + // However we only move backward if the left node isn't nullptr (i.e. we're at least value already) + else if (this->curr_->left_ != nullptr) { + this->curr_ = this->curr_->left_; + + while (this->curr_->right_ != nullptr && this->curr_->rFlag_ != 1) { + this->curr_ = this->curr_->right_; + } + } + } + + //Return the new node + return *this; + } + + friend class ThreadedTree; // Make friend to access threaded tree members }; }; \ No newline at end of file From 29af89640d315251a3c96736305e9ccb878e80a9 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 18:16:32 -0400 Subject: [PATCH 5/6] insertions --- DS/C++/threadedtree.h | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h index 4d3c58c6..2ec600d8 100644 --- a/DS/C++/threadedtree.h +++ b/DS/C++/threadedtree.h @@ -325,4 +325,89 @@ class ThreadedTree { friend class ThreadedTree; // Make friend to access threaded tree members }; + + // Default constructor, sets root node to nullptr + ThreadedTree(){ + root_ = nullptr; + } + + // Inserts a new value into the tree + // @params data: the data value to insert + void insert(const T& data){ + + if (root_ == nullptr) { + root_ = new Node(data); + } + else { + recursiveInsert(root_, data); + } + } + + // Helper function that recursively searches for the correct place to insert a node + // @params node: the current node being inspected while looking for insertion location + // @params parent: the inspected node's immediate parent node + // @params data: the data value to insert + // @return: returns the node modified by the insert (when called recurisely, this chains all the way up to the root) + void recursiveInsert(Node* node, const T& data) { + // If the data is less than the current node's, inspect to the left + if (data < node->data_) { + // If the left node is free, we can add the new node as its left child + if (node->left_ == nullptr || node->lFlag_ == 1) { + Node* ins = new Node(data); + + // Since we are inserting to the left, the new child inherits the current node's left pointer + ins->left_ = node->left_; + // Also need to set the left thread flag of the child if it's not nullptr (i.e. if child is not the least value in the tree) + if (ins->left_ != nullptr) { + ins->lFlag_ = 1; + } + + // The child's right pointer then becomes a thread to the node (iteratively, the parent always comes after left child) + ins->rFlag_ = 1; + ins->right_ = node; + + // Clear the node's left thread flag and point its left to the newly added child + node->lFlag_ = 0; + node->left_ = ins; + + // Null the temporary pointer to avoid errors when going out of scope + ins = nullptr; + } + // If it isn't free, keep inspecting + else { + recursiveInsert(node->left_, data); + } + } + // If the data is greater than the current node's, inspect to the right + else if (data > node->data_) { + // If the right node is free, we can add the new node as its left child + if (node->right_ == nullptr || node->rFlag_ == 1) { + Node* ins = new Node(data); + + // Since we are inserting to the right, the new child inherits the current node's right pointer + ins->right_ = node->right_; + // Also need to set the right thread flag of the child if it's not nullptr (i.e. if child is not the most value in the tree) + if (ins->right_ != nullptr) { + ins->rFlag_ = 1; + } + + // The child's left pointer then becomes a thread to the node (iteratively, the parent always comes after left child) + ins->lFlag_ = 1; + ins->left_ = node; + + // Clear the node's right thread flag and point its right to the newly added child + node->rFlag_ = 0; + node->right_ = ins; + + // Null the temporary pointer to avoid errors when going out of scope + ins = nullptr; + } + // If it isn't free, keep inspecting + else { + recursiveInsert(node->right_, data); + } + } + + // Note: We have to assume that there are no duplicate values as this implementation does not support them + } }; \ No newline at end of file From 3f69d6547c31871f06a36827827e7f1cd965f153 Mon Sep 17 00:00:00 2001 From: Jerry Shueh Date: Fri, 18 Oct 2019 18:43:16 -0400 Subject: [PATCH 6/6] find functions --- DS/C++/threadedtree.h | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/DS/C++/threadedtree.h b/DS/C++/threadedtree.h index 2ec600d8..d9aed4d8 100644 --- a/DS/C++/threadedtree.h +++ b/DS/C++/threadedtree.h @@ -410,4 +410,105 @@ class ThreadedTree { // Note: We have to assume that there are no duplicate values as this implementation does not support them } + + // Helper function that recursively searches for the node with the given data + // @params node: the current node being inspected while looking for insertion location + // @params data: the data value to insert + // @return: returns the found node with matching data, nullptr (end) if not + Node* recursiveFind(Node* node, const T& data) { + Node* ret = nullptr; + + // Only inspect if the node isn't null + if (node != nullptr) { + // If the value is less than the current node's value, continue search to the left + if (data < node->data_) { + ret = recursiveFind(node->left_, data); + } + // If the data is greater than the current node's value, continue searching to the right + else if (data > node->data_) { + ret = recursiveFind(node->right_, data); + } + // If the data is a match, return the current node + else { + ret = node; + } + } + + return ret; + } + + // Finds the node with the matching data value and returns an iterator to it + // @params node: the data value to find + iterator find(const T& data){ + // The recursiveFind() function will either return a node, if found, or nullptr representing "end" if not found + iterator* ret = new iterator(recursiveFind(root_, data), this); + + return *ret; + } + + // Finds the node with the matching data value and returns a constant iterator to it + // @params node: the data value to find + const_iterator find(const T& data) const{ + // The recursiveFind() function will either return a node, if found, or nullptr representing "end" if not found + const_iterator* ret = new const_iterator(recursiveFind(root_, data), this); + + return *ret; + } + + // Returns an iterator to the left-most (least) node in the tree + // @return: iterator pointing to left-most node + iterator begin(){ + Node* ret = nullptr; + + if (root_ != nullptr) { + ret = root_; + while (ret->left_ != nullptr) { + ret = ret->left_; + } + } + + return iterator(ret, this); + } + + // Returns an iterator containing nullptr, which represents the end of the tree's traversal path + // A nullptr works as a suitable end node, because should not occur anywhere else in the tree's traversal path + // All nullptrs at non-end nodes are replaced by thread pointers in a threaded BST + // The only logical nullptr would be to the right of the maximum value, or "one past" the max node + // @return: iterator pointing to right-most node + iterator end(){ + return iterator(nullptr, this); + } + + // Returns a constant iterator to the left-most (least) node in the tree + // @return: constant iterator pointing to left-most node + const_iterator cbegin()const{ + Node* ret = nullptr; + + if (root_ != nullptr) { + ret = root_; + while (ret->left_ != nullptr) { + ret = ret->left_; + } + } + + return const_iterator(ret, this); + } + + // Returns a constant iterator containing nullptr, which represents the end of the tree's traversal path + // A nullptr works as a suitable end node, because should not occur anywhere else in the tree's traversal path + // All nullptrs at non-end nodes are replaced by thread pointers in a threaded BST + // The only logical nullptr would be to the right of the maximum value, or "one past" the max node + // @return: iterator pointing to right-most node + const_iterator cend() const{ + return const_iterator(nullptr, this); + } + + // Destructor uses postfix operator to delete nodes in tree + ~ThreadedTree(){ + iterator i = begin(); + + while (i != end()) { + delete (i++).curr_; + } + } }; \ No newline at end of file