[Collection与数据结构] 二叉树(三):二叉树精选OJ例题(下)

1.二叉树的分层遍历

OJ链接
在这里插入图片描述
上面这道题是分层式的层序遍历,每一层有哪些结点都很明确,我们先想一想普通的层序遍历怎么做

/**
     * 层序遍历
     * @param root
     */
    public void levelOrder1(Node root){
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            Node cur = queue.poll();
            System.out.println(cur.value);
            if (cur.left != null){
                queue.offer(cur.left);
            }
            if (cur.right != null){
                queue.offer(cur.right);
            }
        }
    }

这里我们通过申请队列的方式来实现层序遍历.按照从左到右的方式依次把结点依次存入队列中.

整体思路:

  1. 首先把根节点放入队列中,之后让根结点出队列.
  2. 把根结点的左结点和右结点放入队列中.
  3. 第二层放完之后,再把根结点的左结点出队列,把它的左右结点再放入队列中.再把根结点的右结点出队列,把它的左右结点再放入队列中.
  4. 以此类推,这样便可以做到从左到右,从上到下.

下面我们来实现分层操作:

/**
     * 层序遍历(分层)
     * @param root
     * @return
     */
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> lists = new ArrayList<>();
        Queue<Node> queue = new LinkedList<>();
        if (root == null){//root为空直接返回空列表
            return lists;
        }
        queue.offer(root);//把根节点放进去
        while (!queue.isEmpty()){//队列不为空继续给lists中添加元素
            int size = queue.size();//获取队列大小,就是该层元素的大小
            List<Integer> list = new ArrayList<>();//每一层都有一个List
            while (size != 0){//当size--为0的时候,证明这一层添加完成
                Node cur = queue.poll();
                list.add(cur.value);//忽略警告,下面的两个条件会把cur限制住
                if (cur.left != null){//左子树不为空,把根的左结点放进来
                    queue.offer(cur.left);
                }
                if (cur.right != null){
                    queue.offer(cur.right);//右子树不为空,把根的右结点放进来
                }
                size--;
            }
            lists.add(list);//每一层遍历完都添加到lists中
        }
        return lists;
    }

技巧性:

这里使用了size这个巧妙的变量,用来记录当前队列的大小.我们每一层都会申请一个list,把当前层数中的结点全部放入该list中,当队列的size–为0 ,说明该层的结点遍历完成,把该层的list放入总的lists中.

子问题:判断一棵树是否是完全二叉树

/**
     * 判断一棵树是否是完全二叉树
     * @param root
     * @return
     */
    public boolean isCompeteTree(Node root){
        Queue<Node> queue = new LinkedList<>();//申请队列
        queue.offer(root);//把根节点放入
        Node cur = queue.poll();//弹出根节点赋给cur
        while (cur != null){
            queue.offer(cur.left);
            queue.offer(cur.right);
            cur = queue.poll();//这里需要注意的是,根结点的左右子树如果为null也要放入
        }
        while(!queue.isEmpty()){
            Node cur1 = queue.poll();
            if (cur1 != null){
                return false;//让元素出队列,如果出的过程中遇到不是空的情况,说明不是完全二叉树
            }
        }
        return true;
    }

整体思路:
我们下面只介绍与上面问题不同的地方

  1. 这道题和上面的问题有所不同的一个点就是,即使遍历到最后,cur的左右仍然为空,也要放入队列中.
  2. 在遍历完成之后出队列的时候,如果在出到不等于null的元素,即在层序遍历完成之后,队列中仍然存在元素,此时就不是完全二叉树,否者为完全二叉树.

2. 最近公共祖先

OJ链接
在这里插入图片描述

/**
     * 寻找公共祖先,分为root是p,q中的一个,在root同侧,在root异侧
     * @param root
     * @param p
     * @param q
     * @return
     */
    public Node lowestCommonAncestor(Node root, Node p, Node q) {
        if (root == null){
            return null;//如果是空树返回null
        }
        if (root == p || root == q){
            return root;//如果p或者q中有一个是根结点,那么根结点就是公共祖先
        }
        Node leftnode = lowestCommonAncestor(root.left,p,q);//向左子树递归
        Node rightnode = lowestCommonAncestor(root.right,p,q);//向右子树递归
        if (leftnode != null && rightnode != null){
            return root;//如果左子树和右子树返回的都不是null,说明p,q在root两侧,root是公共祖先
        } else if (leftnode == null) {
            return rightnode;//如果左子树返回null,说明p,q同在root右侧,返回右侧先找到的结点
        }else {
            return leftnode;//同理
        }
    }

