Loading

二叉树的迭代遍历

二叉树迭代遍历的思路,下面的代码是中序遍历:

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> list = new ArrayList();
    Stack<TreeNode> stack = new Stack();

    // 需要针对当前节点 深入其左子树
    TreeNode curr = root;
    // 当前节点不为空或者stack不为空
    while(curr != null || !stack.isEmpty()) {
        // 如果当前节点不为空,需要深入当前节点的左子树
        if (curr != null) {
            stack.push(curr);
            curr = curr.left;
        // 否则,证明上一个节点已经没有左子树了
        // 这时,上一个节点应该被消费了,并且
        // 将它的右子树继续当作一个新的树递归
        // 注意,此时栈可能没空,被消费的节点上面可能还有很多节点没有被消费或被扫描
        } else {
            TreeNode t = stack.pop();
            list.add(t.val);
            curr = t.right;
        }
    }

    return list;
}

用递归的思路思考

当迭代一个树时,我们要用递归的方式思考,会简单些。比如迭代树时会出现很多这样的代码:

curr = curr.left;

此时就是将当前节点的左子树作为一颗新的树重复算法中curr经历过的过程,而不要只是看作“取curr.left赋值给curr”,虽然没错,但少了很多生动。

上面代码中何时curr==null

curr是当前正在迭代中的子树,所以下面的代码预示着,当当前子树没有左子树时,下一轮迭代curr为空:

curr = curr.left;

下面的代码出现在循环中curr==null的分支里,这代表栈中需要最先被消费的子树t没有右子树时,下一轮迭代curr为空,而进入这个分支的前提条件是,子树t也没有左子树,就是说子树t已经是叶子节点:

curr = t.right;

所以

  1. 当当前子树没有左子树时,下轮curr为空
  2. curr为null,且栈中需要最先被消费的子树是个叶子节点时,下轮curr为空

而当前子树为null时,证明左边已经遍历到底了,这时就开启从栈中取最先需要被消费的节点,消费它,并迭代它的右子树的情况。

最初掉进的坑

最初是想用一个curr记录当前的节点,从根节点开始,一直向左,每次都将经过的节点入栈,直到没有左节点,代码就是这样:

Stack<TreeNode> stack = new Stack();
TreeNode curr = root;
while(curr != null) {
    if (curr.left != null) {
        stack.push(curr);
        curr = curr.left;
    } else {
        // ... ???
    }
}

然后我在else里应该怎么做?我知道父节点,也就是上一个节点肯定是栈顶节点,那我弹出个节点作为curr?可是再次遍历时它的left又有了啊

于是就去看了题解,由于是中序遍历,这个时候其实完全可以不让它作为curr,而是直接消费了这个节点,假设这个节点是A,因为按照中序的原则,A当前正在被遍历,消费之后再把它的右子节点作为一个新的子树入栈,继续重复迭代过程。所以就看到了上面的代码。

前序遍历和后序遍历

思路完全没有区别,就是消费节点的位置不一样:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList();
        Stack<TreeNode> stack = new Stack();

        TreeNode curr = root;
        while(curr != null || !stack.isEmpty()) {
            if (curr != null) {
                stack.push(curr);
                // 前序在这里消费节点
                list.add(curr.val);
                curr = curr.left;
            } else {
                TreeNode t = stack.pop();
                curr = t.right;
            }
        }

        return list;
    }
}

后序遍历的代码较难,因为无法确定栈顶节点的右子树是否已经被遍历过了,如果没有遍历过是不能直接消费栈顶节点的。这里使用一个prev来记录上一个被消费的节点,因为根据后序遍历原则,上一个被消费的节点一定是当前节点的直接右子节点:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList();
        Stack<TreeNode> stack = new Stack();

        TreeNode curr = root, prev = null;
        while(curr != null || !stack.isEmpty()) {
            if (curr != null) {
                stack.push(curr);
                curr = curr.left;
            } else {
                TreeNode t = stack.peek();
                if (t.right == null || t.right == prev) {
                    stack.pop();
                    list.add(t.val);
                    prev = t;
                } else {
                    curr = t.right;
                }
            }
        }

        return list;
    }
}

框架

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> list = new ArrayList();
    Stack<TreeNode> stack = new Stack();

    TreeNode curr = root;
    while(curr != null || !stack.isEmpty()) {
        if (curr != null) {
            stack.push(curr);
            // ...
        } else {
            // 按规则操作栈
        }
    }

    return list;
}
posted @ 2022-08-11 17:00  yudoge  阅读(32)  评论(0编辑  收藏  举报