top

Search

Software Key Tutorial

.

UpGrad

Software Key Tutorial

Level Order Traversal

Introduction

Level Order Traversal, also known as Breadth-First Traversal, is a fundamental tree traversal algorithm used to explore nodes in a tree structure layer by layer. It is a popular technique to traverse trees and graphs, making it an essential tool for solving various programming problems. 

If you are someone who is new to data structure and wants to explore the many benefits of Level Order Traversal through a comprehensive guide, you are at the right place! This tutorial will not only delve into the definition, working, and concepts of Level Order Traversal but also look at an array of examples that will simplify this concept further for an enthusiastic learner like yourself.

Overview

Level Order Traversal is a tree traversal algorithm that begins to process the root node, and then moves on to the parent nodes at each level, before starting with the child nodes at the next level. This algorithm uses a queue data structure to ensure nodes are processed in the order of insertion. 

Level Order Traversal is a fundamental algorithm with various applications, such as finding the shortest path, searching, and processing hierarchical data. Its simplicity and efficiency make it a valuable tool for solving programming challenges involving tree-like structures.

How does Level Order Traversal work?

To understand how Level Order Traversal works we have to first mark the starting point of the algorithm, which is the root node. It then begins the process of enqueueing it into the queue structure. After this, the algorithm enters a loop where it constantly dequeues nodes from the front of the queue. 

It performs the required functions and processes the dequeued node, before moving to the back of the queue gradually to enqueue its child nodes (if any). This loop continues until the queue becomes empty, indicating that all nodes in the tree have been visited.

By traversing nodes level by level, Level Order Traversal ensures that nodes at the same level are processed before moving to the next level. This approach ensures that each node in the tree is visited systematically. It is particularly helpful in scenarios where data needs to be manipulated, searched, printed, or processed in a breadth-first sequence. Moreover, the algorithm is efficient in finding the shortest path in unweighted graphs and is often employed in graph algorithms like Dijkstra's algorithm.

Level Order Traversal (Naive Approach)

The naive approach to performing level order traversal involves iterating through the binary tree level by level and printing the nodes at each level. This is typically done using a loop for each level and another loop to traverse nodes at that level.

Here is the pseudocode for level order traversal of a binary tree using the naive approach:

levelOrderNaive(root):
    if root is null:
        return
    height = getHeight(root)
    for level = 1 to height:
        printNodesAtLevel(root, level)

printNodesAtLevel(node, level):
    if node is null:
        return
    if level == 1:
        print(node.value)
    else:
        printNodesAtLevel(node.left, level - 1)
        printNodesAtLevel(node.right, level - 1)

The naive approach involves calculating the height of the tree and then iterating through each level. In this approach, getHeight calculates the height of the tree, and printNodesAtLevel prints the nodes at a specific level using recursive calls. At each level, nodes are printed using recursive calls. This approach is less efficient because it involves a lot of redundant traversals as it repeatedly traverses the same nodes.

Here is the Java implementation of the naive approach:

Code:

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        value = val;
        left = null;
        right = null;
    }
}

public class BinaryTree {
    TreeNode root;

    // Other methods and constructors can go here

    public int getHeight(TreeNode node) {
        if (node == null) {
            return 0;
        }
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        return Math.max(leftHeight, rightHeight) + 1;
    }

    public void printNodesAtLevel(TreeNode node, int level) {
        if (node == null) {
            return;
        }
        if (level == 1) {
            System.out.print(node.value + " ");
        } else {
            printNodesAtLevel(node.left, level - 1);
            printNodesAtLevel(node.right, level - 1);
        }
    }

    public void levelOrderNaive(TreeNode root) {
        if (root == null) {
            return;
        }
        int height = getHeight(root);
        for (int level = 1; level <= height; level++) {
            printNodesAtLevel(root, level);
        }
    }

    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        // Construct the tree
        tree.root = new TreeNode(1);
        tree.root.left = new TreeNode(2);
        tree.root.right = new TreeNode(3);
        tree.root.left.left = new TreeNode(4);
        tree.root.left.right = new TreeNode(5);

        tree.levelOrderNaive(tree.root);
    }
}