这道题分为这几种情况:

  1. p,q其中有一个是根结点,直接返回root.
  2. p,q在根结点的异侧,则在返回的时候,根结点左子树和右子树返回的都不是空,这种情况返回的就是root.
  3. p,q在根结点的同侧,根结点左子树或者右子树有其中一个返回空,那么它们的子树中又有可能是上面的1或者2中的情况,返回子树得到的返回值即可.

3. 中序前序遍历构造二叉树

OJ链接
在这里插入图片描述

/**
     * 中序前序遍历构造二叉树
     * @param preorder 前序遍历
     * @param inorder 中序遍历
     * @return
     */
    public int preindex = 0;//注意遍历前序数组的时候,要设置为成员变量
    public Node buildTree(int[] preorder, int[] inorder) {
        return buildTreeChild(preorder,inorder,0,inorder.length-1);
    }
    private Node buildTreeChild (int[] preorder, int[] inorder, int ibegin ,int iend){
        if (ibegin > iend){
            return null;//遍历中序数组,遍历到头大于尾的时候,返回null
        }
        int inorderindex = findIndex(inorder,preorder[preindex],ibegin,iend);//在中序数组中寻找前序遍历到的字符串
        //找到的即为树的根结点
        preindex ++;//向后遍历前序数组
        Node node = new Node(inorder[inorderindex]);//创建结点
        Node leftnode = buildTreeChild(preorder,inorder,ibegin,inorderindex-1);
        node.left = leftnode;//向中序数组的左遍历,构建左子树
        Node rightnode = buildTreeChild(preorder,inorder,inorderindex+1,iend);
        node.right = rightnode;//向中序数组的右遍历,构建右子树
        return node;
    }
    private int findIndex (int[] inorder, int order,int ibegin, int iend){
        for (int i = ibegin; i <= iend; i++) {
            if (inorder[i] == order){
                return i;
            }
        }
        return -1;
    }

整体思路:

  1. 由于前序遍历可以决定根节点,所以我们要拿着前序遍历数组的元素在中序遍历数组的的元素中寻找对应元素,即根结点,并创建根结点.并找到中序遍历数组中对应的下标位置.(inorderindex)
  2. 之后创建左子树,向下递归,在0~inorderindex-1的范围之中创建左子树.
  3. 之后创建右子树,向下递归,在inorderindex+1~inorder.length-1的范围之中创建右子树.

注意:
遍历前序数组的preindex应该设置为成员变量,否者在每次递归的时候preindex不会向下走,又会返回原来的值.

4. 后序遍历和中序遍历构建二叉树

OJ链接
在这里插入图片描述

/**
     * 后序遍历和中序遍历构建二叉树
     * @param inorder
     * @param postorder
     * @return
     */
    public int postindex = 0;
    public Node buildTree2(int[] inorder, int[] postorder) {
        postindex = postorder.length-1;
        return buildTreeChild2(inorder,postorder,0,inorder.length-1);
    }
    private Node buildTreeChild2(int[] inoder, int[] postorder,int ibegin,int iend){
        if (ibegin > iend){
            return null;
        }
        int inorderindex = findIndex2(inoder,postorder[postindex],ibegin,iend);
        Node node = new Node(inoder[inorderindex]);
        postindex--;
        Node rightnode = buildTreeChild2(inoder,postorder,inorderindex+1,iend);
        node.right = rightnode;
        Node leftnode = buildTreeChild2(inoder,postorder,ibegin,inorderindex-1);
        node.left = leftnode;//由于后序遍历数组的顺序为左右根,所以在从后向前遍历的时候,遍历完根之后是右
        //所以要先构建右子树,再构建左子树
        return node;
    }
    private int findIndex2(int[] inorder,int order,int ibegin,int iend){
        for (int i = ibegin; i <= iend; i++) {
            if (order == inorder[i]){
                return i;
            }
        }
        return -1;
    }

