Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 11

Recursive Binary Tree Traversal

Recommended resources

Hello! Welcome to your next lesson.

In our last session, we dove into the practical implementation of hash tables, exploring how collision resolution strategies like separate chaining and open addressing work in code. We saw how a seemingly simple data structure has nuanced trade-offs regarding memory, complexity, and performance.

Today, we shift our focus from the flat, associative world of hash tables to the hierarchical structure of trees. Trees are a cornerstone of computer science, used for everything from organizing file systems and representing XML/HTML documents to powering efficient search and database indexing.

Your goal for this lesson is to implement a binary tree with recursive traversal algorithms (in-order, pre-order, and post-order). This will build your proficiency with recursion—a critical skill for any software engineer—and lay the groundwork for understanding more advanced tree-based structures that are essential for developing an "architectural mindset."

1. What is a Binary Tree?

A binary tree is a hierarchical data structure where each node has at most two children, typically referred to as the left child and the right child. The tree starts from a single node called the root.

A node in a binary tree is simple to define. It contains:

  1. Some data (e.g., an integer, a string, an object).
  2. A reference (or pointer) to its left child.
  3. A reference (or pointer) to its right child.

If a node does not have a left or right child, its corresponding reference is null.

Here is a typical implementation of a TreeNode class in a language like Java or C# (the concept is identical in Python, JavaScript, etc.):

class TreeNode {
    int data;
    TreeNode left;
    TreeNode right;

    public TreeNode(int data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }
}

2. Traversing a Binary Tree

"Traversing" a tree means visiting every node in a specific order. Unlike linear data structures like arrays or linked lists, which have a clear beginning and end, a tree can be traversed in multiple ways. The three most common methods are pre-order, in-order, and post-order. These are all forms of Depth-First Search (DFS), as they explore as far as possible down each branch before backtracking.

The key to understanding these three traversals is to recognize that for any given node (which can be seen as the root of its own subtree), we have three actions to perform:

  • Visit the Root node itself (e.g., print its data).
  • Traverse the entire Left subtree.
  • Traverse the entire Right subtree.

The difference between pre-order, in-order, and post-order is simply the sequence in which these three actions are executed.

Binary Tree Traversal Examples
Caption: A sample binary tree and the resulting node sequences for In-order, Pre-order, and Post-order traversals. Notice how the order of visiting the root relative to its children defines the traversal type.

To get a clear conceptual overview of these traversals, let's watch a short video.

Binary tree traversal: Preorder, Inorder, Postorder

The video 'Binary tree traversal' from mycodeschool provides an excellent and concise introduction to the three main depth-first traversal methods. It clearly explains how the order of visiting the root, left, and right subtrees defines each traversal.

Watch the first part of the video (00:00 - 02:13). Focus on how the simple rule of visiting the root, left subtree, and right subtree in different permutations leads to the three distinct traversal types.

As the video explains, the traversal names themselves give you the rule:

  • Pre-order: Visit Root, then traverse Left, then traverse Right.
  • In-order: Traverse Left, then visit Root, then traverse Right.
  • Post-order: Traverse Left, then traverse Right, then visit Root.

3. Recursive Implementation of Traversals

The nature of trees lends itself perfectly to recursive solutions. Each traversal algorithm can be expressed as a simple recursive function.

Pre-order Traversal (Root -> Left -> Right)

This is often used for creating a copy of a tree or for serializing it because you process the parent node before its children.

The video you just started provides a fantastic step-by-step visualization of how the recursive calls work for a pre-order traversal. It shows how the function call stack grows and shrinks as the recursion deepens and unwinds.

Binary tree traversal: Preorder, Inorder, Postorder

Let's continue with the 'Binary tree traversal' video to see how pre-order traversal is implemented and how the recursion unfolds.

Watch the detailed explanation of pre-order traversal from 02:13 to 11:58. Pay close attention to the visualization of the function call stack. Understanding how the stack manages the state of the paused function calls is key to mastering recursion.

The logic is remarkably compact. Here is the core of the algorithm presented in a language-agnostic way:

function preorder(node) {
    if (node is null) return;

    visit(node.data);          // Root
    preorder(node.left);       // Left
    preorder(node.right);      // Right
}

