Tree Traversal

1. Inorder Morris Traversal O(1) Space

Concept Strategy Time Space
Threaded Binary Tree Create temporary links from predecessor to current node O(n) O(1)
Predecessor Finding For current node, find rightmost node in left subtree O(1) amortized No stack needed
Link Creation Make predecessor's right child point to current (thread) Temporary modification Restored after use
Link Removal Remove thread after visiting, restore original structure Tree unchanged No permanent changes

Example: Morris inorder traversal

class TreeNode {
    constructor(val = 0, left = null, right = null) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

function morrisInorder(root) {
    const result = [];
    let curr = root;
    
    while (curr !== null) {
        if (curr.left === null) {
            // No left subtree, visit and go right
            result.push(curr.val);
            curr = curr.right;
        } else {
            // Find inorder predecessor
            let pred = curr.left;
            while (pred.right !== null && pred.right !== curr) {
                pred = pred.right;
            }
            
            if (pred.right === null) {
                // Create thread
                pred.right = curr;
                curr = curr.left;
            } else {
                // Thread exists, remove it and visit
                pred.right = null;
                result.push(curr.val);
                curr = curr.right;
            }
        }
    }
    
    return result;
}

// Example tree:     4
//                  / \
//                 2   6
//                / \ / \
//               1  3 5  7
// Inorder: [1, 2, 3, 4, 5, 6, 7]
Note: Morris traversal is O(1) space by using threaded links. Each edge is traversed at most 3 times: once going down, once creating thread, once removing thread. Still O(n) time overall.

Example: Morris preorder and postorder

// Morris Preorder: Visit before going left
function morrisPreorder(root) {
    const result = [];
    let curr = root;
    
    while (curr !== null) {
        if (curr.left === null) {
            result.push(curr.val);
            curr = curr.right;
        } else {
            let pred = curr.left;
            while (pred.right !== null && pred.right !== curr) {
                pred = pred.right;
            }
            
            if (pred.right === null) {
                result.push(curr.val); // Visit before thread
                pred.right = curr;
                curr = curr.left;
            } else {
                pred.right = null;
                curr = curr.right;
            }
        }
    }
    
    return result;
}

// Morris Postorder is more complex
// Requires reversing right spine of left subtrees
function morrisPostorder(root) {
    const dummy = new TreeNode(0);
    dummy.left = root;
    const result = [];
    let curr = dummy;
    
    while (curr !== null) {
        if (curr.left === null) {
            curr = curr.right;
        } else {
            let pred = curr.left;
            while (pred.right !== null && pred.right !== curr) {
                pred = pred.right;
            }
            
            if (pred.right === null) {
                pred.right = curr;
                curr = curr.left;
            } else {
                // Reverse and visit right spine
                reverseAddNodes(curr.left, pred, result);
                pred.right = null;
                curr = curr.right;
            }
        }
    }
    
    return result;
}

function reverseAddNodes(from, to, result) {
    reverse(from, to);
    let node = to;
    while (true) {
        result.push(node.val);
        if (node === from) break;
        node = node.right;
    }
    reverse(to, from);
}

2. Level Order Bottom Up

Approach Method Data Structure Complexity
BFS + Reverse Standard level order, then reverse result array Queue + array O(n) time, O(n) space
BFS + Stack Use stack to reverse levels as we go Queue + stack O(n) time, O(n) space
DFS + Level Tracking Track depth, insert at beginning of level arrays Array of arrays O(n) time, O(h) recursion
Two Queue Process level by level with two queues Two queues O(n) time, O(w) width

Example: Bottom-up level order (BFS + reverse)

function levelOrderBottom(root) {
    if (!root) return [];
    
    const result = [];
    const queue = [root];
    
    while (queue.length > 0) {
        const levelSize = queue.length;
        const level = [];
        
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            level.push(node.val);
            
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        
        result.push(level);
    }
    
    return result.reverse();
}

// Example tree:     3
//                  / \
//                 9  20
//                   /  \
//                  15   7
// Result: [[15,7], [9,20], [3]]

Example: Bottom-up using DFS

function levelOrderBottomDFS(root) {
    const result = [];
    
    function dfs(node, level) {
        if (!node) return;
        
        // Ensure array exists for this level
        if (result.length === level) {
            result.push([]);
        }
        
        result[level].push(node.val);
        
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
    }
    
    dfs(root, 0);
    return result.reverse();
}

// Alternative: Insert at beginning
function levelOrderBottomDFS2(root) {
    const result = [];
    
    function dfs(node, level) {
        if (!node) return;
        
        if (result.length === level) {
            result.unshift([]); // Insert at beginning
        }
        
        result[result.length - 1 - level].push(node.val);
        
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
    }
    
    dfs(root, 0);
    return result;
}

3. Vertical Order Traversal

Concept Technique Sorting Criteria Use Case
Column Indexing Assign column index: left child = col-1, right child = col+1 Primary: column Group nodes by vertical position
Row Tracking Track row (level/depth) for each node Secondary: row Order within same column
Value Sorting Sort by value when col and row are same Tertiary: value Deterministic ordering
Map Storage Use map with column as key, array of (row, val) as value Efficient grouping Collect all nodes per column

Example: Vertical order traversal with sorting

function verticalTraversal(root) {
    if (!root) return [];
    
    const map = new Map(); // col -> [[row, val], ...]
    let minCol = 0, maxCol = 0;
    
    // BFS with (node, row, col) tuples
    const queue = [[root, 0, 0]];
    
    while (queue.length > 0) {
        const [node, row, col] = queue.shift();
        
        if (!map.has(col)) {
            map.set(col, []);
        }
        map.get(col).push([row, node.val]);
        
        minCol = Math.min(minCol, col);
        maxCol = Math.max(maxCol, col);
        
        if (node.left) {
            queue.push([node.left, row + 1, col - 1]);
        }
        if (node.right) {
            queue.push([node.right, row + 1, col + 1]);
        }
    }
    
    const result = [];
    
    // Process columns from left to right
    for (let col = minCol; col <= maxCol; col++) {
        const column = map.get(col);
        
        // Sort by row, then by value
        column.sort((a, b) => {
            if (a[0] !== b[0]) return a[0] - b[0]; // row
            return a[1] - b[1]; // value
        });
        
        result.push(column.map(item => item[1]));
    }
    
    return result;
}

// Example tree:      3
//                   / \
//                  9  20
//                    /  \
//                   15   7
// Result: [[9], [3,15], [20], [7]]

4. Boundary Traversal Pattern

Boundary Part Traversal Direction Exclusions
Left Boundary Root to leftmost leaf (excluding leaf) Top to bottom Don't include leaf nodes
Leaves All leaf nodes from left to right Left to right Inorder/preorder traversal
Right Boundary Rightmost leaf to root (excluding root and leaf) Bottom to top Process in reverse
Root Handling Add root first if not a leaf Special case Avoid duplicates

Example: Binary tree boundary traversal

function boundaryOfBinaryTree(root) {
    if (!root) return [];
    
    const result = [];
    
    // Add root if not a leaf
    if (!isLeaf(root)) {
        result.push(root.val);
    }
    
    // Add left boundary (top to bottom, excluding leaves)
    let node = root.left;
    while (node) {
        if (!isLeaf(node)) {
            result.push(node.val);
        }
        node = node.left ? node.left : node.right;
    }
    
    // Add all leaves (left to right)
    addLeaves(root, result);
    
    // Add right boundary (bottom to top, excluding leaves)
    const rightBoundary = [];
    node = root.right;
    while (node) {
        if (!isLeaf(node)) {
            rightBoundary.push(node.val);
        }
        node = node.right ? node.right : node.left;
    }
    
    // Reverse right boundary
    result.push(...rightBoundary.reverse());
    
    return result;
}

function isLeaf(node) {
    return node && !node.left && !node.right;
}

function addLeaves(node, result) {
    if (!node) return;
    
    if (isLeaf(node)) {
        result.push(node.val);
        return;
    }
    
    addLeaves(node.left, result);
    addLeaves(node.right, result);
}

// Example tree:       1
//                   /   \
//                  2     3
//                 / \   / 
//                4   5 6   
//               / \   /
//              7   8 9
// Boundary: [1, 2, 4, 7, 8, 9, 6, 3]

5. Zigzag Level Order

Approach Method Reversal Strategy Complexity
BFS + Reverse Alternate Normal BFS, reverse every other level Reverse array after level completion O(n)
Deque Use deque, alternate between addFirst/addLast Add to appropriate end based on level O(n)
Two Stacks Alternate between two stacks for levels Stack naturally reverses order O(n)
DFS + Level Flag Track level, insert at appropriate position Insert at beginning or end based on parity O(n)

Example: Zigzag with BFS and reverse

function zigzagLevelOrder(root) {
    if (!root) return [];
    
    const result = [];
    const queue = [root];
    let leftToRight = true;
    
    while (queue.length > 0) {
        const levelSize = queue.length;
        const level = [];
        
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            level.push(node.val);
            
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        
        // Reverse if right to left
        if (!leftToRight) {
            level.reverse();
        }
        
        result.push(level);
        leftToRight = !leftToRight;
    }
    
    return result;
}

// Example:      3
//              / \
//             9  20
//               /  \
//              15   7
// Result: [[3], [20,9], [15,7]]

Example: Zigzag with two stacks

function zigzagLevelOrderStacks(root) {
    if (!root) return [];
    
    const result = [];
    let currLevel = [root];
    let nextLevel = [];
    let leftToRight = true;
    let level = [];
    
    while (currLevel.length > 0) {
        const node = currLevel.pop();
        level.push(node.val);
        
        if (leftToRight) {
            if (node.left) nextLevel.push(node.left);
            if (node.right) nextLevel.push(node.right);
        } else {
            if (node.right) nextLevel.push(node.right);
            if (node.left) nextLevel.push(node.left);
        }
        
        if (currLevel.length === 0) {
            result.push(level);
            level = [];
            [currLevel, nextLevel] = [nextLevel, currLevel];
            leftToRight = !leftToRight;
        }
    }
    
    return result;
}

// Uses stack property: last pushed is first popped
// Alternates child order to achieve zigzag

6. Serialize Deserialize Tree

Format Encoding Decoding Features
Preorder with Nulls Preorder traversal, mark nulls explicitly Reconstruct using preorder pattern Simple, unambiguous
Level Order BFS Level by level, include nulls Rebuild level by level with queue Natural representation
Parenthesis Notation val(left)(right) format Parse recursively Human readable
Compact Binary Use null markers, skip nulls where possible Requires careful parsing Space efficient

Example: Serialize/deserialize with preorder

class Codec {
    serialize(root) {
        const result = [];
        
        function preorder(node) {
            if (!node) {
                result.push("null");
                return;
            }
            result.push(node.val.toString());
            preorder(node.left);
            preorder(node.right);
        }
        
        preorder(root);
        return result.join(",");
    }
    