This program defines a TreeNode class representing a node in the tree, with integer value, left, and right children. The BinaryTree class contains methods to calculate the height of the tree and print nodes at a specific level. In the levelOrderNaive method, it calculates the height of the tree and then iterates through levels, calling printNodesAtLevel to print nodes at each level using recursive calls. The main function constructs a sample binary tree, and levelOrderNaive is called on the root node to perform level order traversal.

Level Order Traversal Using Queue 

The more efficient approach to level order traversal uses a queue data structure to keep track of nodes to be visited. This eliminates the need for recursive calls and nested loops.

Here is the pseudocode for level order traversal of a binary tree using a queue:

levelOrderQueue(root):
    if root is null:
        return
    queue = new Queue()
    queue.enqueue(root)
    while queue is not empty:
        current = queue.dequeue()
        print(current.value)
        if current.left is not null:
            queue.enqueue(current.left)
        if current.right is not null:
            queue.enqueue(current.right)

The queue-based approach is much more efficient as it leverages a queue to process nodes in a level order manner. It starts by enqueueing the root node and then iterates through the queue. For each node dequeued, its value is printed, and its children are enqueued if they exist. This way, the traversal progresses in a breadth-first fashion, ensuring efficient utilization of the nodes and reducing redundant traversals.

Here is the Java implementation of the queue-based approach:

import java.util.LinkedList;
import java.util.Queue;

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        value = val;
        left = null;
        right = null;
    }
}

public class BinaryTree {
    TreeNode root;

    // Other methods and constructors can go here

    public void levelOrderQueue(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode current = queue.poll();
            System.out.print(current.value + " ");
            if (current.left != null) {
                queue.add(current.left);
            }
            if (current.right != null) {
                queue.add(current.right);
            }
        }
    }

    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        // Construct the tree
        tree.root = new TreeNode(1);
        tree.root.left = new TreeNode(2);
        tree.root.right = new TreeNode(3);
        tree.root.left.left = new TreeNode(4);
        tree.root.left.right = new TreeNode(5);

        tree.levelOrderQueue(tree.root);
    }
}

This program also defines a TreeNode class. The BinaryTree class has a levelOrderQueue method which leverages a queue to process nodes in a breadth-first manner. It starts with the root node and iterates through the queue, dequeuing nodes, printing their values, and enqueueing their children if available. The main function constructs a sample binary tree, and levelOrderQueue is called on the root node to perform efficient level order traversal using a queue.

Level Order Traversal of a Binary Tree in Java

Using Recursion

This approach uses recursion to traverse the binary tree level by level.

Here is the pseudocode for level order traversal of a binary tree using recursion:

levelOrderRecursive(root):
    if root is null:
        return
    height = getHeight(root)
    for level = 1 to height:
        printLevel(root, level)

getHeight(node):
    if node is null:
        return 0
    leftHeight = getHeight(node.left)
    rightHeight = getHeight(node.right)
    return max(leftHeight, rightHeight) + 1

printLevel(node, level):
    if node is null:
        return
    if level == 1:
        print(node.value)
    else:
        printLevel(node.left, level - 1)
        printLevel(node.right, level - 1)

Implementation

Here is the Java implementation of the above:

Code:

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;
    
    TreeNode(int val) {
        value = val;
        left = null;
        right = null;
    }
}

public class BinaryTree {
    TreeNode root;

    // Other methods and constructors can go here
    
    public void levelOrderRecursive(TreeNode root) {
        int height = getHeight(root);
        for (int level = 1; level <= height; level++) {
            printLevel(root, level);
        }
    }

    private int getHeight(TreeNode node) {
        if (node == null) {
            return 0;
        }
        int leftHeight = getHeight(node.left);
        int rightHeight = getHeight(node.right);
        return Math.max(leftHeight, rightHeight) + 1;
    }

    private void printLevel(TreeNode node, int level) {
        if (node == null) {
            return;
        }
        if (level == 1) {
            System.out.print(node.value + " ");
        } else {
            printLevel(node.left, level - 1);
            printLevel(node.right, level - 1);
        }
    }

    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        // Construct the tree
        
        tree.levelOrderRecursive(tree.root);
    }
}

