One article clarifies the binary tree's front, middle and back order traversal from recursion to loop, so you don't have to worry about preparing for the interview!

The autumn recruiting season is coming again. Whether it is a written test or an interview with whiteboard programming, binary tree traversal is a very common warm-up question, and the recursive writing method must be familiar to students. But the interviewer sees that you are so familiar with your writing. The young man has exquisite bones and is a good material for buckling. He must study it carefully, and the following question comes. What are the limitations of passing? blablabla... Well, you have a good point, let's rewrite it with a loop. this? ...Many students got stuck. Today's article is to solve this problem, explaining the problem thoroughly at once.

About the stack

First of all, let's review what a stack is. The principle of a stack is first in, last out. The first element to go in is always the last to come out, and the last element to go in is always the first to come out. Many of our behaviors in real life use the stack. Let's take a practical example: say you are eating

  1. I found that my homework was not finished, so I started to write my homework. Task stack: [eat]
  2. Halfway through my homework, I was thirsty and went to drink water. Task stack: [meal, homework]
  3. I saw a kitten on the way to drink water, so I went to pet it. Task stack: [eat, homework, drink water]
  4. After stroking the cat, continue to drink water. Task stack: [meal, homework]
  5. After drinking the water, I started to do my homework. Task stack: [eat]
  6. After finishing the homework, we started to eat. task stack: []
  7. After dinner, what should I do?

As can be seen from the above example, when we are interrupted by a task in progress, we put the ongoing task on the stack. Take out the most recent one every time and continue processing. In fact, recursive function calls are also based on this principle. Let's start to explain step by step how to rewrite the recursive traversal into a loop.

The so-called pre-middle-post order traversal differs from the position of the root node:

  1. Preorder: root node - left subtree traversal expansion - right subtree traversal expansion
  2. Inorder: left subtree traversal expansion - root node - right subtree traversal expansion
  3. Postorder: left subtree traversal expansion - right subtree traversal expansion - root node

We'll walk through it by hand before we start writing the code. (x) represents the node with the value x in the original tree, and the left and right subtrees point to the same; x represents the node with the value x, the left and right subtrees point to null, which is the copied node.

preorder traversal

  • The root node is placed on the stack. stack: [(1)]
  • Pop the top element 1 of the stack, and add the value to the result array. Because the left subtree is processed first, and then the right subtree is processed, so pay attention to the order of stacking. Result: [1], Stack: [(3), (2)]
  • Pop the top element 2 of the stack, add the value to the result array, and then process the subtree of this node. Result: [1, 2], Stack: [(3), (5), (4)]
  • The element 4 at the top of the stack is popped, and the value is added to the result array, and the subtree is empty, so it is not pushed into the stack. Result: [1, 2, 4], Stack: [(3), (5)]
  • Pop the top element 5 of the stack, add the value to the result array, the left subtree is empty, and only the right subtree needs to be pushed into the stack. Result: [1, 2, 4, 5], Stack: [(3), (8)]
  • The top element 8 of the stack is popped, and the value is added to the result array, and the subtree is empty, so it is not pushed into the stack. Result: [1, 2, 4, 5, 8], Stack: [(3)]
  • Pop the top element 3 of the stack, add the value to the result array, and then process the subtree of this node. Result: [1, 2, 4, 5, 8, 3], stack: [(7), (6)]
  • The element 6 at the top of the stack is popped, and the value is added to the result array, and the subtree is empty, so it is not pushed into the stack. Result: [1, 2, 4, 5, 8, 3, 6], Stack: [(7)]
  • The element 7 at the top of the stack is popped, and the value is added to the result array, and the subtree is empty, so it is not pushed into the stack. Result: [1, 2, 4, 5, 8, 3, 6, 7], stack: []
  • The stack is empty, end.

So it is easy to translate the above steps into code: verify that the code is correct in LintCode .

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root != null) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root); // 初始化堆栈
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop(); // 弹出栈顶元素处理
            ret.add(node.val);
            if (node.right != null) {
                stack.push(node.right); // 右子树不为空先入栈,然后再判断左子树。
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
    }
    return ret;
}

Inorder traversal

