Visualizing Binary Search Trees: Deep Dive

Avatar

By squashlabs, Last Updated: September 27, 2023

Visualizing Binary Search Trees: Deep Dive

A Visual Representation

Here is a representation of a Binary Search Tree (BST) with the following numbers: 15, 10, 20, 5, 12, 17, 25.

      15
     /  \
   10   20
   / \   / \
  5  12 17 25

15 is the root of the tree.
10 and 20 are the children of 15.
5 and 12 are the children of 10.
17 and 25 are the children of 20.

And a more complex example with the numbers: 50, 30, 70, 10, 40, 60, 90, 5, 15, 35, 45, 55, 65, 85, 95.

          50
       /      \
     30        70
    /  \      /  \
  10   40   60    90
  / \  / \  / \   / \
 5  15 35 45 55 65 85 95

50 is the root.
30 and 70 are the children of 50.
10 and 40 are the children of 30.
60 and 90 are the children of 70.
5 and 15 are the children of 10.
35 and 45 are the children of 40.
55 and 65 are the children of 60.
85 and 95 are the children of 90.

Related Article: What is Test-Driven Development? (And How To Get It Right)

What is a Binary Search Tree?

A binary search tree (BST) is a popular data structure used in computer science and programming. It is a type of binary tree where each node has at most two children, referred to as the left child and the right child. The BST has a unique property that makes it efficient for searching and organizing data – the values in the left subtree of any given node are always less than the value of the node, and the values in the right subtree are always greater.

A binary search tree is commonly used for operations like searching for a value, inserting a new value, and deleting a value from a collection of data. The unique structure and ordering of the binary search tree make these operations efficient, with a time complexity of O(log n) on average, and O(n) in the worst case for unbalanced trees.

Let’s take a look at how we can visualize a binary search tree in programming.

How to Visualize a Binary Search Tree?

Visualizing a binary search tree can help us better understand its structure and how the values are organized. There are various ways to visualize a binary search tree, but one common approach is to use a graphical representation. This can be achieved by using different shapes or symbols to represent the nodes and connecting them with lines to represent the relationships between the nodes.

To visualize a binary search tree, we can use various programming languages and libraries that provide graphical capabilities. For example, we can use Python and its libraries like matplotlib or graphviz to generate a graphical representation of the binary search tree. Alternatively, we can use JavaScript and HTML to create an interactive visualization in a web browser.

Let’s take a look at an example of how to visualize a binary search tree using Python and the matplotlib library.

import matplotlib.pyplot as plt

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

def visualize_bst(root, ax=None, x=0, y=0, dx=1, dy=1):
    if ax is None:
        fig, ax = plt.subplots()
    
    if root is None:
        return
    
    ax.text(x, y, str(root.value), ha='center', va='center', 
            bbox=dict(facecolor='white', edgecolor='black', boxstyle='circle'))
    
    if root.left is not None:
        ax.plot([x, x-dx], [y-dy, y-1], 'k-')
        visualize_bst(root.left, ax, x-dx, y-1, dx/2, dy)
        
    if root.right is not None:
        ax.plot([x, x+dx], [y-dy, y-1], 'k-')
        visualize_bst(root.right, ax, x+dx, y-1, dx/2, dy)

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Visualize the binary search tree
visualize_bst(root)

# Show the plot
plt.axis('off')
plt.show()

In this example, we define a Node class to represent the nodes in the binary search tree. We also define an insert function to insert values into the binary search tree. Finally, we define a visualize_bst function that uses matplotlib to plot the binary search tree. We recursively traverse the tree and plot each node with its value, and connect the nodes with lines to represent the relationships between them.

This is just one way to visualize a binary search tree. Depending on the programming language and libraries you are using, there may be other approaches and techniques available. The key is to represent the nodes and their relationships in a clear and intuitive way to aid understanding.

Understanding the Structure of a Binary Search Tree

