【二叉树遍历 Java版】二叉树的前中后序遍历and层次遍历

深度优先遍历

深度优先遍历中,根据左右孩子节点和父节点的访问顺序,分为了前、中、后序遍历。 这里的前中后是指父节点相对左右孩子节点的位置:前序遍历即 父-左-右,中序遍历即 左-父-右, 后序遍历即 左-右-父。
下面一个例子:
在这里插入图片描述
所谓深度遍历,即一个节点下面有子树的话,先遍历左子树(如果有)再遍历右子树(如果有),子树遍历完了,加上这个节点(根据前中后序遍历实际情况考虑这个节点是什么时候访问),这棵树遍历完了,再往上走,回到其父节点……以此类推。

题目链接

144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历

递归

递归的代码非常简洁,一个函数自己调用自己,加上一个递归结束条件,即可。
前中后序的递归结束条件都是当前节点node为空。 注意,这里递归函数不需要返回值。

前序遍历

遇到一个节点,就遍历它,然后遍历左子树,然后遍历右子树,这就是前序遍历的顺序。

class Solution {
    static List<Integer> ans;
    public List<Integer> preorderTraversal(TreeNode root) {
        ans = new ArrayList<>();
        getNode(root);
        return ans;
    }
    public static void getNode(TreeNode root){
        if(root == null)
            return;
        ans.add(root.val); // 遍历节点
        getNode(root.left); // 遍历左子树
        getNode(root.right); // 遍历右子树
    }
}

中序遍历

遇到一个节点,先遍历其左子树,左子树遍历完了,再遍历这个节点,然后再遍历这个树的右子树,这就是中序遍历的顺序。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        return ans;
    }

    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;
        getNode(root.left,ans); // 先遍历左子树
        ans.add(root.val); // 再遍历当前节点
        getNode(root.right,ans); // 再遍历右子树
    }
}

后序遍历

后序遍历是左-右-中,即先遍历左子树、右子树,最后再遍历当前节点。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        return ans;
    }
    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;
        getNode(root.left,ans); // 遍历左子树      
        getNode(root.right,ans); // 遍历右子树
        ans.add(root.val);  // 遍历当前节点
    }
}

这里有个很有意思的点,前序遍历是中-左-右,前序遍历的过程中,如果交换左右子树的遍历顺序,变成中-右-左,然后再reverse反转一下,变成左-右-中,即为后序遍历了。
树的遍历(整体)是子树遍历(局部)组成的,最后整体反转,局部内也是反转的,即子树也是满足左-右-中的。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        Collections.reverse(ans); // 反转
        return ans;
    }

    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;                 
        ans.add(root.val); // 遍历当前节点
        getNode(root.right,ans); // 遍历右子树
        getNode(root.left,ans);  // 遍历左子树
    }
}

迭代

所有递归调用的方法,都能转化成非递归版本,一般使用栈来保存中间状态遍历。在树的遍历中即用栈保存,在路径上但是还没有访问的节点。

前序遍历

前序遍历是遇到一个节点就访问,然后呢? 然后再遍历子树,左子树和右子树,在迭代版本中,就要保存左节点和右节点。注意我们用的栈,栈是先入后出,后入先出,我们要先遍历左子树,那么就要后入栈。栈不空就表示还有节点没有访问。

class Solution {    
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Stack<TreeNode> tree = new Stack<>();
        if(root == null)
            return ans;
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            ans.add(node.val); // 访问当前节点
            if(node.right != null)  // 右孩子先入栈,后出栈
                tree.push(node.right);
            if(node.left != null)   // 左孩子后入栈,先出栈
                tree.push(node.left);
        }
        return ans;
    }    
}

后序遍历

前面提到,前序遍历中,左右子树遍历的顺序交换,遍历结果整体反转就能得到得到后序遍历的结果。

//遍历顺序类似前序,得到 中-右-左, 反转即可得到 左-右-中
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            ans.add(node.val); // 访问当前节点
            if(node.left != null) // 左孩子先入栈,后访问
                tree.push(node.left);
            if(node.right != null) // 右孩子后入栈,先访问
                tree.push(node.right);
        }
        Collections.reverse(ans); // 反转
        return ans;
    }
}

中序遍历

中序遍历第一个节点是什么? 是整棵树最左边的孩子节点,或者整棵树最左边的没有左孩子的节点。那么就意味着,在找到这个节点之前,路上遇到的节点都要保存起来。 且先保存的全是整棵树的左边部分,访问完了根节点的时候,右边节点还没入栈呢~ 因此不能单纯用栈非空为遍历结束的条件。同时用一个当前阶段node来记录是否遍历结束。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        TreeNode node = root.left; // 看左孩子
        while(node != null ||!tree.empty()){
        	// 一直向下,直到没有左孩子,才能遍历这个节点
            if(node != null){
                tree.push(node);// 路过的节点都入栈保存
                node = node.left;
            }else{
                node = tree.pop();
                ans.add(node.val); // 访问当前节点
                node = node.right; // 再访问右子树
            }
        }
        return ans;
    }   
}

统一迭代