Pay attention to in-order traversal, first traverse the left subtree, then display the root node, and finally traverse the right subtree. According to the feeling, the stacking order should be: right subtree, root node, left subtree. Note that only one value is needed when the root node is pushed onto the stack, so the node needs to be copied and the left and right subtrees should be empty. Because each element in the stack is a node, if the left and right subtrees are not empty, it means that the node is not a leaf node and cannot be directly output, but the left and right subtrees must be processed first according to the traversal order.

  • The root node is placed on the stack. stack: [(1)]
  • Pop up the top element 1 of the stack, because the left and right subtrees are not empty, according to the traversal order, the left subtree is processed first, then the current node, and then the right subtree is processed, so pay attention to the stacking order. Note : Because only the nodes whose left and right subtrees are empty can be output, so copy the node, set the left and right subtrees to be empty, and then push it into the stack. result: [], stack: [(3),  1 , (2)]
  • Pop the top element 2 of the stack and push it on the stack in the order of traversal. result: [], stack: [(3),  1 , (5), 2 , (4)]
  • Pop the top element 4 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4], stack: [(3),  1 , (5), 2 ]
  • Pop the top element 2 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 2], stack: [(3),  1 , (5)]
  • Pop the top element 5 of the stack, because the left subtree is empty, so it is output directly, and then the right subtree is pushed into the stack. Result: [4, 2, 5], stack: [(3),  1 , (8)]
  • Pop the top element 8 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 2, 5, 8], stack: [(3),  1 ]
  • Pop the top element 1 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 2, 5, 8, 1], stack: [(3)]
  • Pop the top element 3 of the stack, because the left and right subtrees are not empty, push the stack in the order of traversal. Result: [4, 2, 5, 8, 1], stack: [(7), 3 , (6)]
  • Pop the top element 6 of the stack, because the left and right subtrees are empty, so it is output directly. [4, 2, 5, 8, 1, 6], stack: [(7), 3 ]
  • Pop the top element 3 of the stack, because the left and right subtrees are empty, so it is output directly. [4, 2, 5, 8, 1, 6, 3], stack: [(7)]
  • Pop the top element 7 of the stack, because the left and right subtrees are empty, so it is output directly. [4, 2, 5, 8, 1, 6, 3, 7], stack: []
  • The stack is empty, end.

To sum up, the steps to translate into code are as follows:

  • The root node is pushed into the stack
  • if the stack is not empty
    • Pop the top element of the stack
    • If the right subtree is not empty, push it onto the stack.
    • Determine if the left subtree is empty
      • yes! Create a new node, copy the value of the current node, set the left and right subtrees to empty, push them into the stack, and then push the left subtree into the stack.
      • no! Output the current value directly.

Verify that the code is correct in LintCode .

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root != null) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left == null) {
                ret.add(node.val);
            } else {
                TreeNode tmp = new TreeNode(node.val); // 构造函数默认左右子树为null
                stack.push(tmp);
                stack.push(node.left);
            }
        }
    }
    return ret;
}

post order traversal

Now that the in-order traversal is done, the subsequent traversal is based on the code of the in-order traversal. Simply put, if the left and right subtrees of the node are not all empty, copy the value of the current node and put it on the stack, then if the right subtree is not empty, push it to the stack, and if the left subtree is not empty, push it on the stack. In order to facilitate everyone's understanding, let's take the trouble to write the specific steps again.

  • The root node is placed on the stack. stack: [(1)]
  • Pop the top element 1 of the stack, because the left and right subtrees are not all empty, so they are pushed into the stack in order. result: [], stack: [ 1 , (3), (2)]
  • Pop the top element 2 of the stack, because the left and right subtrees are not all empty, so they are pushed into the stack in order. Result: [], stack: [ 1 , (3), 2 , (5), (4)] ( Note : Here node 2 is copied, and the left and right subtrees are all empty.)
  • Pop the top element 4 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4], stack: [ 1 , (3), 2 , (5)]
  • Pop the top element 5 of the stack, because the right subtree is not empty, so it is pushed into the stack in order. Result: [4], stack: [ 1 , (3), 2 , 5 , (8)]
  • Pop the top element 8 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8], stack: [ 1 , (3), 2 , 5 ]
  • Pop the top element 5 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5], stack: [ 1 , (3), 2 ]
  • Pop the top element 2 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5, 2], stack: [ 1 , (3)]
  • Pop the top element 2 of the stack, because the left and right subtrees are not all empty, so they are pushed into the stack in order. Result: [4, 8, 5, 2], stack: [ 1 , 3 , (7), (6)]
  • Pop the top element 5 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5, 2, 6], stack: [ 1 , 3 , (7)]
  • Pop the top element 7 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5, 2, 6, 7], stack: [ 1 , 3 ]
  • Pop the top element 3 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5, 2, 6, 7, 3], stack: [ 1 ]
  • Pop the top element 1 of the stack, because the left and right subtrees are empty, so it is output directly. Result: [4, 8, 5, 2, 6, 7, 3, 1], stack: []
  • The stack is empty, end.

To sum up, the steps to translate into code are as follows:

  • The root node is pushed into the stack
  • if the stack is not empty
    • Pop the top element of the stack
    • Determine whether the left and right subtrees are empty
      • yes! If the right subtree is not empty, push it to the stack; if the left subtree is not empty, push it to the stack.
      • no! Output the current value directly.

Verify that the code is correct in LintCode .

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root != null) {
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if ((node.left == null) && (node.right == null)) {
                ret.add(node.val);
            } else {
                TreeNode tmp = new TreeNode(node.val);
                stack.push(tmp);
                if (node.right != null) {
                    stack.push(node.right);
                }
                if (node.left != null) {
                    stack.push(node.left);
                }
            }
        }
    }
    return ret;
}

Finally, I recommend an online tool for drawing binary trees: http://mshang.ca/syntree/ . Enter the expression yourself, [] represents an empty subtree. The simplest structure is expressed as follows: [1 [2] [3]]. The example in the article can be generated with [1 [2 [4] [5 [] [8]]] [3 [6] [7]]].

Guess you like

Origin blog.csdn.net/panda_lin/article/details/108230003
Recommended