×

Threaded Binary Trees

Introduction

Threaded Binary Trees (TBTs) are an enhancement of normal binary trees intended for in-order traversal only. This means that this data structure is developed with the objective of making the traversal process more efficient by trying to make use of other null pointers apart from the ones present in a traditional binary tree. This optimization is useful for in-order traversal, and no object such as stack or recursion is required, making the TBT useful in situations where there is limited memory and speed of processing is of the essence.

In this article, we will look at the Threaded Binary Trees in detail, including the various types and the various operations such as insertion, deletion, and traversal, among others. We will also discuss the application and limitations of these virtualization technologies in contemporary computing systems. Furthermore, the history of creating TBTs and their types and varieties will be examined, and, in addition, clear examples in various programming languages will be presented. Last of all, we shall discuss the time and space implications of these trees so that the reader gets a full understanding of this crucial data structure.

Historical Background and Development

Threaded Binary Trees were first used for the faster traversal of generic binary trees. During the in-order traversal of a typical binary tree, one has to maintain a stack to remember the nodes or else resort to recursion; both methods are overtime and space-consuming, more so when in a large tree.                           

Threaded Binary Tree

This inefficiency led to the proposal of Threaded Binary Trees as the solution to this problem. By using null pointers to thread the tree connecting nodes all the requirement for a stack or recursion is avoided when implementing a node either in-order predecessor or successor. This was especially famous in early computing when memory was a limited commodity on computers.

 In the evolution of TBTs, they made their niche to such areas that wanted to be searched over and over again, especially in large databases.

Structure of Threaded Binary Trees

To comprehend the complete idea of TBTs, one has to enter the differences in the structural changes from regular binary trees.       

Threaded Binary Tree

Components of a Threaded Binary Tree Node

A typical node in a Threaded Binary Tree contains the following components:

  1. Data: The data or knowledge that is contained in the given node of the network.
  2. Left Pointer: In a normal binary tree, it points to the left child. It may either go to the left child or the in-order predecessor in a TBT.
  3. Right Pointer: In a standard binary tree this will be a pointer pointing to the right child of the current node. In a TBT, it may either point at the right child or the in-order successor.
  4. Left Thread Indicator (lThread): A boolean flag indicating the fact whether the left pointer represents a thread or not.
  5. Right Thread Indicator (rThread): A boolean flag that is true if the right pointer is of type thread.

How Threading Works

Threading in a binary tree changes the tree's pointers to make nodes refer to their in-order predecessor or successor. This establishes a 'thread,' which can be used to trace through the tree in an in-order sequence.

  • Left Threading: If a node, say x, has no left child, then the left pointer of the node x points to the immediate previous node in the in-order traversal.
  • Right Threading: In case a node does not exist, the right child, the right link, will point to the right successor of that node.

For instance, let a node A have its right child B and a left child C; if B has no left child, we know that the left pointer of B will be null in a normal binary tree. In a TBT, instead, the B left pointer would point to A, which is the in-order predecessor of B.

Types of Threaded Binary Trees

There are several variations of TBTs, each offering different benefits depending on the application:

1. Single Threaded Binary Tree: One of the pointers that has been held in L and R's is used for threading, and the other is released for further use. This is further divided into:          

Threaded Binary Tree
  • Left Threaded Binary Tree: But only the left pointers are threaded towards the in-order predecessor.
  • Right Threaded Binary Tree: In threaded trees, only the right pointers are threaded, driving to the in-order successor.

2. Double Threaded Binary Tree: It is both left-threaded (with a particular space for thread) and right-threaded for thread, so it can execute visit-in-order bidirectional passing.

Threaded Binary Tree

In this case, it is obvious that each type of TBT is effective in some situations. For instance, it has come out that double-threaded trees are more useful because one does not have to stick to only one branch or thread as it is sometimes possible to go the other way as well, which is beneficial in places such as the evaluation of expressions in compilers.

Some operations in the Threaded Binary Trees

Each of the basic operations in a TBT, for example, insertion, deletion, and traversal takes longer time compared to those in a standard binomial tree due to the need to consider the threading structure.

Insertion

Insertion into a TBT has to be performed in such a manner that the threading of the tree does not break down. The process involves:      