The structure of a binary search tree plays a crucial role in its functionality and efficiency. As mentioned earlier, a binary search tree is a type of binary tree where each node has at most two children – the left child and the right child. The values in the left subtree of any given node are always less than the value of the node, and the values in the right subtree are always greater.

Let’s take a closer look at the structure of a binary search tree and how it affects its operations.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree.

When inserting a value into the binary search tree, we start from the root node and compare the value with the value of the current node. If the value is less than the current node’s value, we go to the left child and repeat the process. If the value is greater than or equal to the current node’s value, we go to the right child and repeat the process. We continue this process until we reach a null reference, indicating the appropriate position to insert the new value.

Understanding the structure of a binary search tree is fundamental to working with it effectively. It allows us to perform operations like searching, inserting, and deleting values efficiently. By following the ordering property of the binary search tree, we can navigate through the tree and find the desired values in an optimized manner.

Related Article: 16 Amazing Python Libraries You Can Use Now

Exploring Nodes

Each node represents a value and has references to its left child and right child. The nodes are the building blocks of the binary search tree and play a crucial role in its structure and functionality.

Let’s explore the nodes in a binary search tree and how we can work with them.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree.

To explore the nodes in a binary search tree, we can perform various operations like searching for a value, inserting a new value, and deleting a value.

Let’s take a look at an example of how to search for a value in a binary search tree.

def search(root, value):
    if root is None or root.value == value:
        return root
    if value < root.value:
        return search(root.left, value)
    return search(root.right, value)

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Search for a value in the binary search tree
value_to_search = 4
result = search(root, value_to_search)
if result is not None:
    print(f"Value {value_to_search} found in the binary search tree!")
else:
    print(f"Value {value_to_search} not found in the binary search tree!")

In this example, we define a search function that takes a value and a root node as input, and recursively searches for the value in the binary search tree. If the value is found, the function returns the node containing the value. Otherwise, it returns None.

The Role of Left Child in a Binary Search Tree

Each node can have at most two children – the left child and the right child. The left child of a node plays a crucial role in maintaining the ordering property of the binary search tree.

Let’s explore the role of the left child in a binary search tree and how it affects the structure and functionality of the tree.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree.

The left child of a node in a binary search tree contains values that are less than the value of the node itself. This ordering property allows for efficient searching, as we can navigate to the left child if we are searching for a smaller value. By following the left child of a node, we can progressively find smaller values in the binary search tree.

Let’s take a look at an example of how the left child affects the structure and functionality of a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Traverse the binary search tree in in-order
def in_order_traversal(node):
    if node is not None:
        in_order_traversal(node.left)
        print(node.value)
        in_order_traversal(node.right)

in_order_traversal(root)

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then traverse the binary search tree using in-order traversal, which visits the nodes in ascending order of their values. By following the left child of each node, we traverse the binary search tree from the smallest value to the largest value.

The left child of a node is an essential component in maintaining the structure and ordering of a binary search tree. It allows for efficient searching and traversal, making the binary search tree a useful data structure for organizing and manipulating data.

The Role of Right Child in a Binary Search Tree

Each node can have at most two children – the left child and the right child. The right child of a node plays a crucial role in maintaining the ordering property of the binary search tree.

Let’s explore the role of the right child in a binary search tree and how it affects the structure and functionality of the tree.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree.

The right child of a node in a binary search tree contains values that are greater than or equal to the value of the node itself. This ordering property allows for efficient searching and traversal, as we can navigate to the right child if we are searching for a larger value. By following the right child of a node, we can progressively find larger values in the binary search tree.

Let’s take a look at an example of how the right child affects the structure and functionality of a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Traverse the binary search tree in pre-order
def pre_order_traversal(node):
    if node is not None:
        print(node.value)
        pre_order_traversal(node.left)
        pre_order_traversal(node.right)