注意:

  1. 与前序遍历不同的是,这里遍历后序数组的变量为postindex,大小为postorder.length-1,在途中让postindex- -.
  2. 子树先构建右子树,再构建左子树,因为:由于后序遍历数组的顺序为左右根,所以在从后向前遍历的时候,遍历完根之后是右,所以要先构建右子树,再构建左子树.

5. 根据二叉树创建字符串(采用前序遍历)

OJ链接
在这里插入图片描述

/**
     * 根据二叉树创建字符串,采用前序遍历
     * @param root
     * @return
     */
    StringBuilder stringBuilder = new StringBuilder();//创建stringbuilder
    public String tree2str(Node root) {
        if (root == null){
            return null;//空树返回null
        }
        stringBuilder.append(root.value);//添加根结点的值
        if (root.left != null){
            stringBuilder.append("(");
            tree2str(root.left);//添加左结点
            stringBuilder.append(")");
        }else {
            if (root.right != null){
                stringBuilder.append("()");//如果左结点为空,看右结点是否为空,如果不为空,添加()
            }
        }
        if (root.right != null){//添加右结点
            stringBuilder.append("(");
            tree2str(root.right);
            stringBuilder.append(")");
        }
        return stringBuilder.toString();
    }

整体思路:

  1. 创建StringBuilder对象.
  2. 先把根结点放入,之后向左子树遍历.
  3. 如果左结点不为空,键入括号,并键入左结点的值.
  4. 如果左结点为空,又分为两种情况:右结点为空,直接什么都不做,右结点不为空,键入空的括号.
  5. 向右子树遍历.
  6. 构建好StringBilder对象之后,使用toString方法转为String.

6. 二叉树的非递归前序遍历

OJ链接
在这里插入图片描述

/**
     * 二叉树的非递归前序遍历 ->借助栈
     * @param root
     * @return
     */
    public List<Integer> preorderTraversal(Node root) {
        List<Integer> list = new ArrayList<>();//创建顺序表
        if (root == null){
            return list;//如果为空树,返回空顺序表
        }
        Stack<Node> stack = new Stack<>();//创建栈,使用栈代替递归
        Node cur = root;//cur指向根
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {//cur遍历到空
                stack.push(cur);//把根结点放入栈中,用入栈的方式代替递归中递的过程
                list.add(cur.value);//并放入顺序表,完成了前序遍历的第一步,根
                cur = cur.left;//向左遍历,完成了前序遍历的第二部,左
                //循环回去又是下一个根,有到了前序遍历的第一步
            }
            Node top = stack.pop();//开始返回,用出栈的方式代替递归的归的过程
            cur = top.right;//cur遍历到右结点,循环回去完成了前序遍历的第三部,右
        }
        return list;
    }

整体思路:

  1. 该题需要借助栈来代替递归的过程.
  2. 先使得cur指向root.
  3. 把cur放入栈中,并放入list中,之后cur向左结点走,依次都把向左走的结点放入栈中.
  4. 在出栈的时候,top得到出栈元素,拿着出栈元素找到top结点的右结点,之后依次循环3,4两步.

注意:

  1. 在top拿到出栈元素之后,如果没有外层循环,该结点右结点就无法执行思路中的第三步.
  2. 出栈的时候,栈有可能为空,所以要添加!stack.isEmpty().

7. 二叉树的非递归中序遍历

在这里插入图片描述

/**
     * 二叉树的非递归中序遍历 ->借助栈
     * @param root
     * @return
     */
    public List<Integer> inorderTraversal(Node root) {
        List<Integer> list = new ArrayList<>();//创建顺序表
        if (root == null){
            return list;//如果为空树,返回空顺序表
        }
        Stack<Node> stack = new Stack<>();//创建栈,使用栈代替递归
        Node cur = root;//cur指向根
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {//cur遍历到空
                stack.push(cur);//把根结点放入栈中,用入栈的方式代替递归中递的过程
                cur = cur.left;//向左遍历
                //循环回去,让左结点入栈
            }
            Node top = stack.pop();//开始返回,用出栈的方式代替递归的归的过程
            list.add(top.value);//为顺序表添加结点
            cur = top.right;//cur遍历到右结点
            //循环回去,可能遍历到最后,top的右节点是空,但是栈不为空,走下来就可以把根节点放入list中
        }
        return list;
    }