    deserialize(data) {
        const values = data.split(",");
        let index = 0;
        
        function buildTree() {
            if (values[index] === "null") {
                index++;
                return null;
            }
            
            const node = new TreeNode(
                parseInt(values[index++])
            );
            node.left = buildTree();
            node.right = buildTree();
            return node;
        }
        
        return buildTree();
    }
}

// Example: Tree [1,2,3,null,null,4,5]
// Serialized: "1,2,null,null,3,4,null,null,5,null,null"

Example: Serialize/deserialize with BFS

class CodecBFS {
    serialize(root) {
        if (!root) return "";
        
        const result = [];
        const queue = [root];
        
        while (queue.length > 0) {
            const node = queue.shift();
            
            if (node) {
                result.push(node.val);
                queue.push(node.left);
                queue.push(node.right);
            } else {
                result.push("null");
            }
        }
        
        return result.join(",");
    }
    
    deserialize(data) {
        if (!data) return null;
        
        const values = data.split(",");
        const root = new TreeNode(
            parseInt(values[0])
        );
        const queue = [root];
        let i = 1;
        
        while (queue.length > 0) {
            const node = queue.shift();
            
            // Left child
            if (values[i] !== "null") {
                node.left = new TreeNode(
                    parseInt(values[i])
                );
                queue.push(node.left);
            }
            i++;
            
            // Right child
            if (values[i] !== "null") {
                node.right = new TreeNode(
                    parseInt(values[i])
                );
                queue.push(node.right);
            }
            i++;
        }
        
        return root;
    }
}

// Level order format is more intuitive
// Matches typical tree diagram representation
Note: For serialization:
  • Preorder: Most compact recursive solution, easy to implement
  • Level order: Matches visual representation, easier to debug
  • Always mark nulls: Essential for unambiguous reconstruction
  • Choose delimiter wisely: Avoid conflicts with node values (use comma, pipe, etc.)

Summary: Tree Traversal Patterns

  • Morris Traversal: O(1) space inorder/preorder using threaded binary tree - create temporary links to predecessors, visit, then remove links
  • Predecessor Finding: For node N, predecessor is rightmost node in left subtree - enables threading without stack
  • Level Order Bottom-Up: Standard BFS then reverse, or DFS with level tracking and reverse insertion
  • Vertical Order: Assign column indices (left=-1, right=+1), track row, sort by (col, row, val) - use BFS with tuples
  • Boundary Traversal: Three parts: left boundary (top-down), leaves (left-right), right boundary (bottom-up reversed)
  • Zigzag Level Order: BFS with alternating reversal, or two stacks alternating child order, or deque with directional insertion
  • Serialization: Preorder with null markers (compact recursive) or level order (intuitive iterative) - always mark nulls explicitly
  • Deserialization: Preorder uses recursion with index tracking; level order uses queue with parent-child linking
  • Space Optimization: Morris O(1) for traversal, iterative for level order O(width), recursive uses O(height) stack
  • Common Patterns: BFS for level-based, DFS for depth-based, threading for space optimization, queue/stack for order control