pre_order_traversal(root)

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then traverse the binary search tree using pre-order traversal, which visits the nodes in the order of root, left child, and right child. By following the right child of each node, we traverse the binary search tree from the largest value to the smallest value.

The right child of a node is an essential component in maintaining the structure and ordering of a binary search tree. It allows for efficient searching and traversal, making the binary search tree a useful data structure for organizing and manipulating data.

Related Article: Agile Shortfalls and What They Mean for Developers

Understanding Parent Nodes in a Binary Search Tree

In a binary search tree, each node has references to its left child, right child, and parent. The parent node plays a crucial role in maintaining the structure and relationships of the binary search tree.

Let’s explore the role of parent nodes in a binary search tree and how they affect the structure and functionality of the tree.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
        self.parent = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
            root.left.parent = root
        else:
            root.right = insert(root.right, value)
            root.right.parent = root
    return root

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, a reference to its right child, and a reference to its parent. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree.

The parent node of a node in a binary search tree allows us to navigate from a child node back to its parent. This relationship is crucial for various operations like deleting a node, traversing the tree upwards, and maintaining the integrity of the tree.

Let’s take a look at an example of how parent nodes affect the structure and functionality of a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Find the parent of a node
def find_parent(node, value):
    if node is None or node.value == value:
        return node.parent
    if value < node.value:
        return find_parent(node.left, value)
    return find_parent(node.right, value)

# Find the parent of a value in the binary search tree
value_to_find = 4
parent = find_parent(root, value_to_find)
if parent is not None:
    print(f"The parent of value {value_to_find} is {parent.value}.")
else:
    print(f"The value {value_to_find} is either the root node or not found in the binary search tree.")

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then define a find_parent function that takes a value and a root node as input and recursively searches for the parent node of the value in the binary search tree. If the value is found, the function returns the parent node. Otherwise, it returns None.

Exploring In-Order Traversal in a Binary Search Tree

In a binary search tree, in-order traversal is a popular method to visit all the nodes in the tree. In this traversal technique, the left subtree is visited first, followed by the root node, and then the right subtree.

Let’s explore in-order traversal in a binary search tree and how it can be implemented.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

def in_order_traversal(node):
    if node is not None:
        in_order_traversal(node.left)
        print(node.value)
        in_order_traversal(node.right)

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree. Additionally, we define an in_order_traversal function that performs in-order traversal on the binary search tree.

To traverse the binary search tree in in-order, we first call the in_order_traversal function with the root node as the argument. The function recursively traverses the left subtree, visits the root node, and then recursively traverses the right subtree. By following this order, we can visit all the nodes in the binary search tree in ascending order of their values.

Let’s take a look at an example of how to perform in-order traversal on a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Traverse the binary search tree in in-order
in_order_traversal(root)

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then perform in-order traversal on the binary search tree by calling the in_order_traversal function with the root node as the argument. The function visits the nodes in ascending order of their values and prints their values to the console.

In-order traversal is a useful technique to explore the nodes in a binary search tree in a specific order. It allows us to visit the nodes in ascending or descending order of their values, depending on the implementation. In addition to printing the values, we can perform other operations on the nodes during the traversal, such as searching for a specific value or updating the values in the nodes.

Understanding Pre-Order Traversal in a Binary Search Tree

In a binary search tree, pre-order traversal is a popular method to visit all the nodes in the tree. In this traversal technique, the root node is visited first, followed by the left subtree, and then the right subtree.

Let’s explore pre-order traversal in a binary search tree and how it can be implemented.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

def pre_order_traversal(node):
    if node is not None:
        print(node.value)
        pre_order_traversal(node.left)
        pre_order_traversal(node.right)

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree. Additionally, we define a pre_order_traversal function that performs pre-order traversal on the binary search tree.

To traverse the binary search tree in pre-order, we first call the pre_order_traversal function with the root node as the argument. The function visits the root node, recursively traverses the left subtree, and then recursively traverses the right subtree. By following this order, we can visit all the nodes in the binary search tree in the desired sequence.

