【二叉树第三弹】— 使用迭代实现遍历及解题

本文详细介绍了如何使用迭代方法解决二叉树的各种问题,包括层序遍历、节点计数、叶子节点计数、对称性和完全二叉树的判断以及最大宽度计算。通过对比迭代和递归的实现,展示了迭代解题的优势,提供了完整的Java代码实现。
摘要由CSDN通过智能技术生成

往期传送门:

🌸【二叉树第一弹】基础知识详解及遍历代码实现

🌸【二叉树第二弹】— 基础面试题

目录

初识迭代

利用迭代解题

使用迭代实现层序遍历—力扣102题

计算二叉树的节点个数

计算叶子节点个数

判断是否为对称二叉树—力扣101题

判断一棵树是否是完全二叉树—力扣958题

求二叉树最大宽度—力扣662题

前中后序的迭代实现

1.前序遍历—力扣144题

2.中序遍历—力扣94题

3.后序遍历—力扣145题


初识迭代

什么是迭代?迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。换句话说就是不断的用变量的旧值推出新值的过程。和递归有很大的区别。

下面各题都是是利用迭代的方式来解题,很多是我们前面使用递归方式实现过的,小伙伴们可以对比看看哪个方法更适合你哟

利用迭代解题

使用迭代实现层序遍历—力扣102题

我们借助队列来操作,先将根节点入队

323ab7641ca6421b8dd735773373f5a2.png

 当队列为空时,所有节点就处理完毕了,队列中保存的都是下一层要处理的元素

95011df143a44fcabffbeceb6071cc2e.png注意!每次进入内循环之前,要先计算队列的长度,因为在将节点的左右节点入队时,队列的长度是发生变化的

e803a95821924162a30e58d4f377f259.png

代码实现:

public class Num102 {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        //借助队列实现遍历
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            //保存当前层的元素
            List<Integer> curlist = new ArrayList<>();
            //开始遍历取出当前层的所有元素添加到 curlist中
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                curlist.add(cur.val);
                if(cur.left != null){
                    queue.offer(cur.left);
                }
                if(cur.right != null){
                    queue.offer(cur.right);
                }
            }
            ret.add(curlist);
        }
        return ret;
    }
}

计算二叉树的节点个数

    //使用非递归找节点个数
    public static int getNodesNonRecursion(TreeNode root){
        if(root == null){
            return 0;
        }
        int count = 0;
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        while (!deque.isEmpty()){
            TreeNode cur = deque.poll();
            count ++;
            if(cur.right != null){
                deque.offer(cur.right);
            }
            if(cur.left != null){
                deque.offer(cur.left);
            }
        }
        return count;
    }

计算叶子节点个数

 //使用非递归计算叶子节点个数
    public static int getleafNodesNC(TreeNode root){
        if(root == null){
            return 0;
        }
        int ret = 0;
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        while (!deque.isEmpty()){
            TreeNode cur = deque.poll();
            if(cur.left == null && cur.right == null){
                ret ++;
                continue;
            }
            if(cur.right != null){
                deque.offer(cur.right);
            }
            if(cur.left != null){
                deque.offer(cur.left);
            }
        }
        return ret;
    }

判断是否为对称二叉树—力扣101题

       public boolean isSymmetric(TreeNode root) {
          if(root == null){
              return true;
          }
          Deque<TreeNode> deque = new  LinkedList<>();
          deque.offer(root.left);
          deque.offer(root.right);
          while (!deque.isEmpty()){
              //一次取出两个节点,这两个节点就是两颗子树的根节点
              TreeNode t1 = deque.poll();
              TreeNode t2 = deque.poll();
              if(t1 == null && t2 == null){
                  //左右两棵树都为空,继续后面的判断
                  continue;
              }
              if(t1 == null || t2 == null){
                  //只有一个为空
                  return false;
              }
              if(t1.val != t2.val){
                  return false;
              }
              //左右两棵树都不为空,且节点值相同
              //继续判断 t1.left 和 t2.right 以及 t1.right 和 t2.left
              deque.offer(t1.left);
              deque.offer(t2.right);
              deque.offer(t1.right);
              deque.offer(t2.left);
          }
          return true;
      }
}

判断一棵树是否是完全二叉树—力扣958题

我们知道,完全二叉树的叶子节点只出现在最下层或者次下次,当出现第一个叶子节点,或者第一个只有左子树没有右子树的节点时,该节点后面的所有节点都是叶子节点

因此,我们使用层序遍历,并且可以引入一个标记位,来区分二叉树的两种状态:

  1. 状态一:每个节点都有左右子树
  2. 状态二:每个节点都是叶子节点

切换状态的条件:

  • 当碰到第一个只有左树没有右树的节点时
  • 当碰到第一个叶子节点时

dc67642fe4b3423baed579a09a80fc88.png

 代码实现:

