Table of Contents
Introduction
A. J. Perlis and C. Thornton proposed a new version of a binary tree called “Threaded Binary Tree” that uses NULL pointers to improve its traversal process. In a threaded binary tree, NULL pointers are replaced by references of other nodes in the tree. These extra references are called threads.
Although threads can be deleted in any binary tree, it is most efficient to delete threads in a binary search tree or its variants, i.e. a tree that meets the Binary Search Tree properties. As a result, the Threaded Binary Tree we will use in this blog is a Binary Search Tree having Threads (threaded binary search tree).
Node Structure of Threaded Binary tree
struct Node{ int data; Node *left, *right; // false if the right pointer points to inorder successor bool rightThread; // false if the left pointer points to inorder predecessor bool leftThread; };
Before moving directly to Deletion in the Threaded Binary Search Tree, let us first look at the basics of the Threaded Binary Tree.
Deletion in Threaded Binary Search Tree
For deletion in the Threaded Binary Search Tree, we will first search the value to be deleted, and then we will see the possible cases for deleting the node in which the value is present.
// For deleting the value from threaded BST with the given root // and returning a new root of BST struct Node* dThreadedBST(struct Node* root, int value) { // Function for initializing the parent node to NULL and current node to root. struct Node *parent = NULL, *current = root; // Set true if value is found int found = 0; // Now, we will search value in BST while (current != NULL) { if (value == current->data) { found = 1; break; } parent = current; if (value < current->data) { if (current->leftThread == false) current = current->left; else break; } else { if (current->rightThread == false) current = current->right; else break; } } if (found == 0) printf("The value is not present in tree\n"); // When the node to be deleted has two children else if (current->leftThread == false && current->rightThread == false) root = case3(root, parent , current); // When the node to be deleted has only left child else if (current->leftThread == false) root = case2(root, parent , current); // When the node to be deleted has only right child else if (current->rightThread == false) root = case2(root, parent , current); // When a leaf node needs to be deleted else root = case1(root, parent , current); return root; }
We can use the delete operation to remove a node from a threaded binary search tree. However, there are three cases for removing it.
Case 1: When a leaf node needs to be deleted
When deleting a leaf Node in BST, the left or right pointer of the parent node is set to NULL. Whereas in Threaded binary search trees, it is turned into a thread instead of setting the pointer to NULL.
If the leaf Node is the left child of its parent, then after deletion, the parent’s left pointer should become a thread referring to its predecessor.
Let’s understand this using an example.
In the tree given above, if we delete node 18, then the left pointer of its parent node, node 20, will point to its predecessor node 15 (as shown below).
Similarly, If the leaf Node is the right child of its parent, then after deletion, the parent’s right pointer should become a thread referring to its successor. After deletion, the Node that was the in-order successor of the leaf Node will now become the in-order successor of the parent Node.
Now, let’s have a look at the code:
// Here, 'parent ' is a pointer referring to the parent node // and 'current' is a pointer to the current node. struct Node* case1 (struct Node* root, struct Node* parent, struct Node* current) { // If root node has to be deleted if (parent == NULL) root = NULL; // If the node to be deleted is left child of its parent else if (current == parent ->left) { parent ->leftThread = true; parent ->left = current->left; } // If the node to be deleted is the right child of its parent else { parent ->rightThread = true; parent ->right = current->right; } // Now memory is freed, and new root is returned free(current); return root; }
Case 2: When the node to be deleted has only one child
After deleting the Node like in a BST the inorder successor and predecessor of the Node are found.
s = inSucc(current); p = inPred(current);
If Node to be deleted has a left subtree, then after deletion, the right thread of its predecessor should point to its successor.
Let’s understand this using an example.
In the figure given above, node 24 has node 23 as its predecessor and node 30 as its successor. So, after deleting node 24, node 30 becomes the successor of node 23, so the right thread of 23 will point to 30 (as shown in the figure given below).
Similarly, if Node to be deleted has a right subtree, then after deletion, the left thread of its successor should point to its predecessor.
Now, let’s have a look at the code:
// Here, 'parent ' is a pointer referring to the parent node // and 'current' is a pointer to the current node. struct Node* case2 (struct Node* root, struct Node* parent, struct Node* current) { struct Node* child; // First initialize whether the child Node to be deleted has left child. if (current->leftThread == false) child = current->left; // or a right child else child = current->right; // If root node has to be deleted if (parent == NULL) root = child; // If the node to be deleted is left child of its parent else if (current == parent ->left) parent ->left = child; // If the node to be deleted is right child of its parent else parent ->right = child; // Find successor and predecessor Node* s = inSucc(current); Node* p = inPred(current); // If current node has left subtree if (current->leftThread == false) p->right = s; // If current node has right subtree. else { if (current->rightThread == false) s->left = p; } // Now memory is freed and new root is returned free(current); return root; }
Case 3: When the node to be deleted has two children
We find the in-order successor of the current Node (Node to be deleted) and then copy the information of this successor into the current Node. After this, the in-order successor Node is deleted using either Case 1 or Case 2.
Let’s have a look at the code:
// Here, 'parent ' is a pointer referring to the parent node // and 'current' is a pointer to the current node struct Node* case3(struct Node* root, struct Node* parent, struct Node* current) { // Find the inorder successor struct Node* parsucc = current; struct Node* succ = current->right; // Find the leftmost child while (succ->leftThread==false) { parsucc = succ; succ = succ->left; } current->data = succ->data; if (succ->leftThread == true && succ->rightThread == true) root = case1(root, parsucc, succ); else root = case2(root, parsucc, succ); return root; }
Complete Code for Deletion
#include <bits/stdc++.h> using namespace std; struct Node { struct Node *left, *right; int data; // false if the left pointer points to a predecessor of in Inorder Traversal bool leftThread; // false if the right pointer points to a predecessor of in Inorder Traversal bool rightThread; }; // For inserting a Node in Threaded Binary Tree struct Node* insert(struct Node* root, int key) { // Searching for a Node with given value Node* current = root; Node* parent = NULL; while (current != NULL) { // If value already exists, then we will return the root if (key == (current->data)) { printf("Duplicate value !\n"); return root; } // Updating the parent pointer parent = current; // Taking the left subtree if (key < current->data) { if (current->leftThread == false) current = current->left; else break; } // Taking the right subtree else { if (current->rightThread == false) current = current->right; else break; } } // Creating a new Node Node* temp = new Node; temp->data = key; temp->leftThread = true; temp->rightThread = true; if (parent == NULL) { root = temp; temp->left = NULL; temp->right = NULL; } else if (key < (parent->data)) { temp->left = parent->left; temp->right = parent; parent->leftThread = false; parent->left = temp; } else { temp->left = parent; temp->right = parent->right; parent->rightThread = false; parent->right = temp; } return root; } // For returning the inorder successor using left and right children struct Node* inSucc(struct Node* current) { if (current->rightThread == true) return current->right; current = current->right; while (current->leftThread == false) current = current->left; return current; } // For returning inorder successor using rightThread struct Node* inorderSuccessor(struct Node* current) { // If rightThread is set, we can quickly find if (current->rightThread == true) return current->right; // Or return the leftmost child of right subtree current = current->right; while (current->leftThread == false) current = current->left; return current; } // Print the threaded tree void inorder(struct Node* root) { if (root == NULL) printf("Tree is empty"); // Taking leftmost Node struct Node* current = root; while (current->leftThread == false) current = current->left; while (current != NULL) { printf("%d ", current->data); current = inorderSuccessor(current); } } struct Node* inPred(struct Node* current) { if (current->leftThread == true) return current->left; current = current->left; while (current->rightThread == false) current = current->right; return current; } // Here 'parent ' is a pointer referring to the parent node // and 'current' is a pointer to the current node. struct Node* case1(struct Node* root, struct Node* parent,struct Node* current) { // If root node has to be deleted if (parent == NULL) root = NULL; // If the node to be deleted is left child of its parent else if (current == parent->left) { parent->leftThread = true; parent->left = current->left; } // If the node to be deleted is the right child of its parent else { parent->rightThread = true; parent->right = current->right; } // Now memory is freed and new root is returned free(current); return root; } // Here 'parent ' is a pointer referring to the parent node // and 'current' is a pointer to the current node. struct Node* case2 (struct Node* root, struct Node* parent, struct Node* current) { struct Node* child; // First initialize whether the child Node to be deleted has left child. if (current->leftThread == false) child = current->left; // or a right child else child = current->right; // If root node has to be deleted if (parent == NULL) root = child; // If the node to be deleted is left child of its parent else if (current == parent ->left) parent ->left = child; // If the node to be deleted is right child of its parent else parent ->right = child; // Find successor and predecessor Node* s = inSucc(current); Node* p = inPred(current); // If current node has left subtree if (current->leftThread == false) p->right = s; // If current node has right subtree. else { if (current->rightThread == false) s->left = p; } // Now memory is freed and new root is returned free(current); return root; } // Here 'parent' is a pointer referring to the parent node // and 'current' is a pointer to the current node struct Node* case3(struct Node* root, struct Node* parent, struct Node* current) { // Find the inorder successor struct Node* parsucc = current; struct Node* succ = current->right; // Find the leftmost child while (succ->leftThread==false) { parsucc = succ; succ = succ->left; } current->data = succ->data; if (succ->leftThread == true && succ->rightThread == true) root = case1(root, parsucc, succ); else root = case2(root, parsucc, succ); return root; } // For deleting the value from threaded BST with the given root // and returning a new root of BST struct Node* dThreadedBST(struct Node* root, int value) { // For initializing the parent node as NULL and current node as root. struct Node *parent = NULL, *current = root; // Set true if value is found int found = 0; // Searching value in BST while (current != NULL) { if (value == current->data) { found = 1; break; } parent = current; if (value < current->data) { if (current->leftThread == false) current = current->left; else break; } else { if (current->rightThread == false) current = current->right; else break; } } if (found == 0) printf("The value not present in tree\n"); // When the node to be deleted has two children else if (current->leftThread == false && current->rightThread == false) root = case3(root, parent , current); // When the node to be deleted has only left child else if (current->leftThread == false) root = case2(root, parent , current); // When the node to be deleted has only right child else if (current->rightThread == false) root = case2(root, parent , current); // When a leaf node needs to be deleted else root = case1(root, parent , current); return root; } // Driver Program int main() { struct Node* root = NULL; root = insert(root, 45); root = insert(root, 8); root = insert(root, 24); root = insert(root, 20); root = insert(root, 15); root = insert(root, 30); root = insert(root, 23); root = insert(root, 18); root = dThreadedBST(root, 24); inorder(root); return 0; }
Output
8 15 18 20 23 30 45
Time complexity – O(h), where h is the height of the threaded binary search tree. The height of the tree can be ‘n’ in the worst case, and all the keys are deleted in ascending or descending order (Skewed Tree) where all the nodes except the leaf have only one child, and we may have to travel from root to the deepest leaf node.
Space complexity – O(1), as we are not using any additional auxiliary space.
Frequently Asked Questions
A threaded binary search tree is a variation of a normal binary search tree. In a threaded binary search tree, if the left pointer of a node is NULL, it is pointed to its in-order predecessor (if it exists), and if the right pointer of a node is NULL, it is pointed to its in-order successor (if it exists) using links called as threads.
Yes, we can perform deletion in a threaded binary search tree like it is performed in a binary search tree. First, we will search the value to be deleted and then see the possible cases for deleting it. There are mainly three cases when performing deletion:
1. When a leaf node is to be deleted.
2. When the node to be deleted has only one child.
3. When the node to be deleted has two children.
Some advantages of a Threaded Binary tree are:
1. They do not require a stack or recursion for their traversal.
2. In threaded binary trees, memory is not wasted as whenever a node’s left/right pointer is NULL, its inorder predecessor/successor is stored.
3. We can do a backward traversal in a double-threaded binary tree.
4. In-order traversal is fast in a threaded binary tree than a normal binary tree.
Key Takeaways
In this article, we learned about ways to delete a node in threaded binary search trees. Majorly three prominent cases arrive when we try to delete a node. The first one is when the node is a leaf node, the second one when the node to be deleted has only one child (that means left or right pointer is not NULL), and the third one is when the node has two children (that means both left and right child pointers are not NULL). Hence, we traversed the tree, keeping in mind all three cases.
By: Mehak Goel
Leave a Reply