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<>();//每一层都有一个Listwhile (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();//弹出根节点赋给curwhile (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();//创建stringbuilderpublic 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.mzph.cn/news/816311.shtml

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

相关文章

Spring Boot(二)— 自定义Spring Boot Starter

在Spring Boot中&#xff0c;自定义Spring Boot Starter是一个常见且强大的功能&#xff0c;它允许开发者为特定的功能或库创建自己的自动配置&#xff0c;从而简化集成过程。 1 前置知识 Spring Boot的事件为应用的启动和关闭提供了详细的上下文信息&#xff0c;使得开发者能…

基于表面势的增强型p-GaN HEMT器件模型

来源&#xff1a;电子学报 22年 摘要 为了满足功率电路及系统设计对p-GaN HEMT&#xff08;High Electron Mobility Transistor&#xff09;器件模型的需求&#xff0c;本文建立了一套基于表面势计算方法的增强型p-GaN HEMT器件SPICE&#xff08;Simulation Program with Int…

Golang | Leetcode Golang题解之第27题移除元素

题目&#xff1a; 题解&#xff1a; func removeElement(nums []int, val int) int {left, right : 0, len(nums)for left < right {if nums[left] val {nums[left] nums[right-1]right--} else {left}}return left }

软件杯 深度学习卷积神经网络垃圾分类系统 - 深度学习 神经网络 图像识别 垃圾分类 算法 小程序

文章目录 0 简介1 背景意义2 数据集3 数据探索4 数据增广(数据集补充)5 垃圾图像分类5.1 迁移学习5.1.1 什么是迁移学习&#xff1f;5.1.2 为什么要迁移学习&#xff1f; 5.2 模型选择5.3 训练环境5.3.1 硬件配置5.3.2 软件配置 5.4 训练过程5.5 模型分类效果(PC端) 6 构建垃圾…

Eland上传bge-large-zh-v1.5向量化模型到ElasticSearch中

最近需要做一些向量检索&#xff0c;试试ES 一、准备 系统&#xff1a;MacOS 14.3.1 ElasticSearch&#xff1a;8.13.2 Kibana&#xff1a;8.13.2 本地单机环境&#xff0c;无集群&#xff0c;也不基于Docker BGE是一个常见的文本转向量的模型&#xff0c;在很多大模型RAG应…

数据仓库—维度建模—维度表设计

维度表 维度表(Dimension Table)是数据仓库中描述业务过程中各种维度信息的表,用于提供上下文和描述性信息,以丰富事实数据的分析 维度表是维度建模的灵魂所在,在维度表设计中碰到的问题(比如维度变化、维度层次、维度一致性、维度整合和拆分等)都会直接关系到维度建模…

防汛物资仓库管理系统|实现应急物资仓库三维可视化

系统概述 智慧应急物资仓库可视化系统&#xff08;智物资DW-S300&#xff09;采用了 B/S 架构的设计&#xff0c;通过浏览器即可快速登录操作。实现对库房内的应急物资从申购入库、出库、调拨、库内环境监测、维修保养、检测试验、处置报废等全周期、科学、规范的管理。系统以…

ssh爆破服务器的ip-疑似肉鸡

最近发现自己的ssh一直有一些人企图使用ssh暴力破解的方式进行密码破解.就查看了一下,真是网络安全太可怕了. 大家自己的服务器密码还是要设置好,管好,做好最基本的安全措施,不然最后只能沦为肉鸡. ssh登陆日志可以在/var/log下看到,ubuntu的话为auth.log,centos为secure文件 查…

45.HarmonyOS鸿蒙系统 App(ArkUI)创建列表(List)

列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、音乐列…

ChatGPT 和 Elasticsearch:使用 Elastic 数据创建自定义 GPT

作者&#xff1a;Sandra Gonzales ChatGPT Plus 订阅者现在有机会创建他们自己的定制版 ChatGPT&#xff0c;称为 GPT&#xff0c;这替代了之前博客文章中讨论的插件。基于本系列的第一部分的基础 —— 我们深入探讨了在 Elastic Cloud 中设置 Elasticsearch 数据和创建向量嵌…