public class Num958 {
    public boolean isCompleteTree(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        //标记位,用来判断当前的状态
        boolean isSecondStep = false;
        while (!deque.isEmpty()){
            TreeNode cur = deque.poll();
            if(!isSecondStep){
                //第一种状态
                if(cur.left == null && cur.right != null){
                    //只有右孩子,错误
                    return false;
                } else if(cur.left != null && cur.right != null) {
                    deque.offer(cur.left);
                    deque.offer(cur.right);
                } else if(cur.left != null ) {
                    //只有左孩子,没有右孩子
                    isSecondStep = true;
                    deque.offer(cur.left);
                }else {
                    //此时既没有左孩子,也没有右孩子,既叶子节点
                    isSecondStep = true;
                }
            }else {
                //此时为第二状态
                if(cur.left != null || cur.right != null){
                    return false;
                }
            }
        }
        return true;
    }
}

求二叉树最大宽度—力扣662题

力扣https://leetcode-cn.com/problems/maximum-width-of-binary-tree/每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。

d9d273aa91df4145ba37bce6eae99c97.png

 思路:我们借助完全二叉树的编号来做题,若根节点从1开始编号,父节点编号为K,则左孩子的编号为 2K,右孩子的编号为 2K + 1

0a2948dd27064d74a3e7b73ff4950ed1.png

 可以得出结论:每层宽度恰好等于该层最右侧节点编号 - 最左侧节点编号 + 1

代码实现:

public class Num662 {
    public int widthOfBinaryTree(TreeNode root) {
        if(root == null){
            return 0;
        }
        int maxWidth = 0;
        Queue<NodeNum> queue = new LinkedList<>();
        queue.offer(new NodeNum(root,1));
        while (!queue.isEmpty()){
            //当前层宽度
            int levelWidth = 0;
            //当前层元素个数
            int size =queue.size();
            //最左侧节点编号
            int L = 0;
            //最右侧节点编号
            int R = 0;
            for (int i = 0; i < size; i++) {
                NodeNum cur = queue.poll();
                if(i == 0){
                    //node就是最左侧节点
                    L = cur.num;
                }
                if(i == size - 1){
                    //node就是最右侧节点
                    R = cur.num;
                }
                if(cur.node.left != null){
                    queue.offer(new NodeNum(cur.node.left,cur.num * 2));
                }
                if(cur.node.right != null){
                    queue.offer(new NodeNum(cur.node.right,cur.num * 2 +1));
                }
            }
            levelWidth = R - L +1;
            maxWidth = Math.max(maxWidth,levelWidth);
        }
        return maxWidth;
    }
    //此时我们在层序遍历中,会存储每个出现的节点和它对应的编号
    //因此我们创建一个类,将TreeNode二次包装,新增一个节点的编号
    private class NodeNum{
        TreeNode node;
        //节点编号
        int num;
        public NodeNum(TreeNode node,int num){
            this.node = node;
            this.num = num;
        }
    }
}

前中后序的迭代实现

1.前序遍历—力扣144题

前序遍历是根左右,借助栈来实现,先将根节点压入栈中,由于栈是先进后出,所以先压入右子树,再压入左子树。

     public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        Deque<TreeNode> stack = new ArrayDeque<>();
        stack.push(root);
       while ((!stack.isEmpty())){
            TreeNode cur = stack.pop();
            //先访问根节点
            ret.add(cur.val);
            //先压入右孩子
            if(cur.right != null){
                stack.push(cur.right);
            }
            //再压入左孩子
            if(cur.left != null){
                stack.push(cur.left);
            }
        }
        return ret;
     }

2.中序遍历—力扣94题

中序遍历是左根右,如图搭配代码展示了元素入栈出栈的过程,要理解,元素出栈是作为“根节点”出栈的,意思就是当访问它和它的左树之后,再次回头访问这个“根节点”时,这个节点才输出,也就是访问两次才输出

de160bbc56e44b83acf95ad4978c149f.png

 代码实现:

public class Num94_NonRrecurion {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        //当前走到的节点
        TreeNode cur = root;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (cur != null || !stack.isEmpty()){
            //一路向左走到底
            while (cur != null){
                stack.push(cur);
                cur = cur.left;
            }
            //此时栈顶就是最左侧的节点
            cur = stack.pop();
            ret.add(cur.val);
            //继续访问右子树
            cur = cur.right;
        }
        return ret;
    }
}

3.后序遍历—力扣145题

后序遍历是左根右,和中序遍历类似,元素出栈是作为“根节点”出栈的,和中序遍历不同的是,后序遍历元素要三次访问“根节点”时才能输出。

代码实现:

public class Num145 {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        if(root == null){
            return ret;
        }
        TreeNode cur = root;
        //上一个完全处理过的节点,也就说左右根全部处理完毕的节点
        TreeNode prev = null;
        Deque<TreeNode> stack = new ArrayDeque<>();
        while (!stack.isEmpty() || cur != null){
            //先一路向左走到底
            while (cur != null){
                stack.push(cur);
                cur = cur.left;
            }
            //此时左树为空,cur取出栈顶元素,第二次访问
            cur = stack.pop();
            //判断右树是否为空或者被访问过
            if(cur.right == null || prev == cur.right){
                ret.add(cur.val);
                //当前节点cur就是最后处理的根节点,更新prev引用,变为cur
                prev = cur;
                cur = null;
            }else {
                //此时右树不为空且没有处理过,就需要把根节点再压入栈中,继续处理右子树
                stack.push(cur);
                cur = cur.right;
            }
        }
        return ret;
    }
}

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值