The code defines a TreeNode class that represents a node in a binary tree. Each node has an integer value and references to its left and right child nodes. The constructor of the TreeNode class initializes these properties. The main functionality is implemented in the BinaryTree class. This class contains a method called levelOrderRecursive that performs level order traversal using a recursive approach.

Using Queue

This approach uses a queue data structure to perform level order traversal iteratively.

Here are the steps for the queue-based approach:

  1. Create a queue to store nodes to be visited.

  2. Enqueue the root node into the queue.

  3. While the queue is not empty, do the following:

  • Dequeue a node from the front of the queue.
  • Print the value of the dequeued node.
  • Enqueue its left child if it exists.
  • Enqueue its right child if it exists.
  1. Repeat steps 3 until the queue is empty, which ensures that all nodes are processed in a level order manner.

Here is the pseudocode for binary tree Level Order Traversal using queue:

levelOrderQueue(root):
    if root is null:
        return
    queue = new Queue()
    queue.enqueue(root)
    while queue is not empty:
        current = queue.dequeue()
        print(current.value)
        if current.left is not null:
            queue.enqueue(current.left)
        if current.right is not null:
            queue.enqueue(current.right)

Implementation

Code:

import java.util.LinkedList;
import java.util.Queue;

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;
    
    TreeNode(int val) {
        value = val;
        left = null;
        right = null;
    }
}

public class BinaryTree {
    TreeNode root;

    // Other methods and constructors can go here
    
    public void levelOrderQueue(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode current = queue.poll();
            System.out.print(current.value + " ");
            if (current.left != null) {
                queue.add(current.left);
            }
            if (current.right != null) {
                queue.add(current.right);
            }
        }
    }

    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        // Construct the tree
        
        tree.levelOrderQueue(tree.root);
    }
}

The code begins by defining the TreeNode class with integer valus and references to its left and right child nodes. The constructor initializes these properties.The BinaryTree class includes a method named levelOrderQueue responsible for performing level order traversal using a queue-based approach.

Inside the levelOrderQueue method, it starts by checking if the root node is null. If it is, there's no tree to traverse, so the method returns. A Queue<TreeNode> named queue is created using LinkedList and this queue will facilitate the orderly traversal of nodes. A while loop is employed to iterate through the queue.

Comparison of Both Approaches

Depth-First Traversal and Level Order Traversal (Breadth-First Traversal) are two different approaches for exploring nodes in a tree data structure. While both have their advantages and applications, the choice of which to use depends on the specific problem and the desired output. Let us compare the two approaches to understand them in detail:

Aspect

Depth-First Traversal

Level Order Traversal (Breadth-First Traversal)

Processing Order

Top-Down

Left to Right

Data Structure

Uses Stack

Uses Queue

Memory Usage

Requires less memory

Requires more memory

Implementation Complexity

Typically simpler to implement

Slightly more complex

Usage

Useful for specific tree-related problems

Ideal for searching, shortest path, and processing hierarchical data

Application

In-order, pre-order, and post-order traversals

Level-by-level exploration

Algorithm Type

Depth-First Search (DFS)

Breadth-First Search (BFS)

Conclusion

Level Order Traversal stands as a valuable algorithm in the realm of tree and graph exploration. Its systematic approach of traversing nodes layer by layer enables efficient searching, shortest path finding, and processing of hierarchical data. 

Whether implemented recursively or using an iterative queue approach, understanding both methods equips developers with the flexibility to apply Level Order Traversal effectively across various scenarios. Embrace this powerful technique and enhance your programming capabilities in tackling diverse challenges involving tree-like structures.

FAQs

1. What do you mean by Level Order Traversal in a binary tree?

Level Order Traversal of tree, also known as Breadth First Traversal explores a binary tree layer by layer, starting from the root node and moving to its children, followed by their respective children, and so on.

2. What is Level Order Traversal in queue?

Level Order Traversal using a queue involves using a queue data structure to process nodes in a binary tree. It enqueues nodes during exploration and dequeues them to visit their children, ensuring a breadth-first exploration.

3. What is Level Order Traversal Hackerrank?

On Hackerrank, Level Order Traversal challenges involve solving problems related to exploring trees in a breadth-first manner. They may require implementing the algorithm using either recursion or an iterative approach with a queue to achieve the desired output.

Leave a Reply

Your email address will not be published. Required fields are marked *