与前序的不同点:

  1. 在左结点入栈的时候,不入list
  2. 在最后出栈的时候先把左结点放入list中,最后cur的右为null,但是栈不为空,所以可以进入下一层循环,就可以得到原来cur上一层的根节点.

8. 二叉树的非递归后序遍历

OJ链接
在这里插入图片描述

public List<Integer> postorderTraversal(Node root) {
        List<Integer> list = new ArrayList<>();
        if (root == null) {
            return list;
        }
        Stack<Node> stack = new Stack<>();
        Node cur = root;
        Node prev = null;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            Node top = stack.peek();
            if (top.right == null || top.right == prev) {
                stack.pop();
                list.add(top.value);
                prev = top;
            } else {
                cur = top.right;
            }
        }
        return list;
    }

注意:
在走到最后的时候,会出现死循环,所以我们要使用prev来记录最近存入list的结点,以避免死循环

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/553774.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中文编程入门(Lua5.4.6中文版)第十二章 Lua 协程 参考《愿神》游戏

在《愿神》的提瓦特大陆上&#xff0c;每一位冒险者都拥有自己的独特力量——“神之眼”&#xff0c;他们借助元素之力探索广袤的世界&#xff0c;解决谜题&#xff0c;战胜敌人。而在提瓦特的科技树中&#xff0c;存在着一项名为“协同程序”的高级秘术&#xff0c;它使冒险者…

今天刷两题(day2)

题目一&#xff1a;最长公共前缀 题目描述&#xff1a; 给你一个大小为 n的字符串数组 strs &#xff0c;其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀&#xff0c;返回这个公共前缀。输入输出描述&#xff1a; 输入&#xff1a;"abca","…

照片光晕光学特效模拟调色Boris FX Optics 2024 mac下载安装教程

Boris FX Optics 2024 Mac版是一款照片光晕光学特效模拟调色软件&#xff0c;旨在模拟光学相机滤镜&#xff0c;专用镜头&#xff0c;胶卷和颗粒&#xff0c;镜头光晕&#xff0c;光学实验室处理&#xff0c;色彩校正以及自然光和摄影效果。用户可以通过应用光学并从160个滤镜和…

Day43:LeedCode 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果…

图书管理系统概述

自友图书馆管理系统解决方案适用于中小学、大中专院校以及企事业单位中小型图书馆的自动化管理需求&#xff0c;其功能覆盖了图书馆自动化集成管理业务流程所包括的所有环节。《图书馆管理系统》首先应该按照我国图书馆行业通用CNMARC格式及《中图法第四版》行业标准开发而成,支…

计算机网络-IS-IS基础概念二

前面已经学习了IS-IS的定义、组成、NET地址标识以及路由器级别分类等&#xff0c;今天继续学习IS-IS基础概念知识。 参考链接&#xff1a;IS-IS路由协议基础概念 一、IS-IS支持的网络类型 IS-IS会自动根据接口的数据链路层封装决定该接口的缺省网络类型&#xff0c; IS-IS支持两…

【Go语言快速上手(二)】 分支与循环函数讲解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Go语言专栏⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Go语言知识   &#x1f51d;&#x1f51d; Go快速上手 1. 前言2. 分支与循环2.1…

中国隧道空间分布

中国隧道空间分布数据&#xff0c;包含2020年全国大部分地区16000余条隧道分布点位数据&#xff0c;数据包括市名称、区县名称、隧道名称和隧道经纬度。数据包含shp和EXCEl两种格式&#xff0c;部分隧道空间位置有偏移。 欢迎大家关注、收藏和留言&#xff0c;如果您想要什么数…

【GPT-4最新研究】GPT-4与科学探索:揭秘语言模型在科学领域的无限可能

各位朋友们&#xff0c;你们知道吗&#xff1f;自然语言处理领域最近取得了巨大的突破&#xff01;大型语言模型&#xff08;LLM&#xff09;的出现&#xff0c;简直就像打开了新世界的大门。它们不仅在语言理解、生成和翻译方面表现出色&#xff0c;还能涉足许多其他领域&…

网站创建的流程是什么

网站的创建过程包括几个主要的步骤&#xff0c;其中涉及到一系列的决策和实践操作。下面我将详细介绍网站创建的流程&#xff0c;帮助读者了解如何创建一个成功的网站。 第一步&#xff1a;确定网站目标和功能 在创建网站之前&#xff0c;你需要明确自己网站的目标和功能。是用…