Threaded Binary Tree
  • Finding the Correct Position: Move up and down the tree to get the proper place of the new node just as it will be done in AN ordinary binary tree.
  • Adjusting Pointers: For the new node to be added to the tree, update the pointers of neighboring nodes as a measure.
  • Maintaining Threads: The thread indicators should be updated so as to make sure that the new node is properly threaded.

Program:

class Node {

    int data;

    Node left, right;

    boolean lThread, rThread;


    Node(int data) {

        this.data = data;

        this.left = null;

        this.right = null;

        this.lThread = true;

        this.rThread = true;

    }

}


class ThreadedBinaryTree {

    // Function to insert a node into the threaded binary tree

    static Node insert(Node root, int value) {

        Node parent = null;

        Node current = root;

        // Traverse the tree to find the correct position

        while (current != null) {

            parent = current;

            if (value < current.data) {

                if (!current.lThread)

                    current = current.left;

                else

                    break;

            } else {

                if (!current.rThread)

                    current = current.right;

                else

                    break;

            }

        }

        // Create the new node

        Node newNode = new Node(value);

        // Insert the new node in the correct position

        if (parent == null) {

            root = newNode;

        } else if (value < parent.data) {

            newNode.left = parent.left;

            newNode.right = parent;

            parent.lThread = false;

            parent.left = newNode;

        } else {

            newNode.left = parent;

            newNode.right = parent.right;

            parent.rThread = false;

            parent.right = newNode;

        }

        return root;

    }

    // Function to perform inorder traversal of a threaded binary tree

    static void inorder(Node root) {

        if (root == null) return;

        // Reach the leftmost node

        Node current = root;

        while (!current.lThread)

            current = current.left;

        // Traverse the tree using threads

        while (current != null) {

            System.out.print(current.data + " ");

            if (current.rThread) {

                current = current.right;

            } else {

                current = current.right;

                while (current != null && !current.lThread)

                    current = current.left;

            }

        }

    }

}

// The Main class where the program execution begins

public class Main {

    public static void main(String[] args) {

        Node root = null;

        root = ThreadedBinaryTree.insert(root, 20);

        root = ThreadedBinaryTree.insert(root, 10);

        root = ThreadedBinaryTree.insert(root, 30);

        root = ThreadedBinaryTree.insert(root, 5);

        root = ThreadedBinaryTree.insert(root, 16);

        System.out.print("Inorder Traversal: ");

        ThreadedBinaryTree.inorder(root);

    }

}

Output:

Threaded Binary Tree

This example demonstrates the insertion process for a right-threaded binary tree. The key is to ensure that when a new node is inserted, the tree's threads are correctly updated so that the in-order traversal remains accurate.

Deletion

Deletion in a Threaded Binary Tree is more complicated than insertion because it can disrupt the tree's threading. When a node is deleted, the pointers of its predecessor and successor must be updated to maintain the in-order traversal.

  • Identify the Node: Find the node to be deleted.
  • Adjust Threads: Update the pointers of the predecessor and successor to bypass the deleted node.
  • Rebalance if Necessary: Ensure that the threading structure remains intact after deletion.

Deleting a node with two children is particularly challenging, as it involves finding the in-order successor, replacing the node with its successor, and then adjusting the threads.

In-Order Traversal

In-order traversal is the primary operation where Threaded Binary Trees shine. Thanks to the threads, traversal can be done without using a stack or recursion, making it both time-efficient and space-efficient.

Threaded Binary Tree

Program:

class Node {

    int data;

    Node left, right;

    boolean lThread, rThread;

    Node(int data) {

        this.data = data;

        this.left = null;

        this.right = null;

        this.lThread = true;

        this.rThread = true;

    }

}

class ThreadedBinaryTree {

    // Function to insert a node into the threaded binary tree

static Node insert(Node root, int value) {

        Node parent = null;

        Node current = root;

        // Traverse the tree to find the correct position

        while (current != null) {

            parent = current;

            if (value < current.data) {

                if (!current.lThread)

                    current = current.left;

                else

                    break;

            } else {

                if (!current.rThread)

                    current = current.right;

                else

                    break;

            }

        }

        // Create the new node

        Node newNode = new Node(value);

        // Insert the new node in the correct position

        if (parent == null) {

            root = newNode;

        } else if (value < parent.data) {

            newNode.left = parent.left;

            newNode.right = parent;

            parent.lThread = false;

            parent.left = newNode;

        } else {

            newNode.left = parent;

            newNode.right = parent.right;

            parent.rThread = false;

            parent.right = newNode;

        }

        return root;

    }

