Skip to main content

Welcome to a Grasp lesson

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

Lesson 38

Level-Order Traversal Using a Queue

Recommended resources

In our previous lesson, we explored the stack, a data structure defined by its Last-In, First-Out (LIFO) behavior, and used it to solve the 'Valid Parentheses' problem. This gave you a solid tool for handling tasks involving reversal and nested structures.

Today, we'll turn our attention to the stack's counterpart: the queue. We will explore its First-In, First-Out (FIFO) nature and see how to implement it in Java. Then, we will apply this knowledge to tackle another common interview problem: performing a level-order traversal of a binary tree. This is a foundational algorithm for many tree-based problems, from searching in graphs to serializing and deserializing tree structures.

1. The Queue: First-In, First-Out (FIFO)

A queue is an abstract data structure that, like a stack, manages a collection of elements. However, it follows a different rule: First-In, First-Out (FIFO). The first element added to the queue will be the first one to be removed. The most common analogy is a line at a ticket counter: the first person to get in line is the first person to be served.

This behavior is governed by a few primary operations:

  • add (or enqueue): Adds an element to the back (or tail) of the queue.
  • remove (or dequeue): Removes and returns the element from the front (or head) of the queue.
  • peek: Looks at the element at the front of the queue without removing it.

As with stacks, we also have an isEmpty() method to check if the queue is empty.


2. Implementing a Queue in Java

In Java's Collections Framework, the Queue is an interface. For general-purpose use, you'll typically choose an implementation class. While the ArrayDeque we used for stacks can also function as a queue, the LinkedList class is another very common and intuitive choice because it implements the Queue interface and naturally supports efficient addition at the tail and removal from the head.

Let's look at a typical Queue instantiation:

// LinkedList is a common implementation of the Queue interface.
Queue<Integer> myQueue = new LinkedList<>();

// Add elements to the back of the queue
myQueue.add(10); // Queue: [10]
myQueue.add(20); // Queue: [10, 20]
myQueue.add(30); // Queue: [10, 20, 30]

// Remove the element from the front of the queue
int frontElement = myQueue.remove(); // returns 10. Queue: [20, 30]

// Peek at the front element without removing it
int nextElement = myQueue.peek(); // returns 20. Queue: [20, 30]

The FIFO principle ensures that elements are processed in the order they were received, which is a crucial property for the algorithm we're about to study.


3. Binary Trees and Level-Order Traversal

Before we dive into the main problem, let's quickly set up the structure of a binary tree. As a CSE student, you're likely familiar with this, but a quick refresher on the Java implementation is helpful. A binary tree is a hierarchical data structure where each node has at most two children, referred to as the left child and the right child.

Implementing a Binary Tree in Java

This Baeldung article provides the basic Java code for a binary tree. It's a great starting point for any tree-based problem.

Focus on the initial part of the article, specifically the sections defining the Node class and the BinaryTree class. Pay attention to how a Node contains a value and references to its left and right children. Read the code snippets under the Node class and the BinaryTree class.

Now, what is level-order traversal? It means visiting the nodes of a tree level by level, from top to bottom, and within each level, from left to right. This is also known as Breadth-First Search (BFS).

For the tree in the image below, a level-order traversal would visit the nodes in the following sequence: 1, 2, 3, 4, 5, 6, 7.

This "visit in order of arrival" pattern is a perfect use case for a queue. The FIFO property ensures that we process all nodes at level k before moving on to any nodes at level k+1.

Level order traversal of Binary Tree | Step by step demo with example | Study Algorithms

This video from Nikhil Lohia provides a clear visual introduction to the concept of level-order traversal and explains why a queue is the ideal data structure for the job.

Watch the introduction from the beginning to understand what "levels" are and see a demonstration of the traversal order. Then, watch the section on the main idea which explains how the FIFO property of a queue naturally fits the requirements of level-order traversal.


4. The Algorithm for Level-Order Traversal

The core idea is to use a queue to keep track of nodes to visit. A clever trick allows us to group the nodes by level for our output.