【Proteus】51单片机对直流电机的控制

直流电机&#xff1a;输出或输入为直流电能的旋转电机。能实现直流电能和机械能互相转换的电机。把它作电动机运行时是直流电动机&#xff0c;电能转换为机械能&#xff1b;作发电机运行时是直流发电机&#xff0c;机 械能转换为电能。 直流电机的控制&#xff1a; 1、方向控制…

详细介绍医用PSA变压吸附制氧机设备的工艺特点

随着技术的不断进步&#xff0c;医用氧气作为一种重要的治疗资源&#xff0c;其供应方式也在不断地改进和升级。其中&#xff0c;医用PSA(Pressure Swing Adsorption&#xff0c;变压吸附)变压吸附制氧机设备因其高效、安全、稳定的特点&#xff0c;受到了广大机构的青睐。那么…

Biome 1.7 发布,支持从 ESLint 和 Prettier 迁移

近日&#xff0c;Biome v1.7 正式发布&#xff01;这个新版本提供了从 ESLint 和 Prettier 迁移的简单路径。它还引入了格式化程序和 linter 的实验性机器可读报告、新的 linter 规则和许多修复。 使用以下命令更新 Biome&#xff1a; npm install --save-dev --save-exact b…

DSPE-PEG-TPP 磷脂聚乙二醇-磷酸三苯酯 靶向线粒体纳米颗粒药物递送系统

DSPE-PEG-TPP 磷脂聚乙二醇-磷酸三苯酯 靶向线粒体纳米颗粒药物递送系统 【中文名称】磷脂-聚乙二醇-磷酸三苯酯 【英文名称】DSPE-PEG-TPP 【结 构】 【品 牌】碳水科技&#xff08;Tanshtech&#xff09; 【纯 度】95%以上 【保 存】-20℃ 【规 格】50mg,…

windows11 wsl2 ubuntu20.04安装vision mamba并进行测试

windows11 wsl2 ubuntu20.04安装vision mamba 安装流程使用cifar-100测试安装成功 安装流程 vision mamba安装了半天才跑通&#xff0c;记录一下流程在wsl上安装cuda wget https://developer.download.nvidia.cn/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05…

浅析ARM Contex-CM3内核架构

目录 概述 1. Cortex-M3类型MCU 1.1 MCU 架构 1.2 实时性系统概念 1.3 处理器命名法 1.4 MCU的一些知识 2. Cortex-M3 概览 2.1 Cortex-M3综述 2.2 寄存器组 2.3 操作模式和特权极别 2.4 内建的嵌套向量中断控制器 2.5 存储器映射 2.6 总线接口 2.7 存储器保护单元…

Spring Boot 目前还是最先进的吗?

当谈到现代Java开发框架时&#xff0c;Spring Boot一直处于领先地位。它目前不仅是最先进的&#xff0c;而且在Java生态系统中拥有着巨大的影响力。 1. 什么是Spring Boot&#xff1f; Spring Boot是由Spring团队开发的开源框架&#xff0c;旨在简化基于Spring的应用程序的开…

L1-8 刮刮彩票

“刮刮彩票”是一款网络游戏里面的一个小游戏。如图所示&#xff1a; 每次游戏玩家会拿到一张彩票&#xff0c;上面会有 9 个数字&#xff0c;分别为数字 1 到数字 9&#xff0c;数字各不重复&#xff0c;并以 33 的“九宫格”形式排布在彩票上。 在游戏开始时能看见一个位置上…

sql篇-内连接-左连接-右连接

内连接&#xff1a;表1 inner join 表2 on 条件 inner join join&#xff08;简写&#xff09; 查找&#xff1a;满足 匹配两个表条件的记录&#xff1a;student.s_id s.s_id(不匹配的记录不筛选) select * from student inner join score s on student.s_id s.s_id; 查询…

mpeg4标准与 H264标准下QP值间 关系

1 标准对比 MPEG(mpeg1,mpeg2,mpeg4) 与H264 QP值间 关系。 x264vfw 的1pass 是按照 I q:21 P q:24 B q:26 的量化算的,而且在vfw里面不能改变这些参数. 但在mencoder里则可以定义1pass的 qp_constant<1−51> 这个和xvid不同的,xvid一般是用q2跑1pass的,当然你也可以在x2…