Preorder, Inorder, and Postorder tree traversals are all forms of Depth-First Search (DFS) . They are different ways of visiting the nodes of a binary tree by exploring as deep as possible along each branch before backtracking, which is the core principle of DFS.
Preorder Traversal (Root, Left, Right) :
The preorder traversal of a binary tree recursively visits the the root node first, then visits left subtree, followed by the right subtree.
- Visit the root node.
- Recursively visit the left subtree.
- Recursively visit the right subtree.
The preorder traversal of a binary tree iteratively uses a stack to simulate the recursion.
- Push the root node to the stack.
- Pop the top of the stack, process the node, and push its right child (if it exists), followed by its left child (if it exists), so that left is processed first (LIFO)
- Continue this process until the stack is empty.
Inorder Traversal (Left, Root, Right) :
The inorder traversal of a binary tree recursively visits the left subtree first, then visits the root node, followed by the right subtree.
- Recursively visit the left subtree.
- Visit the root node.
- Recursively visit the right subtree.
The inorder traversal of a binary tree iteratively uses a stack to simulate the recursion.
- Start from the root, and push all left children to the stack until we reach the leftmost node.
- pop the top node from the stack, process the node, and move to its right child.
- The loop continues until both the current node is null and the stack is empty.
Postorder Traversal (Left, Right, Root) :
The postorder traversal of a binary tree recursively visits the left subtree first, then visits the right subtree, followed by the root node.
- Recursively visit the left subtree.
- Recursively visit the right subtree.
- Visit the root node.
The postorder traversal of a binary tree iteratively uses two stacks. One for performing a modified pre-order traversal(Root -> Right -> Left), and one for reversing the modified pre-order traversal.
- Use stack1 to perform a modified pre-order traversal (Root -> Right -> Left), where we process the root first, followed by the right child, and then the left child.
- Each time we pop a node from stack1, we push it onto stack2. This is reversing the modified pre-order traversal, so we get the nodes in post-order (left -> right -> root) when we pop from stack2.
- After stack1 is empty, we pop all elements from stack2 to get the post-order traversal.
Real Example
Consider the following binary tree:
1
/ \
2 3
/ \ \
4 5 6
-
Preorder Traversal:
- Start at the root: 1
- Traverse the left subtree: 2, 4, 5
- Traverse the right subtree: 3, 6
- Preorder: [1, 2, 4, 5, 3, 6]
-
Inorder Traversal:
- Traverse the left subtree: 4, 2, 5
- Visit the root: 1
- Traverse the right subtree: 3, 6
- Inorder: [4, 2, 5, 1, 3, 6]
-
Postorder Traversal:
- Traverse the left subtree: 4, 5, 2
- Traverse the right subtree: 6, 3
- Visit the root: 1
- Postorder: [4, 5, 2, 6, 3, 1]
Java code implementations for performing Preorder, Inorder, Postorder traversals and Breadth-First Search of a binary tree
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
// Definition for a binary tree node. Defines the structure of a node in the binary tree, with integer value `val`, and references to left and right child nodes.
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x;}
}
public class BinaryTreeTraversals {
// Preorder traversal method visits the root node first, then recursively visits the left subtree, followed by the right subtree.
public static void preorderTraversalRecursively(TreeNode root) {
if (root == null) { return; }
// Visit the root node
System.out.print(root.val + " ");
// Traverse the left subtree
preorderTraversalRecursively(root.left);
// Traverse the right subtree
preorderTraversalRecursively(root.right);
}
public static void preorderTraversalIteratively(TreeNode root) {
if (root == null) { return; }
// Use a stack to simulate the recursion
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
// Pop the top of the stack, process the node, and push its right child (if it exists), followed by its left child (if it exists). Continue this process until the stack is empty.
TreeNode current = stack.pop();
System.out.print(current.val + " ");
// Push right first so that left is processed first (LIFO)
if (current.right != null) {
stack.push(current.right);
}
if (current.left != null) {
stack.push(current.left);
}
}
}
// Inorder traversal method
public static void inorderTraversalRecursively(TreeNode root) {
if (root == null) { return; }
// Traverse the left subtree
inorderTraversalRecursively(root.left);
// Visit the root node
System.out.print(root.val + " ");
// Traverse the right subtree
inorderTraversalRecursively(root.right);
}
public static void inorderTraversalIteratively(TreeNode root) {
// Use a stack to simulate recursion
Stack<TreeNode> stack = new Stack<>();
TreeNode current = root;
// The loop continues until both the current node is null and the stack is empty
while (current != null || !stack.isEmpty()) {
// Push all the left children onto the stack
while (current != null) {
stack.push(current);
current = current.left;
}
// When we reach the leftmost node, we pop the top node from the stack, process the node, and move to its right child
current = stack.pop();
System.out.print(current.val + " ");
// Move to the right subtree
current = current.right;
}
}
// Postorder traversal method
public static void postorderTraversalRecursively(TreeNode root) {
if (root == null) { return; }
// Traverse the left subtree
postorderTraversalRecursively(root.left);
// Traverse the right subtree
postorderTraversalRecursively(root.right);
// Visit the root node
System.out.print(root.val + " ");
}
public static void postorderTraversalIteratively(TreeNode root) {
if (root == null) { return; }
// Use stack1 to perform a modified pre-order traversal (Root -> Right -> Left), where we process the root first, followed by the right child, and then the left child.
Stack<TreeNode> stack1 = new Stack<>();
// Each time we pop a node from stack1, we push it onto stack2. This is reversing the modified pre-order traversal, so we get the nodes in post-order (left -> right -> root) when we pop from stack2.
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(root);
// Perform a modified pre-order traversal: Root -> Right -> Left
while (!stack1.isEmpty()) {
TreeNode current = stack1.pop();
stack2.push(current); // Push the node to stack2
// Push the left and right children to stack1
if (current.left != null) {
stack1.push(current.left);
}
if (current.right != null) {
stack1.push(current.right);
}
}
// After stack1 is empty, pop all the elements from stack2 to get the post-order
while (!stack2.isEmpty()) {
System.out.print(stack2.pop().val + " ");
}
}
public static void breadthFirstSearch(TreeNode root) {
if (root == null) return;
// A queue is used to keep track of nodes at each level. We enqueue the root node first and then, for every node we dequeue, we enqueue its left and right children (if they exist). The queue ensures that nodes are processed level by level.
Queue<TreeNode> queue = new LinkedList<>();
// Start at the root of the tree, and enqueue the root node
queue.add(root);
while (!queue.isEmpty()) {
TreeNode currentNode = queue.poll(); // Dequeue the front node
System.out.print(currentNode.val + " "); // Process the current node
// Enqueue left child of the current node if it exists
if (currentNode.left != null) {
queue.add(currentNode.left);
}
// Enqueue right child of the current node if it exists
if (currentNode.right != null) {
queue.add(currentNode.right);
}
}
}
public static void main(String[] args) {
// Constructing the example binary tree
// 1
// / \
// 2 3
// / \ \
// 4 5 6
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.right = new TreeNode(6);
// Preorder Traversal: 1 2 4 5 3 6
System.out.print("Preorder Traversal Recursively: ");
BinaryTreeTraversals.preorderTraversalRecursively(root);
System.out.print("Preorder Traversal Iteratively: ");
BinaryTreeTraversals.preorderTraversalIteratively(root);
// Inorder Traversal: 4 2 5 1 3 6
System.out.print("Inorder Traversal Recursively: ");
BinaryTreeTraversals.inorderTraversalRecursively(root);
System.out.print("Inorder Traversal Iteratively: ");
BinaryTreeTraversals.inorderTraversalIteratively(root);
// Postorder Traversal: 4 5 2 6 3 1
System.out.print("Postorder Traversal Recursively: ");
BinaryTreeTraversals.postorderTraversalRecursively(root);
System.out.print("Postorder Traversal Iteratively: ");
BinaryTreeTraversals.postorderTraversalIteratively(root);
// Breadth-First Search(BFS, Level-Order Traversal): 1 2 3 4 5 6
System.out.print("Breadth-First Search(BFS, Level-Order Traversal): ");
BinaryTreeTraversals.breadthFirstSearch(root);
}
}
Understanding Why a Binary Tree Cannot Be Reconstructed From Pre-order and Post-order Traversals Alone
1. Pre-order and Post-order Traversals
- Pre-order traversal visits the nodes in the order: Root → Left → Right.
- Post-order traversal visits the nodes in the order: Left → Right → Root.
When we have pre-order and post-order traversal results, the problem is that these two sequences do not provide enough information to unambiguously determine the tree structure, especially if the tree is not strictly binary (i.e., nodes can have either no children, one child, or two children).
Consider a simple example:
- A pre-order traversal of a tree gives:
A B C - A post-order traversal of the same tree gives:
B C A
From these two traversals alone, you can't definitively say whether the tree has a structure like:
A
/
B
\
C
or like:
A
/
C
/
B
The reason is that both structures would generate the same pre-order and post-order sequences. Thus, pre-order and post-order traversals do not provide enough information to reconstruct the tree uniquely.
2. Pre-order and In-order Traversals
In contrast, when we combine pre-order and in-order traversal results, we can recover the tree unambiguously.
- Pre-order traversal always begins with the root node.
- In-order traversal allows us to determine the structure of the left and right subtrees.
For example, if the pre-order traversal is A B D E C F and the in-order traversal is D B E A F C, you can deduce the following:
- The root (
A) comes first in pre-order. - In the in-order sequence, everything to the left of
A(i.e.,D B E) is part of the left subtree, and everything to the right ofA(i.e.,F C) is part of the right subtree. - Recursively applying this logic to the left and right subtrees helps reconstruct the entire tree uniquely.
3. Post-order and In-order Traversals
Similarly, post-order and in-order traversals allow us to reconstruct the binary tree. Post-order gives the root last, and the in-order sequence helps in identifying the subtrees, making it possible to rebuild the tree in a unique way.