Here is the step-by-step algorithm:

  1. Create an empty list to store the final result (a list of lists, where each inner list represents a level).
  2. If the root of the tree is null, return the empty list.
  3. Initialize a queue and add the root node to it.
  4. Start a while loop that continues as long as the queue is not empty.
  5. Inside the loop (this is the key part):
    a. Get the current size of the queue. Let's call it levelSize. This number represents all the nodes at the current level.
    b. Create a new empty list to store the values of the nodes for the current level.
    c. Start an inner for loop that runs levelSize times.
    d. In this inner loop:
    i. Dequeue a node. Let's call it currentNode.
    ii. Add the value of currentNode to the list for the current level.
    iii. If currentNode has a left child, enqueue it.
    iv. If currentNode has a right child, enqueue it.
    e. After the inner loop finishes, you have processed all nodes for the current level. Add the list of node values for this level to your final result list.
  6. Once the while loop finishes (the queue is empty), return the final result list.

The image below provides a fantastic visualization of this process. It shows the state of the queue after each level is processed.

This diagram illustrates level-order traversal on a binary tree. At each step, a level is processed by removing all its nodes from the queue. As each node is removed, its children are added to the back of the queue, forming the next level to be processed.

For a more detailed explanation and code implementation, the following resources are excellent.

102. Binary Tree Level Order Traversal - Explanation

This NeetCode article provides a superb, detailed walkthrough of the algorithm and its Java implementation, which is formatted exactly as you'd see in an interview platform like LeetCode.

Read the Breadth First Search section, which covers both the intuition and the detailed algorithm. Study the Java code. Notice how it uses a for loop based on q.size() to process each level. Finally, review the Time & Space Complexity analysis.

L8. Level Order Traversal of Binary Tree | BFS | C++ | Java

For a dynamic walkthrough, this video from "take U forward" demonstrates the algorithm and explains the code line by line.

Watch the algorithm explanation, which uses an animation to show nodes moving in and out of the queue. Follow along with the Java code walkthrough to see it all come together. Pay attention to the brief but important complexity analysis at the end.


5. Java Implementation and Analysis

Here is a complete Java solution based on the resources. It defines the TreeNode class and the levelOrder method that implements the algorithm.

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

// Definition for a binary tree node.
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) { this.val = val; }
}

public class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        // This will store the final result.
        List<List<Integer>> result = new ArrayList<>();

        // Handle the edge case of an empty tree.
        if (root == null) {
            return result;
        }

        // The queue is used for the BFS traversal.
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            // Get the number of nodes at the current level.
            int levelSize = queue.size();
            // This list will store the nodes of the current level.
            List<Integer> currentLevel = new ArrayList<>();

            // Process all nodes at the current level.
            for (int i = 0; i < levelSize; i++) {
                TreeNode currentNode = queue.remove();
                currentLevel.add(currentNode.val);

                // Add children of the current node to the queue for the next level.
                if (currentNode.left != null) {
                    queue.add(currentNode.left);
                }
                if (currentNode.right != null) {
                    queue.add(currentNode.right);
                }
            }
            // Add the completed level to the final result.
            result.add(currentLevel);
        }

        return result;
    }
}

Complexity Analysis

  • Time Complexity: O(n), where n is the number of nodes in the tree. This is because every node is visited (enqueued and dequeued) exactly once.
  • Space Complexity: O(w), where w is the maximum width of the tree. In the worst case (a complete binary tree), the last level can contain up to n/2 nodes, so the space complexity is also considered O(n). This space is used by the queue to store the nodes of a level.

Conclusion

This lesson completes our initial tour of essential interview patterns in data structures. You've now seen how stacks (LIFO) and queues (FIFO) provide simple but powerful models for processing data in sequence.

Key Takeaways:

  • A queue is a data structure that operates on a First-In, First-Out (FIFO) basis.
  • In Java, the Queue interface is commonly implemented using a LinkedList.
  • Level-order traversal (or BFS) is a method for visiting tree nodes level by level, from left to right.
  • The FIFO nature of a queue makes it the perfect tool for level-order traversal, ensuring nodes are processed in the correct order.
  • The key to grouping nodes by level is to capture the queue's size before processing the nodes of that level.

In our next module, we will shift gears from data structures and algorithms to explore some powerful features of modern Java. We'll start with lambda expressions, which will enable you to write more concise and functional code, particularly when working with collections. This will be our first step into the "Modern Java" features that are widely used in industry today.