上面迭代版本中,前序和后序代码很相似,但是中序非常不一样,还需要用node是否为空辅助判断遍历是否结束。 如果有一个前中后序统一的代码模板,会更方便记忆。
现在不管当前节点是否要访问,都把它放入栈里面,但是依旧要保持前中后序遍历中节点的顺序。 即前序:先存右孩子、再存左孩子、再存自己;中序:先存右孩子、再存自己、再存左孩子;后序:先存自己、再存右孩子、再存左孩子。因为入栈是先入后出,所有存的顺序是和遍历顺序相反的。
然后在当前节点后面存一个null,下次碰到null就知道下一个节点应该访问了。

前序遍历

入栈顺序是 右-左-中-null

class Solution {    
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Stack<TreeNode> tree = new Stack<>();
        if(root == null)
            return ans;
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop(); // 栈顶节点出栈
            if(node != null){ // 不为空考虑左右孩子入栈     
                if(node.right != null)  // 右孩子先入栈,后出栈
                    tree.push(node.right);
                if(node.left != null)   // 左孩子后入栈,先出栈
                    tree.push(node.left);                
                tree.push(node); // 当前节点再入栈
                tree.push(null); // null标记当前节点
            }else{ // 栈顶是null说明前面一个就是要访问的
                node = tree.pop();
                ans.add(node.val);// 访问
            }           
        }
        return ans;
    }    
}

后序遍历

入栈顺序是 中-null-右-左

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.peek(); //注意当前节点不出栈
            if(node != null){
                tree.push(null);// 添加标志null节点
                if(node.right != null)
                    tree.push(node.right);
                if(node.left != null)
                    tree.push(node.left);                
            }else{
                tree.pop();
                node = tree.pop(); // 访问当前节点
                ans.add(node.val);
            }
        }
        return ans;
    }
}

中序遍历

入栈顺序是 右-中-null-左

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            if(node != null){
                if(node.right != null) // 右孩子先入栈
                    tree.push(node.right);
                tree.push(node); // 当前节点入栈
                tree.push(null); // 添加标识
                
                if(node.left != null) // 左孩子入栈
                    tree.push(node.left);
            }else{
                node = tree.pop();
                ans.add(node.val);// 访问节点
            }
        }
        return ans;
    }   
}

广度优先遍历

广度优先遍历即为层次遍历。深度用栈,广度用队列,先入先出。
每一层的节点数量,为当前queue中元素的数量!

102. 二叉树的层序遍历

102. 二叉树的层序遍历

class Solution {
    public List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            List<Integer> levelElements = new ArrayList<Integer>();
            int levelSize = queue.size(); //当前层的节点数量
            for(int i=0;i<levelSize;i++){
                TreeNode node = queue.poll();
                levelElements.add(node.val);
                // 出队后左右孩子依次入队
                if(node.left!= null)
                    queue.offer(node.left);
                if(node.right!= null)
                    queue.offer(node.right);
            }
            // 从上往下访问 直接将层的结果加入ans中
            ans.add(levelElements);
        }
        return ans;
    }
}

107. 二叉树的层序遍历 II

107. 二叉树的层序遍历 II
这个题目只是从最后一层输出,实际遍历的时候还是从上往下,只是每次把访问的那一层放在最前面。


class Solution {
    public List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            List<Integer> levelElements = new ArrayList<Integer>();
            int levelSize = queue.size();
            for(int i=0;i<levelSize;i++){
                TreeNode node = queue.poll();
                levelElements.add(node.val);
                if(node.left!= null)
                    queue.offer(node.left);
                if(node.right!= null)
                    queue.offer(node.right);
            }
            // ❗注意要求从最后一层访问,实际还是从上往下访问,但是每一层的结果都加在最前面
            ans.add(0,levelElements);
        }
        return ans;
    }
}

637. 二叉树的层平均值

637. 二叉树的层平均值

只需要在层次访问的基础上,将各个节点的val求平均。

class Solution {
    public List<Double> ans = new ArrayList<Double>();

    public List<Double> averageOfLevels(TreeNode root) {
        if (root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            TreeNode node = null;
            double sum = 0.0;
            for (int i = 0; i < size; i++) {
                node = queue.poll();
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
                // ❗求节点值的和
                sum += node.val;
            }
            // 将平均值加入ans
            ans.add(sum / size);
        }
        return ans;        
    }
}

199. 二叉树的右视图

199. 二叉树的右视图
从广度优先(即层次遍历)的角度,这个题就是访问每一层时,只将这个层最后一个元素放入ans中。 因此一个二叉树的右视图,看到的就是每一层最右边的元素。

class Solution {
    public List<Integer> ans = new ArrayList<Integer>();
    public List<Integer> rightSideView(TreeNode root) {
        if(root ==  null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            int size = queue.size();
            TreeNode node = null;
            while(size > 0){
                node = queue.poll();
                if(node.left != null)
                    queue.offer(node.left);
                if(node.right != null)
                    queue.offer(node.right);
                // ❗只访问最后一个元素
                if(size == 1)
                    ans.add(node.val);
                size--;
            } 
        }
        return ans;        
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值