    // Function to perform inorder traversal of a threaded binary tree

    static void inOrder(Node root) {

        Node current = root;

        // Move to the leftmost node

        while (current != null && !current.lThread) {

            current = current.left;

        }

        // Traverse the tree using threads

        while (current != null) {

            System.out.print(current.data + " ");

            if (current.rThread) {

                current = current.right;

            } else {

                current = current.right;

                while (current != null && !current.lThread) {

                    current = current.left;

                }

            }

        }

    }

}

public class Main {

    public static void main(String[] args) {

        Node root = null;


        root = ThreadedBinaryTree.insert(root, 20);

        root = ThreadedBinaryTree.insert(root, 10);

        root = ThreadedBinaryTree.insert(root, 30);

        root = ThreadedBinaryTree.insert(root, 5);

        root = ThreadedBinaryTree.insert(root, 16);

        System.out.print("Inorder Traversal: ");

        ThreadedBinaryTree.inOrder(root);

    }

}

Output:

Threaded Binary Tree

In this traversal there is no use of additional data structures to go from node to node as the threads themselves help to traverse.

Advantages of Threaded Binary Trees

Threaded Binary Trees offer several advantages over traditional binary trees:

  1. Efficient In-Order Traversal: Using threads, TBTs enable in-order traversal at an exceptionally high speed without any use of recursion or stacks, an element that will be of immense benefit in systems with restricted memory storage.
  2. Space Optimization: Since TBTs employ null pointers for threading, the above set of applications can take better advantage of all the available memory.
  3. Simplicity in Traversal Code: Traversal algorithms require minimum complexities and the least chances of error because they don't involve any other data structure as the stack does.
  4. Performance Boost: The advantage of the TBTs is that they show good results in applications where in-order traversal occurs frequently.

Such benefits make TBTs most suitable for use in contexts where memory space and search time dictate usage.

Disadvantages of Threaded Binary Trees

Despite their benefits, Threaded Binary Trees have some drawbacks:

  1. Complex Insertion and Deletion: Due to the requirement that the threading structure must be preserved, the insertion as well as deletion operations are more involved as compared to a typical binary tree.
  2. Overhead of Additional Flags: lThread and rThread increase the memory usage of the system and add minor overhead to each node in the system.
  3. Limited Applicability: In this sense, TBTs are the most beneficial in situations where in-order traversal is employed intensively. For other types of traversal (pre-order, post-order as an example), they do not offer notable benefits.

These disadvantages imply that while the use of TBTs is advantageous, it should be cured in a context where the advantages overshadow the disadvantages.

Applications of Threaded Binary Trees

Threaded Binary Trees find use in several applications, particularly those requiring frequent and efficient in-order traversal:

  1. Database Indexing: In database systems, TBTs can be applied to store indices if in-order access is expected.
  2. Expression Trees in Compilers: For the case of compiler design/implementation, we have the TBT in which in-order would represent the expression tree.
  3. Tree-Based Data Structures: There are certain special-purpose data structures where in-order traversal is most efficient; TBTs may be used constructively here.

In such cases, such as fast traversal and memory minimization, TBTs may be quite effective.

Variations and Extensions

In the recent past, modifications of the simple structure represented by TBT have been put in place to cater to certain requirements. Some notable extensions include:

  1. Height-Balanced Threaded Binary Trees: These integrate characteristics of AVL trees and threading such that when threading balances the tree, its traversal is efficiently performed.
  2. Multi-Threaded Binary Trees: There are two of these, which extend the concept of threading to multi-way threading, thus allowing other forms of traversal relationships.

All of these variations come at different costs in terms of the amount of code written and the size of the resultant files.

Detailed Example: Implementing a Threaded Binary Tree in Java

Let’s revisit the Java implementation of a Threaded Binary Tree with an emphasis on how threading is managed during insertion:

class Node {

    int data;

    Node left, right;

    boolean lThread, rThread;

