Print a Binary Search Tree in Python

Recently, I was working on a data analytics project for a tech startup in San Francisco, where I needed to visualize hierarchical employee data. The challenge was displaying a binary search tree structure in a readable format.

I’ve discovered there’s no single “best” way to print a BST. Different scenarios require different approaches.

In this comprehensive guide, I’ll share practical methods I use to print binary search trees in Python. Each method serves a specific purpose, from simple debugging to creating professional visualizations.

Binary Search Trees in Python

Before jumping into printing methods, let me quickly explain what we’re working with.

A Binary Search Tree (BST) is a hierarchical data structure where each node has at most two children. The left child contains smaller values than the parent, and the right child contains larger values.

Here’s the basic BST node structure I use in all my projects:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class BST:
    def __init__(self):
        self.root = None

    def insert(self, val):
        if not self.root:
            self.root = TreeNode(val)
        else:
            self._insert_recursive(self.root, val)

    def _insert_recursive(self, node, val):
        if val < node.val:
            if node.left is None:
                node.left = TreeNode(val)
            else:
                self._insert_recursive(node.left, val)
        else:
            if node.right is None:
                node.right = TreeNode(val)
            else:
                self._insert_recursive(node.right, val)

# Create sample BST with US city populations (in millions)
bst = BST()
cities_population = [8.4, 3.9, 2.7, 2.3, 1.6, 1.5, 1.4]  # NYC, LA, Chicago, Houston, Phoenix, Philadelphia, San Antonio
for pop in cities_population:
    bst.insert(pop)

Method 1: In-Order Traversal Printing

In-order traversal is my go-to method when I need sorted output. This approach visits nodes in ascending order: left subtree → root → right subtree.

I use this method frequently when debugging BST implementations or when I need to verify that data is properly sorted.

def print_inorder(root):
    """Print BST nodes in ascending order"""
    if root:
        print_inorder(root.left)
        print(f"{root.val:.1f}", end=" ")
        print_inorder(root.right)

def print_inorder_with_labels(root, cities_dict=None):
    """Print BST with city labels for better understanding"""
    if not cities_dict:
        cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                      1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}

    def inorder_helper(node):
        if node:
            inorder_helper(node.left)
            city_name = cities_dict.get(node.val, "Unknown")
            print(f"{city_name}: {node.val:.1f}M", end=" → ")
            inorder_helper(node.right)

    inorder_helper(root)
    print("End")

# Usage
print("In-Order Traversal (Sorted):")
print_inorder(bst.root)
print("\n")

print("In-Order with City Labels:")
print_inorder_with_labels(bst.root)

I executed the above example code and added the screenshot below.

print binary tree python

In-order traversal is a simple yet powerful way to retrieve BST data in sorted order, and adding labels makes the output more meaningful and readable.

Method 2: Pre-Order Traversal Printing

Pre-order traversal prints the root first, then left and right subtrees. I find this method useful when I need to understand the tree’s construction order or when serializing the tree.

This approach follows: root → left subtree → right subtree.

def print_preorder(root):
    """Print BST nodes in pre-order (root first)"""
    if root:
        print(f"{root.val:.1f}", end=" ")
        print_preorder(root.left)
        print_preorder(root.right)

def print_preorder_with_depth(root, depth=0, label="Root"):
    """Print pre-order traversal with depth indicators"""
    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}

    if root:
        indent = "  " * depth
        city_name = cities_dict.get(root.val, "Unknown")
        print(f"{indent}{label}: {city_name} ({root.val:.1f}M)")

        if root.left:
            print_preorder_with_depth(root.left, depth + 1, "L")
        if root.right:
            print_preorder_with_depth(root.right, depth + 1, "R")

# Usage
print("Pre-Order Traversal:")
print_preorder(bst.root)
print("\n")

print("Pre-Order with Depth Indicators:")
print_preorder_with_depth(bst.root)

I executed the above example code and added the screenshot below.

python print tree

Pre-order traversal reveals the tree’s construction sequence, making it ideal for understanding structure and preparing the tree for serialization.

Method 3: Level-Order (Breadth-First) Printing

Level-order traversal prints nodes level by level, from left to right. I use this method when I need to understand the tree’s structure or when creating visual representations.

This approach requires a queue data structure to track nodes at each level.

from collections import deque

def print_level_order(root):
    """Print BST nodes level by level"""
    if not root:
        return

    queue = deque([root])

    while queue:
        node = queue.popleft()
        print(f"{node.val:.1f}", end=" ")

        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

def print_level_order_by_levels(root):
    """Print each level on a separate line"""
    if not root:
        return

    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}

    queue = deque([root])
    level = 0

    while queue:
        level_size = len(queue)
        print(f"Level {level}: ", end="")

        for _ in range(level_size):
            node = queue.popleft()
            city_name = cities_dict.get(node.val, "Unknown")
            print(f"{city_name}({node.val:.1f})", end=" ")

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        print()  # New line after each level
        level += 1

# Usage
print("Level-Order Traversal:")
print_level_order(bst.root)
print("\n")

print("Level-Order by Levels:")
print_level_order_by_levels(bst.root)

I executed the above example code and added the screenshot below.

print tree python

Level-order traversal captures the tree’s structure layer by layer, making it perfect for visual representations and analyzing breadth relationships.

Method 4: Tree Structure Visualization

When working on complex projects, I often need a visual representation of the tree structure. This method creates an ASCII art representation that clearly shows parent-child relationships.

def print_tree_structure(root, space=0, height=10):
    """Print tree structure with ASCII art"""
    if root is None:
        return
    
    space += height
    print_tree_structure(root.right, space, height)
    
    print()
    for i in range(height, space):
        print(end=" ")
    print(f"{root.val:.1f}")
    
    print_tree_structure(root.left, space, height)