Let’s take a look at an example of how to perform pre-order traversal on a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Traverse the binary search tree in pre-order
pre_order_traversal(root)

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then perform pre-order traversal on the binary search tree by calling the pre_order_traversal function with the root node as the argument. The function visits the nodes in the desired sequence and prints their values to the console.

Pre-order traversal is a useful technique to explore the nodes in a binary search tree in a specific order. It allows us to visit the nodes in the desired sequence, enabling us to perform various operations on the nodes during the traversal. In addition to printing the values, we can perform other operations like searching for a specific value, updating the values in the nodes, or constructing a new binary search tree based on the traversal sequence.

Related Article: 24 influential books programmers should read

Exploring Post-Order Traversals

Post-order traversal is a popular method to visit all the nodes in the tree. In this traversal technique, the left subtree is visited first, followed by the right subtree, and then the root node.

Let’s explore post-order traversal in a binary search tree and how it can be implemented.

class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(root, value):
    if root is None:
        return Node(value)
    else:
        if value < root.value:
            root.left = insert(root.left, value)
        else:
            root.right = insert(root.right, value)
    return root

def post_order_traversal(node):
    if node is not None:
        post_order_traversal(node.left)
        post_order_traversal(node.right)
        print(node.value)

In this example, we define a Node class that represents a node in the binary search tree. Each node has a value, a reference to its left child, and a reference to its right child. We also define an insert function that inserts a new value into the binary search tree while maintaining the structure and ordering of the tree. Additionally, we define a post_order_traversal function that performs post-order traversal on the binary search tree.

To traverse the binary search tree in post-order, we first call the post_order_traversal function with the root node as the argument. The function recursively traverses the left subtree, recursively traverses the right subtree, and then visits the root node. By following this order, we can visit all the nodes in the binary search tree in the desired sequence.

Let’s take a look at an example of how to perform post-order traversal on a binary search tree.

# Create a binary search tree
root = None
values = [5, 3, 7, 2, 4, 6, 8]
for value in values:
    root = insert(root, value)

# Traverse the binary search tree in post-order
post_order_traversal(root)

In this example, we create a binary search tree with the values [5, 3, 7, 2, 4, 6, 8]. We then perform post-order traversal on the binary search tree by calling the post_order_traversal function with the root node as the argument. The function visits the nodes in the desired sequence and prints their values to the console.

Post-order traversal is a useful technique to explore the nodes in a binary search tree in a specific order. It allows us to visit the nodes in the desired sequence, enabling us to perform various operations on the nodes during the traversal. In addition to printing the values, we can perform other operations like searching for a specific value, updating the values in the nodes, or calculating properties of the tree based on the traversal sequence.

External Sources

Visualgo: Binary Search Tree Visualization
GeeksforGeeks: Binary Search Tree
Wikipedia: Binary Search Tree

You May Also Like

16 Amazing Python Libraries You Can Use Now

In this article, we will introduce you to 16 amazing Python libraries that are widely used by top software teams. These libraries are powerful tools that can enhance... read more

Agile Shortfalls and What They Mean for Developers

What is the best software development methodology to use? This question is the topic of hot debate during the project implementation stage. However, what you choose... read more

24 influential books programmers should read

The fast-paced world of programming demands that people remain up-to-date. In fact, getting ahead of the curve makes a programmer stand out in his professional field.... read more

The issue with Monorepos

A monorepo is an arrangement where a single version control system (VCS) repository is used for all the code and projects in an organization. In this article, we will... read more

The most common wastes of software development (and how to reduce them)

Software development is a complex endeavor that requires much time to be spent by a highly-skilled, knowledgeable, and educated team of people. Often, there are time... read more

Intro to Security as Code

Organizations need to adapt their thinking to protect their assets and those of their clients. This article explores how organizations can change their approach to... read more