    Node(int data) {

        this.data = data;

        this.left = null;

        this.right = null;

        this.lThread = true;

        this.rThread = true;

    }

}

class ThreadedBinaryTree {

    private Node root;

    ThreadedBinaryTree() {

        root = null;

    }

    void insert(int value) {

        if (root == null) {

            root = new Node(value);

            return;

        }

        Node parent = null;

        Node current = root;

        while (current != null) {

            parent = current;

            if (value < current.data) {

                if (!current.lThread) {

                    current = current.left;

                } else {

                    break;

                }

            } else {

                if (!current.rThread) {

                    current = current.right;

                } else {

                    break;

                }

            }

        }

        Node newNode = new Node(value);

        if (value < parent.data) {

            newNode.left = parent.left;

            newNode.right = parent;

            parent.lThread = false;

            parent.left = newNode;

        } else {

            newNode.left = parent;

            newNode.right = parent.right;

            parent.rThread = false;

            parent.right = newNode;

        }

    }

    void inOrder() {

        Node current = root;

        while (current != null && !current.lThread) {

            current = current.left;

        }

        while (current != null) {

            System.out.print(current.data + " ");

            if (current.rThread) {

                current = current.right;

            } else {

                current = current.right;

                while (current != null && !current.lThread) {

                    current = current.left;

                }

            }

        }

    }

    public static void main(String[] args) {

        ThreadedBinaryTree tbt = new ThreadedBinaryTree();

        tbt.insert(20);

        tbt.insert(10);

        tbt.insert(30);

        tbt.insert(5);

        tbt.insert(15);

        tbt.insert(25);

        tbt.insert(35);


        System.out.print("In-order Traversal of Threaded Binary Tree: ");

        tbt.inOrder();

    }

}

Output:

Threaded Binary Tree

This work contains a simple Java implementation that nonetheless captures the essence of threaded binary trees. The traversal method is based on threading and shows how easy and effective TBTs can be.

Complexity Analysis

Understanding the time and space complexities of operations in a Threaded Binary Tree is crucial:

  • Time Complexity:
    • Insertion: O(n) in the worst case because a tree can become skewed, and then this algorithm takes a time proportional to this.
    • Traversal: O(n) for given in-order traversal.
    • Deletion: That is, O(n) because of the adjustment of threads.
  • Space Complexity: O(n) since no other data structure, such as stacks, is used during this traversal because of threading.

These complexities explain why TBTs are efficient, especially during traversals, as will be illustrated later.

Comparison Between Threaded Binary Trees and Other Binary Trees

1. Threaded vs. Standard Binary Trees:

Threaded Binary Trees (TBTs) aid in the improvement of the traversal rate since the threads are denoted by null pointers for the in-order outline. It does not require using of stacks or recursion, while in contrast to the binary trees the null pointers are unused. In this context, memory usage is made better by reusing these pointers by TBTs hence enabling a better performance in the traversal process.

2. Threaded Binary Trees vs. AVL Trees:

AVL trees are balanced binary trees that try to balance data by maintaining its height balance so as to support operations such as search, insertion, or deletion. They are useful for datasets that may need to be regularly updated in a short while. While walking to get from one place to another, Other activities such as TBTs place more emphasis on how fast one can traverse the area of interest. Because of this, TBTs are well suited for database applications where the dataset retrieval rate is more important than de-allocation.

3. Threaded Binary Trees vs. Red-Black Trees:

Red-Black Trees are further implementations of Binary Search Trees, and as such, they offer O(log n) time complexity for the operations. TBT, while being simpler and much more optimized for in-order traversal, is ideal for situations that require fast traversal but are less beneficial for fast-changing databases.

Conclusion

Threaded Binary Trees are possibly among the most intriguing data structures, offering more perks than drawbacks when it comes to space and time complexity, especially in the case of in-order traversal. They are an elegant solution to the problems arising from the use of the traditional binary trees while providing better usage of memory in specific instances. Despite making operations such as insertion and deletion slightly more complex, their applications make them a rather useful addition to the programmer's repertoire.

 The knowledge of work with Threaded Binary Trees allows considering more developed tree structures and searching for another suitable data structure. In cases where the amount of data is significant or the scope of operation is limited, there's apparently a reason to consider TBTs as a viable solution.


Related Topics

Introduction to 1D-Arrays