软件无线电安全之HackRF One初探

HackRF介绍 HackRF是一款开源软件无线电&#xff08;SDR&#xff09;平台&#xff0c;由Great Scott Gadgets公司推出。它具有广泛的频率覆盖范围&#xff0c;从1 MHz到6 GHz&#xff0c;支持大部分常见的无线通信频段。采用软件定义无线电技术&#xff0c;HackRF提供了自定义…

C语言 函数——断言与防御式编程

目录 如何确定假设的真假&#xff1f; 断言 防御式编程&#xff08;Defensive programming&#xff09; 如何确定假设的真假&#xff1f; 程序中的假设 *某个特定点的某个表达式的值一定为真 *某个特定点的某个表达式的值一定位于某个区间等 问题&#xff1a;如何确定这些…

嵌入式单片机 TTL电平、232电平、485电平的区别和联系

一、简介 TTL、232和485是常见的串口通信标准&#xff0c;它们在电平和通信方式上有所不同&#xff0c; ①一般情况下TTL电平应用于单片机外设&#xff0c;属于MCU/CPU等片外外设&#xff1b; ②232/485电平应用于产品整体对外的接口&#xff0c;一般是片外TTL串口转232/485…

五、Jenkins、Docker、SpringClound持续集成

Jenkins、Docker、SpringClound持续集成 一、部署介绍1.部署图2.微服务项目结构3.项目启动顺序 二、微服务项目在Windows运行1.配置java、maven环境2.初始化数据库表/数据2.1 tensquare_gathering服务表2.2 tensquare_gathering服务表 3.启动微服务4.微服务接口测试4.1 获取用户…

黑马苍穹外卖--再来一单(stream流转换、赋值与收集映射)

1.首先明确一下业务规则: 业务规则&#xff1a; 再来一单就是将原订单中的商品重新加入到购物车中 2.产品页面原型和开发接口文档 3.业务层逻辑代码开发 3.1 查询方向 我们要明确的是: 再来一单就是将原订单中的商品重新加入到购物车中------直接把商品加入到购物车&#…

prompt 工程整理(未完、持续更新)

工作期间会将阅读的论文、一些个人的理解整理到个人的文档中&#xff0c;久而久之就积累了不少“个人”能够看懂的脉络和提纲&#xff0c;于是近几日准备将这部分略显杂乱的内容重新进行梳理。论文部分以我个人的理解对其做了一些分类&#xff0c;并附上一些简短的理解&#xf…

分布式幂等性

1. 什么是幂等性&#xff1f; 幂等性是指在分布式系统中&#xff0c;一个操作多次执行的结果与其执行一次的结果相同。设计具有幂等性的分布式系统可以有效避免数据不一致和重复处理的问题。 幂等系统的应用场景 在微服务架构下&#xff0c;由于分布式天然特性的时序问题, 以…

【大语言模型】基础:TF-IDF

TF-IDF (Term Frequency-Inverse Document Frequency) 是一种用于信息检索与文本挖掘的统计方法&#xff0c;用来评估一个词对于一个文件集或一个语料库中的其中一份文件的重要性。它是一种常用于文本处理和自然语言处理的权重计算技术。 原理 TF-IDF 由两部分组成&#xff1…

二叉树例题分享

文章目录 二叉树例题分享[235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/)[701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/)[108. 将有序数组转换为二叉搜索树…

<计算机网络自顶向下> TCPUDP套接字编程

应用实现&#xff1a;源端的应用进程交换报文实现应用协议&#xff0c;来实现各种各样的网络应用&#xff08;dash&#xff0c;email, etc&#xff09; 而应用层通信不可以直接通信&#xff0c;需要借助下层的服务才可以进行&#xff0c;通过层间接口交给下层&#xff0c;通过…