笔记:关于二叉树的遍历

1. 中序遍历

1.1 标记法

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        
        LinkedList<Object> stack = new LinkedList<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            Object top = stack.pop();
            if (top == null) {
                continue;
            }

            // 如果是TreeNode类型,则依次将右节点、当前节点、左节点入栈
            // 注意,推入当前节点时,实际上是将当前节点的val值入栈,表示将当前节点标记为已读
            if (top instanceof TreeNode) {
                TreeNode node = (TreeNode) top;
                stack.push(node.right);
                stack.push(node.val);
                stack.push(node.left);
            
            // 如果是Integer类型,说明该节点已经被展开过了,此时直接将该节点的val值记录下来
            } else {
                list.add((Integer) top);
            }
        }
        return list;
    }
}

1.2 常规方法

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

        // 待处理节点初始化为root
        TreeNode cur = root;
        LinkedList<TreeNode> stack = new LinkedList<>();
        
        while (cur != null || !stack.isEmpty()) {

            // 先将待处理的二叉树最左端路径上的节点入栈
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            
            // 此时的栈顶元素就是中序遍历中真正遍历到的节点
            TreeNode top = stack.pop();
            
            // 具体的处理逻辑
            list.add(top.val);

            // 接下来需要处理原栈顶元素的右子树
            cur = top.right;
        }
        return list;
    }
}

1.3 莫里斯遍历

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

        TreeNode cur = root;
        while (cur != null) {

            // 如果当前节点的左孩子为空,则处理当前节点,然后进入右子树
            if (cur.left == null) {
                
                // 具体的处理逻辑
                list.add(cur.val);
                
                cur = cur.right;
            
            // 如果当前节点的左孩子不为空,这时候需要先进入左子树
            // 但在进入之前,我们需要先想想怎么在遍历完左子树后依然能够回到当前节点
            //     鉴于中序遍历有如下两个特点:
            //     1. 树的最右边的节点总是最后才遍历到
            //     2. 在处理了某个节点后,当前指针会指向该节点的右子节点
            // 因此可以找到左子树最右边的节点(下称前驱结点),将其right指针指向当前节点
            // 这样,在前驱节点处理完后,自然会进入前驱结点的右子树,这样就回到了当前节点
            } else {

                // 在当前节点的左子树中找到最右的节点,遇到null或当前节点就停下来
                TreeNode rightest = cur.left;
                while (rightest.right != null && rightest.right != cur) {
                    rightest = rightest.right;
                }
                
                // 若前驱节点的右孩子为空,说明当前节点的左子树还未被遍历
                // 因此将前驱结点的右孩子设置为当前节点,然后进入左子树
                if (rightest.right == null) {
                    rightest.right = cur;
                    cur = cur.left;
                
                // 若前驱节点的右孩子为当前节点,说明当前节点的左子树已经遍历完了
                // 因此将前驱节点的右孩子置为空,将二叉树恢复原貌
                // 然后处理当前节点,并进入右子树
                } else {
                    rightest.right = null;
                    
                    // 具体的处理逻辑
                    list.add(cur.val);
                    
                    cur = cur.right;
                }
            }
        }

        return list;
    }
}

2. 前序遍历

2.1 标记法

代码和中序遍历基本一样,只需将节点的入栈顺序调整为右节点、左节点、当前节点即可

2.2 常规方法

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
 
        // 待处理节点初始化为root
        TreeNode cur = root;
        LinkedList<TreeNode> stack = new LinkedList<>();
         
        while (cur != null || !stack.isEmpty()) {
 
            // 如果当前节点不为空,则先处理当前节点
            // 处理完成后需要处理左节点;但如果直接左移的话,之后就很难回到右节点了
            // 因此先将右节点入栈(也就是将右节点记录下来),然后再去处理左节点
            while (cur != null) {
                list.add(cur.val);
                stack.push(cur.right);
                cur = cur.left;
            }
             
            // 栈顶元素top是个右节点
            // 执行到这里,说明top的父节点和父节点的左节点都已经被处理完成了
            // 因此接下来需要处理top节点
            cur = stack.pop();
        }
        return list;
    }
}

3. 后序遍历

3.1 标记法

代码和中序遍历基本一样,只需将节点的入栈顺序调整为当前节点、右节点、左节点即可

3.2 常规方法

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
 
        // 待处理节点初始化为root
        TreeNode cur = root;
        LinkedList<TreeNode> stack = new LinkedList<>();
        
        // 记录上次被处理完成的节点
        TreeNode pre = null;
        
        while (cur != null || !stack.isEmpty()) {
 
            // 先将待处理的二叉树最左端路径上的节点入栈
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
             
            // 栈顶元素top是个父节点
            // 执行到这里,说明top的左节点已经被处理完成了
            // 但是我们还不知道top的右节点是否被处理过,因此还需要进一步判断:
            // 1. 如果右节点是空,则相当于右节点已经处理完成
            // 2. 否则,由于后续遍历中,右节点处理完成后会立即处理父节点
            //    因此如果pre是cur的右节点,也能说明cur的右节点已经处理完成
            cur = stack.pop();
            
            // 如果右节点已经处理完成,则接下来需要处理当前节点,并更新pre为当前节点
            // 注意最后要将cur置为null,否则下次循环会误以为当前节点还未被处理过
            if (cur.right == null || cur.right == pre) {
                list.add(cur.val);
                pre = cur;
                cur = null;
            
            // 否则,当前节点还不能被处理,需要重新入栈,然后处理右节点
            } else {
                stack.push(cur);
                cur = cur.right;
            }
        }
        return list;
    }
}

全部评论

相关推荐

不愿透露姓名的神秘牛友
03-05 00:46
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务