One Dimensional Array Technical Definitions The simplest version of an Array is a One-Dimensional Array, in which the items are stored linearly and may be accessed individually by supplying the index value...

6 minutes read.

Optimal binary search tree using dynamic programming

Implementation // We are creating a presentation where we will present a recursive method of the optimal binary search tree problem.  #include <bits/stdc++.h> using namespace std; //creating a utility function that will help us...

9 minutes read.

Time Complexity of Selection Sort in Data Structure

What is Time Complexity? The term “Time complexity” can be defined as the number of times executions made of a particular sequence of instructions and not the total amount of time...

3 minutes read.

Difference Between Linear and Non Linear Data Structures

Data Structure A data structure is a data object together with the relationships between the instances and the individual elements that compose an instance. These relationships are defined by the operations...

5 minutes read.

Serialize and Deserialize a Binary Tree

Implementation // Writing a C++ program to check the serialization and deserialization of binary tree.   #include <iosstream> /* A binary tree node contains a key and a pointer to the left and right...

4 minutes read.

Bubble Sort vs Quick Sort

In this article, we are going to compare two sorting techniques, Bubble sort and Quick Sort. In starting, we will first discuss the idea of sorting an array using bubble...

7 minutes read.

Circular Linked List

Circular Linked List A circular linked list where all nodes are connected to their next node and last node is connected to the starting node or we can say all nodes...

5 minutes read.

Properties of Binary Tree

Trees are maybe of the most significant datum structures. They are used to store and figure out data. A binarytree is a tree data structure made from nodes, all of which has...

3 minutes read.

Stack Data Structure

The stack is a non-primitive and linear data structure. It works on the principle of LIFO (Last In First Out). That is, the element that is added to the end...

3 minutes read.

Minimum Spanning Tree

Before getting to know about the minimum spanning tree, we should first discuss about what is a spanning tree. A spanning tree is basically a sub or minimized graph that...

7 minutes read.

Difference between B-tree and Binary Tree

What is B-TREE? The nodes of B-tree are sorted during in-order traversal, and it is called self-balancing tree. A node in a B-tree can have more than two offspring, in contrast...

3 minutes read.

B Tree vs B + Tree: Data Structure

Difference Between B Tree and B+ Tree What is B Tree? B-Tree is a self-balancing or special type of m-way tree. B-Trees are used mainly in disc access. If we want...

3 minutes read.

Hashing

Hashing: Hashing is a process in which a large amount of data is mapped to a small table with the help of hashing function. It is a searching technique. Hash table We...

4 minutes read.

CSS Text-indent

Text-indent The Text-indent property of CSS is used to set any first line’s indentation inside a text’s block. It describes the horizontal space amount that puts establish before the text line. It...

3 minutes read.

Given a Binary Tree Swap Nodes at K Height

Implementation // Writing a C++ program that will help us exchange the nodes.  #include<bits/stdc++.h> using namespace std; // Creating a binary tree node. struct __nod { int record; struct __nod *Lft, *Rt; }; // creating a function that will help...

8 minutes read.

Heap Sort vs Merge Sort

In this article, we are going to discuss the Heap Sort, Merge sort and the difference between them. What is Heap Sort? Heap – A heap is an abstract data type categorised...

7 minutes read.

Binary Tree vs Binary Search Tree: Data Structure

Difference Between Binary Tree and Binary Search Tree What is Binary Tree? A tree which each node can have utmost two children called binary tree. These children are referred as the ‘left...

3 minutes read.

Advantages and Disadvantages of Linked List

Advantages of Linked List The linked list is a dynamic data structure.You can also decrease and increase the linked list at run-time. That is, you can allocate and deallocate memory at...

3 minutes read.

Finding Rank in a Binary Search Tree

Implementation // writing a C++ program to find out the rank and element in the program.  #include <bits/stdc++.h> using namespace std; struct __nod { int record; __nod *Lft, *Rt; int LftSize; }; __nod* new__nod(int record) { __nod *temp = new __nod; temp->record...

6 minutes read.

Introduction to 2D-Arrays

Two Dimensional Array Technical Definitions An array of arrays is a common definition for a two-dimensional array. A matrix is another name for a two-dimensional array. A matrix looks like a table...

3 minutes read.