def print_tree_with_branches(root, prefix="", is_last=True):
    """Print tree with branch-like structure"""
    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}
    
    if root is not None:
        city_name = cities_dict.get(root.val, "Unknown")
        print(prefix + ("└── " if is_last else "├── ") + f"{city_name} ({root.val:.1f}M)")
        
        children = []
        if root.left is not None:
            children.append(("left", root.left))
        if root.right is not None:
            children.append(("right", root.right))
        
        for i, (child_type, child_node) in enumerate(children):
            is_last_child = i == len(children) - 1
            extension = "    " if is_last else "│   "
            print_tree_with_branches(child_node, prefix + extension, is_last_child)

Create a more balanced tree for better visualization

balanced_bst = BST()
balanced_values = [2.7, 1.5, 8.4, 1.4, 2.3, 3.9, 1.6]  # Chicago as root for balance
for val in balanced_values:
    balanced_bst.insert(val)

print("ASCII Tree Structure:")
print_tree_structure(balanced_bst.root)
print("Tree with Branches:")
print_tree_with_branches(balanced_bst.root)

def print_detailed_tree_structure(root):
    """Print comprehensive tree structure with node relationships"""
    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}
    
    def get_tree_info(node, level=0, position="Root"):
        if not node:
            return []
        
        city_name = cities_dict.get(node.val, "Unknown")
        info = [(level, position, f"{city_name} ({node.val:.1f}M)")]
        
        if node.left:
            info.extend(get_tree_info(node.left, level + 1, "Left"))
        if node.right:
            info.extend(get_tree_info(node.right, level + 1, "Right"))
        
        return info
    
    tree_info = get_tree_info(root)
    
    print("Detailed Tree Structure:")
    for level, position, node_info in tree_info:
        indent = "  " * level
        print(f"{indent}Level {level} ({position}): {node_info}")

# Usage
print("\nDetailed Tree Structure:")
print_detailed_tree_structure(balanced_bst.root)

Tree structure visualization provides an intuitive, readable map of parent-child relationships, making complex hierarchies easier to analyze and debug.

Method 5: Pretty Print with Indentation

This is my favorite method for debugging and presentations. It creates a clean, indented representation that’s easy to read and understand the hierarchy at a glance.

def pretty_print_bst(root, indent="", last=True, is_root=True):
    """Create a beautiful indented tree representation"""
    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}
    
    if root is not None:
        city_name = cities_dict.get(root.val, "Unknown")
        
        if is_root:
            print("Binary Search Tree Structure:")
            print("=" * 40)
            print(f" {city_name} ({root.val:.1f}M) [ROOT]")
            is_root = False
        else:
            print(indent, end="")
            if last:
                print("└─ ", end="")
                indent += "   "
            else:
                print("├─ ", end="")
                indent += "│  "
            
            print(f" {city_name} ({root.val:.1f}M)")
        
        # Count children to determine which is last
        children = []
        if root.left:
            children.append(('left', root.left))
        if root.right:
            children.append(('right', root.right))
        
        for i, (side, child) in enumerate(children):
            is_last_child = (i == len(children) - 1)
            pretty_print_bst(child, indent, is_last_child, False)

def print_bst_with_statistics(root):
    """Print BST with additional statistics"""
    def calculate_stats(node):
        if not node:
            return 0, 0, 0  # height, nodes, sum
        
        left_height, left_nodes, left_sum = calculate_stats(node.left)
        right_height, right_nodes, right_sum = calculate_stats(node.right)
        
        height = max(left_height, right_height) + 1
        nodes = left_nodes + right_nodes + 1
        total_sum = left_sum + right_sum + node.val
        
        return height, nodes, total_sum
    
    height, total_nodes, population_sum = calculate_stats(root)
    
    print("\n" + "="*50)
    print("BST STATISTICS")
    print("="*50)
    print(f" Total Cities: {total_nodes}")
    print(f" Tree Height: {height}")
    print(f" Total Population: {population_sum:.1f}M people")
    print(f" Average Population: {population_sum/total_nodes:.2f}M people")
    print("="*50)

def interactive_bst_printer(root):
    """Interactive menu for different printing options"""
    cities_dict = {8.4: "NYC", 3.9: "LA", 2.7: "Chicago", 2.3: "Houston", 
                  1.6: "Phoenix", 1.5: "Philadelphia", 1.4: "San Antonio"}
    
    while True:
        print("\n BST Printing Options:")
        print("1. Pretty Print Structure")
        print("2. In-Order Traversal")
        print("3. Level-Order Traversal")
        print("4. Tree Statistics")
        print("5. Exit")
        
        choice = input("\nSelect an option (1-5): ").strip()
        
        if choice == '1':
            pretty_print_bst(root)
        elif choice == '2':
            print("\nIn-Order Traversal (Sorted Cities by Population):")
            print_inorder_with_labels(root, cities_dict)
        elif choice == '3':
            print("\nLevel-Order Traversal:")
            print_level_order_by_levels(root)
        elif choice == '4':
            print_bst_with_statistics(root)
        elif choice == '5':
            print("Goodbye! ")
            break
        else:
            print(" Invalid option. Please try again.")

pretty_print_bst(balanced_bst.root)
print_bst_with_statistics(balanced_bst.root)

Pretty-printing a BST with indentation transforms raw tree data into a visually appealing, structured format, making it easier to debug, present, and understand complex hierarchies while also providing valuable statistical insights.

Conclusion

Printing binary search trees effectively is more art than science. The method you choose depends entirely on your specific use case.

I still use all five methods regularly in my projects. Each serves a unique purpose and has saved me countless hours of debugging time.

The next time you’re working with BSTs, try implementing these methods in your projects. You’ll find they make tree manipulation much more intuitive and enjoyable.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.