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

news2024/10/6 8:35:52

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的结点,以避免死循环

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024第十五届蓝桥杯 JAVA B组 填空题

没参加这次蓝桥杯算法赛&#xff0c;十四届蓝桥杯被狂虐&#xff0c;对算法又爱又恨&#xff0c;爱我会做的题&#xff0c;痛恨我连题都读不懂的题&#x1f62d;,十四届填空只做对一个&#xff0c;今天闲的蛋疼想看看这次比赛能做对几个。 暂时没找到题目&#xff0c;这是网上找…

【Linux】阿里云ECS搭建lnmp和lamp集群

搭建LNMP&#xff08;Linux Nginx MySQL PHP&#xff09;或LAMP&#xff08;Linux Apache MySQL PHP&#xff09;集群 创建ECS实例&#xff1a; 在阿里云控制台创建多个ECS实例&#xff0c;选择相应的操作系统和配置&#xff0c;确保这些实例在同一VPC网络内&#xff0c;…

探索ERC20代币:构建您的第一个去中心化应用

下面文章中会涉及到该资源中的代码&#xff0c;如果想要完整版代码可以私信我获取&#x1f339; 文章目录 概要整体架构流程技术名词解释ERC20智能合约web3.js 技术细节ERC20合约部署创建前端界面前端与智能合约互连运行DAPP 小结 概要 在加密货币世界中&#xff0c;ERC20代币…

<计算机网络自顶向下> P2P应用

纯P2P架构 没有或者极少一直运行的Server&#xff0c;Peer节点间歇上网&#xff0c;每次IP地址都可能变化任意端系统都可以直接通信利用peer的服务能力&#xff0c;可扩展性好例子&#xff1a;文件分发; 流媒体; VoIP类别:两个节点相互上载下载文件&#xff0c;互通有无&#…

【opencv】示例-text_skewness_correction.cpp 校正文本图像的倾斜度

// 此教程展示了如何矫正文本的偏斜。 // 程序接受一个偏斜的源图像作为输入&#xff0c;并显示非偏斜的文本。#include <opencv2/core.hpp> // 包含OpenCV核心功能的头文件 #include <opencv2/imgcodecs.hpp> // 包含OpenCV图像编解码功能的头文件 #include <o…

大模型实战案例:8卡环境微调马斯克开源大模型 Grok-1

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总…

文献阅读:Viv:在 web 上多尺度可视化高分辨率多重生物成像数据

文献介绍 「文献题目」 Viv: multiscale visualization of high-resolution multiplexed bioimaging data on the web 「研究团队」 Nils Gehlenborg&#xff08;美国哈佛医学院&#xff09; 「发表时间」 2022-05-11 「发表期刊」 Nature Methods 「影响因子」 47.9 「DOI…

java设计模式之策略模式实操

一、背景 临床服务项目流向规则匹配&#xff0c;比如说医生开一个“CT”检查&#xff0c;该检查应该由哪个科室来执行&#xff0c;是通过流向规则配置来决定的&#xff0c;具体配置如下图&#xff1a; 通过相关的条件匹配&#xff0c;最终找到流向科室。 二、设计思路 有几个注…

云计算:Linux 部署 OVN 集群

目录 一、实验 1.环境 2.Linux 部署 OVN 集群&#xff08;中心端&#xff09; 3.Linux 部署 OVN 集群&#xff08;业务端1&#xff09; 4.Linux 部署 OVN 集群&#xff08;业务端2&#xff09; 4.OVN 中心端 连接数据库 5.OVN 业务端1 加⼊控制器 6.OVN 业务端2 加⼊控…

AI克隆语音(基于GPT-SoVITS)

概述 使用GPT-SoVITS训练声音模型&#xff0c;实现文本转语音功能。可以模拟出语气&#xff0c;语速。如果数据质量足够高&#xff0c;可以达到非常相似的结果。相比于So-VITS-SVC需要的显卡配置更低&#xff0c;数据集更小&#xff08;我的笔记本NVIDIA GeForce RTX 4050 Lap…

