javascripttreestacknodestraversal

Finding leftmost nodes in every level of a tree


Here's my code to set up tree nodes.

class TreeNode {
  constructor(value, left, right, level) {
    this.value = value;
    this.left = left;
    this.right = right;
    this.level = level
  }
}

Here's my code to find the leftmost node at each tree level.

function findFirstNodes(root) {

    const stack = [root];

    root.level = 0;
    const firsts = [];

    while (stack.length > 0) {

      const curr = stack.pop();

      if (firsts[curr.level]) {

          firsts.push(curr.value);

      };

      if (curr.left) {

          stack.unshift(curr.left);

      };

    };

    return firsts;

};

Here's my test code.

const simpleTree = new TreeNode(4, null, null);
simpleTree.right = new TreeNode(8, null, null);
simpleTree.left = new TreeNode(3, null, null);
simpleTree.right.right = new TreeNode(2, null, null);

console.log(findFirstNodes(simpleTree)); // -> [ 4, 3, 2 ]

Yet my result array comes out empty.

I have a general good grasp on the knowledge of how to traverse trees and such, but many of the details are still rusty to me. The code doesn't result in any errors, but the array just comes out empty. I tried console logging the curr variable when the tree is traversed, but I only get the one with 3.


Solution

  • There are a few issues:

    Not a real problem, but naming your array stack is misleading, because you use it as a queue, not a stack.

    The idea to approach this with a queue is good. But because unsift has a bad time complexity, I would suggest to use two (nested) loops: one that iterates the nodes in the last level that was already visited, and one that visits all the direct children of those nodes and puts them in a new array: this will have all nodes of the next level. Once that is done, this array can become the source to work with in the outer loop again. This way it is easy to identify the first node on each level.

    Here is the suggested code:

    class TreeNode {
        constructor(value, left, right) {
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
    
    function findFirstNodes(root) {
        let levelNodes = [root];
    
        const firsts = [];
        while (levelNodes.length > 0) {
            firsts.push(levelNodes[0].value);
            const nextLevelNodes = [];
            for (const node of levelNodes) {
                if (node.left) nextLevelNodes.push(node.left);
                if (node.right) nextLevelNodes.push(node.right);
            }
            levelNodes = nextLevelNodes;
        }
        return firsts;
    }
    
    // Your test case:
    const simpleTree = new TreeNode(4,
        new TreeNode(3),
        new TreeNode(8,
            null,
            new TreeNode(2)
        )
    );
    
    console.log(findFirstNodes(simpleTree)); // -> [ 4, 3, 2 ]

    To do the same for any rooted tree, not just binary trees, you need to define your TreeNode class differently, and stop using left and right.

    class TreeNode {
        constructor(value, ...children) {
            this.value = value;
            this.children = children;
        }
    }
    
    function findFirstNodes(root) {
        let levelNodes = [root];
    
        const firsts = [];
        while (levelNodes.length > 0) {
            firsts.push(levelNodes[0].value);
            levelNodes = levelNodes.flatMap(node => node.children);
        }
        return firsts;
    }
    
    // Your test case. Note that now the node with value 2 is "just" 
    //    THE child, not specifically a "right" child.
    const simpleTree = new TreeNode(4,
        new TreeNode(3),
        new TreeNode(8,
            new TreeNode(2)
        )
    );
    
    console.log(findFirstNodes(simpleTree)); // -> [ 4, 3, 2 ]