For a concrete code example in Java, you can refer to the following resource.

Trees: Preorder, Inorder, Postorder Traversals

The article 'Mastering Trees: Preorder, Inorder, Postorder Traversals' provides clear and concise Java implementations for all three traversal methods.

Read the section on 'Preorder'. Review the Java code and the accompanying explanation. Note how it directly maps to the 'Root -> Left -> Right' pattern.

In-order and Post-order Traversal

The beauty of these algorithms is that once you understand one, you understand them all. The implementations for in-order and post-order are almost identical to pre-order—only the sequence of the three core operations changes.

  • In-order (Left -> Root -> Right): This is particularly useful for Binary Search Trees (which we'll cover next), as it visits the nodes in sorted order.
  • Post-order (Left -> Right -> Root): This is useful when you need to process child nodes before the parent, such as when deleting nodes in a tree (to avoid creating orphaned children) or evaluating an expression tree.

The mycodeschool video quickly covers the code for these two traversals.

Binary tree traversal: Preorder, Inorder, Postorder

Now, let's see how simple it is to adapt the pre-order code to perform in-order and post-order traversals.

Watch the segment from 11:58 to 13:21. Notice how the same three lines of code are just reordered to produce the different traversal outputs.

And for the corresponding Java implementations, you can continue with the article you just reviewed.

Trees: Preorder, Inorder, Postorder Traversals

Let's finish the article by looking at the code for in-order and post-order traversals.

Read the sections 'Inorder' and 'Postorder'. Compare the code to the pre-order version and confirm that the only change is the order of the recursive calls and the visit statement.

Test your understanding!

Using the tree from the image Binary Tree Traversal Orders shown earlier, what would be the output if you wanted to delete all the nodes in the tree? Which traversal order should you use and why?

Show answer

You should use post-order traversal (D, E, B, H, I, F, J, K, L, G, C, A).

The reason is that in post-order traversal, a parent node is visited only after its left and right children have been visited. When deleting nodes, you must delete the children before you delete the parent. If you deleted the parent first (as in pre-order), you would lose the references to its children, making them unreachable and impossible to delete (a memory leak).


4. Complexity Analysis

As you requested, let's analyze the performance of these algorithms. The complexity is the same for all three recursive traversal methods.

Binary tree traversal: Preorder, Inorder, Postorder

Finally, let's analyze the time and space complexity of these algorithms. The 'mycodeschool' video concludes with a perfect summary.

Watch the final part of the video from 13:21 to 14:22. It explains why the time complexity is O(n) and the space complexity is O(h).

Here's a summary of the complexity analysis:

  • Time Complexity:

    • Each traversal algorithm visits every node in the tree exactly once.
    • Therefore, the time taken is directly proportional to the number of nodes, .
  • Space Complexity:

    • Although we don't explicitly create a data structure to store nodes, the recursive function calls consume memory on the call stack.
    • The maximum number of functions on the call stack at any given time is equal to the height () of the tree (the longest path from the root to a leaf node).
    • Worst-case: For a skewed tree (like a linked list), the height is equal to , so space complexity becomes .
    • Best/Average-case: For a balanced tree, the height is approximately , so space complexity is .

Conclusion

In this lesson, you have learned the fundamentals of binary trees and how to traverse them using the three main depth-first recursive algorithms.

Key Takeaways:

  • A binary tree is a hierarchical data structure where each node has at most a left and a right child.
  • The three main depth-first traversal methods are pre-order (Root-Left-Right), in-order (Left-Root-Right), and post-order (Left-Right-Root).
  • The recursive implementations are elegant and simple, differing only in the order of the three main operations: visiting the root, and recursively traversing the left and right subtrees.
  • All three traversals have a time complexity of and a space complexity of due to the recursion stack depth.
  • The choice of traversal has practical consequences, making each one suitable for different tasks like copying, sorting, or deleting.

In our next lesson, we will build upon this foundation by adding a specific ordering property to our tree to create a Binary Search Tree (BST). You will see how this simple constraint, combined with the power of in-order traversal, allows for highly efficient searching, insertion, and deletion of data.