基于LSTM的新闻中文文本分类——基于textCNN与textRNN

构建词语字典 def build_vocab(file_path, tokenizer, max_size, min_freq):# 定义词汇表字典&#xff1a;使用 vocab_dic {} 初始化一个空字典&#xff0c;用于存储每个词及其出现频率vocab_dic {}with open(file_path, r, encodingUTF-8) as f:for line in tqdm(f):lin l…

为什么光伏探勘测绘需要无人机?

随着全球对可再生能源需求的不断增长&#xff0c;光伏产业也迎来了快速发展的机遇。光伏电站作为太阳能发电的主要形式之一&#xff0c;其建设前期的探勘测绘工作至关重要。在这一过程中&#xff0c;无人机技术的应用正逐渐展现出其独特的优势。那么&#xff0c;为什么光伏探勘…

《手机维修600G资料》云盘下载地址

无意中发现一个生财之道&#xff0c;哈哈哈&#xff0c;就是发现有人在一些视频平台&#xff0c;发手机维修之类的视频吸引客户。这样自己就不用开店也可以接生意了。问题剩下就一个了&#xff0c;把手机维修技术学好&#xff0c;一技在手&#xff0c;天上我有。 《手机维修600…

有条件的打破IBGP水平分割----反射规则和联邦+实验举例

背景&#xff1a;在一个AS中的设备运行了BGP协议&#xff0c;那么正常应该都连接了其他的AS&#xff0c;存在EBGP邻居关系&#xff1b;又由于IBGP的水平分割规则&#xff0c;导致从外部学习到的路由传递给本地AS时&#xff0c;需要和本地AS中运行BGP协议都要建立IBGP邻居关系&a…

基于R语言实现的负二项回归模型【理解与实现】-理解负二项回归模型和泊松回归模型之间的区别

前言 我们可以在R语言中使用MASS包中的glm.nb函数来拟合负二项模型&#xff0c;以及使用glm函数来拟合泊松模型。以下是一个详细的过程&#xff0c;包括模拟数据的生成、模型的拟合、结果的比较和解释。 需要的包 if (!require("MASS")) install.packages("M…

ES增强框架easy-es

因为最近做的功能是关于舆情的,所以数据量比较大的,本来打算用MySQL做时间分表来做,但是经过一段时间的测试,发现数据量太大,用时间分表不能满足性能的要求,所以决定将数据存储改为ES,但是短时间内改底层框架又不是一个小工程,时间上不允许,所以找到了一个很合适的框架,他跟myb…

Echarts简单的多表联动效果和添加水印和按钮切换数据效果

多表联动 多表联动效果指的是在多个表格之间建立一种交互关系&#xff0c;以便它们之间的操作或选择能够相互影响。通常情况下&#xff0c;多表联动效果可以通过以下方式之一实现&#xff1a; 数据关联&#xff1a; 当在一个表格中选择或操作某些数据时&#xff0c;另一个表格…

DataGrip连接Docker中的MySQL容器

获取MySQL镜像 通过命令行工具或者docker desktop 命令行&#xff1a; docker pull mysqldocker desktop工具&#xff0c;tag可以指定版本 创建mysql容器 我们知道dockerfile用于编写镜像&#xff0c;dockercompose用于编排容器&#xff0c;所以这里我用dockercompose来创…

eNSP防火墙配置实验(trust、DMZ、untrust)

【拓扑】 设备 接口 IP地址/子网掩码/网关 AR1 G0/0/0 10.1.3.2/24 G0/0/1 100.1.1.2/24 FW1 G0/0/0 192.168.166.254/24 G1/0/0 10.1.1.1/24&#xff0c;trust域 G1/0/1 10.1.2.1/24&#xff0c;DMZ域 G1/0/2 100.1.3.1/24&#xff0c;untrust域 LSW1 G0/0/…

ssm051网上医院预约挂号系统+jsp

网上医院预约挂号系统设计与实现 摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让医院预约挂号信息的管理模式进行升级&#xff0c;也为了更好的维护医院预约挂号信息&#xff0c;网上医院…