二叉树的序列化和反序列化(Java)

概述

关于面试中常见的其他二叉树算法题,参考面试+算法之二叉树(Java)。二叉树的定义(注意到有使用lombok提供的两个注解):

@lombok.Data
@lombok.AllArgsConstructor
private static class TreeNode {private TreeNode left;private TreeNode right;private final int val;TreeNode(int x) {this.val = x;}
}

将有序数组转换为二叉搜索树

给定一个升序排序的整数数组nums,将其转换为一棵高度平衡的二叉搜索树。来自LeetCode

分析:给定一个有序数组,转换成二叉搜索树,即左子树小于根节点,根节点小于右子树,满足条件的二叉搜索树显然不止一种。

一般情况下,大多数人都会考虑取数组的中间元素作为根节点。为啥选择中间元素,为了确保得到的二叉树的高度差尽可能小。如果数组的元素个数是奇数,根节点可以唯一确定;如果是偶数,则根节点不唯一。所以,

如果题目没有限定高度平衡的二叉树,则得到的二叉树将会更多。最差的情况就是退化成仅有根节点和左子树或右子树的二叉树,即退化成类似链表的结构。

采用递归的方法:

public static TreeNode sortedArrayToBST(int[] nums) {return traversal(nums, 0, nums.length - 1);
}private static TreeNode traversal(int[] nums, int left, int right) {if (left > right) {// 叶子节点return null;}int mid = left + ((right - left) / 2);TreeNode node = new TreeNode(nums[mid]);node.left = traversal(nums, left, mid - 1);node.right = traversal(nums, mid + 1, right);return node;
}

从中序与后序遍历序列构造二叉树

来自LeetCode

public static TreeNode buildTree(int[] inOrder, int[] postOrder) {return helper(inOrder, postOrder, postOrder.length - 1, 0, inOrder.length - 1);
}private static TreeNode helper(int[] inOrder, int[] postOrder, int postEnd, int inStart, int inEnd) {if (inStart > inEnd) {return null;}int currentVal = postOrder[postEnd];TreeNode current = new TreeNode(currentVal);int inIndex = 0;for (int i = inStart; i <= inEnd; i++) {if (inOrder[i] == currentVal) {inIndex = i;}}TreeNode left = helper(inOrder, postOrder, postEnd - (inEnd - inIndex) - 1, inStart, inIndex - 1);TreeNode right = helper(inOrder, postOrder, postEnd - 1, inIndex + 1, inEnd);current.left = left;current.right = right;return current;
}

序列化

递归的思想,采用前序遍历,对空节点需要特殊处理,使用任何占位符都可:

public static String serialize(TreeNode root) {StringBuilder sb = new StringBuilder();serializeHelper(root, sb);return sb.substring(0, sb.length() - 1);
}private static void serializeHelper(TreeNode node, StringBuilder sb) {if (node == null) {sb.append("#,"); // 空节点return;}sb.append(node.val).append(",");serializeHelper(node.left, sb);serializeHelper(node.right, sb);
}

反序列化

能否从序列化后的字符串,反序列化得到一棵树呢?
答案是可以的。前提是知道对空节点的处理(填位字符串)策略,节点的间隔(字符)策略。

能否从序列化后的字符串,反序列化得到原始的二叉树?
答案是不一定,如果想要反序列化得到原始的二叉树,有一些前提条件:

  • 序列化方案,是前序、中序还是后序
  • 对空节点的处理策略
  • 节点的间隔(字符)策略
public static TreeNode deserialize(String data) {LinkedList<String> nodes = new LinkedList<>(Arrays.asList(data.split(",")));return deserializeHelper(nodes);
}private static TreeNode deserializeHelper(LinkedList<String> nodes) {String val = nodes.removeFirst();if (val.equals("#")) {return null;}TreeNode node = new TreeNode(Integer.parseInt(val));node.left = deserializeHelper(nodes);node.right = deserializeHelper(nodes);return node;
}

测试方法:

public static void main(String[] args) {TreeNode head = init();String s = serialize(head);// TreeNode.toString()方法String b = deserialize(s).toString();System.out.println("序列化:" + s);System.out.println("反序列化:" + b);
}

TreeNode.toString()方法:

@Override
public String toString() {return toString(this);
}public String toString(TreeNode r) {if (r == null) {return "#";} else {return r.val + "," + toString(r.left) + "," + toString(r.right);}
}

输出:

序列化:8,4,2,#,1,#,#,3,#,#,7,#,6,5,#,#,#
反序列化:8,4,2,#,1,#,#,3,#,#,7,#,6,5,#,#,#

结论:

  • 已知序列化方案和空节点处理策略后,可唯一反序列化得到原始二叉树
  • 二叉树的打印方法里对空节点的处理策略保持一致,且间隔符都是,,则两个字符串相等

进阶

如果不知道序列化方案,如何反序列化得到二叉树?

结论:可以反序列化,但是不一定是原始的二叉树,所以这个反序列化也没有任何意义。

为了解决这个问题,有几个选择:

  • 使用包含遍历顺序信息的序列化格式:
    在序列化字符串中包含遍历顺序信息。例如,在字符串前面加上遍历顺序的标识符(如P表示前序(Pre-order),I表示中序(In-order),O表示后序(post-Order))。
  • 双序列化:
    使用两种不同的遍历顺序分别序列化树,并将两个序列化结果一起保存。两种方案字符|以分隔。例如,使用前序和中序,或后序和中序。

其中方案二基于这样一个已被证实的结论:若存在对同一棵二叉树的两种不一样的遍历(序列化)方案,则一定可以唯一确定这棵二叉树。

/*** 序列化二叉树(前序和中序)*/
public static String serialize1(TreeNode root) {StringBuilder preOrder = new StringBuilder();StringBuilder inOrder = new StringBuilder();serializePreOrder(root, preOrder);serializeInOrder(root, inOrder);return preOrder.toString() + "|" + inOrder.toString(); // 使用 | 作为分隔符
}private static void serializePreOrder(TreeNode node, StringBuilder sb) {if (node == null) {sb.append("#,");return;}sb.append(node.val).append(",");serializePreOrder(node.left, sb);serializePreOrder(node.right, sb);
}private static void serializeInOrder(TreeNode node, StringBuilder sb) {if (node == null) {sb.append("#,");return;}serializeInOrder(node.left, sb);sb.append(node.val).append(",");serializeInOrder(node.right, sb);
}/*** 反序列化二叉树*/
public static TreeNode deserialize1(String data) {// 需预知的信息:两个字符串的分隔符String[] parts = data.split("\\|");// 需预知的信息:序列化的间隔符LinkedList<String> preOrderNodes = new LinkedList<>(Arrays.asList(parts[0].split(",")));LinkedList<String> inOrderNodes = new LinkedList<>(Arrays.asList(parts[1].split(",")));return buildTree(preOrderNodes, inOrderNodes);
}private static TreeNode buildTree(LinkedList<String> preOrderNodes, LinkedList<String> inOrderNodes) {// 需预知的信息:对空节点的处理策略if (preOrderNodes.isEmpty() || preOrderNodes.peek().equals("#")) {preOrderNodes.poll();inOrderNodes.poll();return null;}String rootVal = preOrderNodes.poll();TreeNode root = new TreeNode(Integer.parseInt(rootVal));int inOrderIndex = inOrderNodes.indexOf(rootVal);root.left = buildTree(preOrderNodes, new LinkedList<>(inOrderNodes.subList(0, inOrderIndex)));root.right = buildTree(preOrderNodes, new LinkedList<>(inOrderNodes.subList(inOrderIndex + 1, inOrderNodes.size())));inOrderNodes.poll();return root;
}

可以看出,这两个方案还是得提前知道一些规则信息。事实上,网络通信就是一个包含序列化和反序列化的过程,如果不知道序列化规则(即协议信息),则反序列化几乎没有意义。

参考

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

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

相关文章

java 参数传递(尤其注意参数是对象的情况)

8大基本数据类型为 值传递 类和数组为 引用传递&#xff0c;传递的是地址 但是要注意虽然类是引用传递&#xff0c;但是要注意&#xff0c;调用方法是新开一个栈 因此如果进行p null或者 Person p new Person()等语句&#xff0c;要格外注意&#xff1a; 如果主函数再次输出…

基于场景的 Java Spring Boot 热门面试问题

随着 Spring Boot 继续主导 Java 生态系统&#xff0c;对熟悉这个强大框架的熟练开发人员的需求正在上升。如果您正在准备 Spring Boot 面试&#xff0c;尤其是作为一名经验丰富的专业人士&#xff0c;那么专注于测试您的实践知识和解决问题能力的基于场景的问题至关重要。本文…

音频demo:将PCM数据与alaw、mulaw、g711数据的相互转换

1、README 前言 (截图来源&#xff1a;https://blog.csdn.net/u014470361/article/details/88837776) 我的理解&#xff1a; 首先需要知道的是u-law/a-law是用于脉冲编码的压缩/解压缩算法。而G.711是指在8KHz采样率&#xff08;单声道&#xff09;中&#xff0c;使用的u-law或…

LLM- 注意力机制

一&#xff1a;什么是注意力机制&#xff0c;以及产生背景&#xff1f; &#xff08;1&#xff09;&#xff1a;RNN模型[RNN模型]的缺点&#xff1a;下图是例如RNN模型解决机器翻译的例子&#xff0c;从这个例子可以看到Encoder最后一个向量&#xff08;eos&#xff09;送给了…

springboot整合vue上传文件至oss

引言 在现代Web应用中&#xff0c;文件上传是一个常见需求。而将文件存储在云存储服务&#xff08;如阿里云OSS、AWS S3等&#xff09;则是一个优雅且高效的解决方案。本文将使用Spring Boot作为后端框架&#xff0c;Vue作为前端框架&#xff0c;演示如何实现文件上传至阿里云…

Docker启动安装nacos

单机模式# 拉取镜像 docker pull nacos/nacos-server # (最新版) docker pull nacos/nacos-server:v2.2.0挂载目录中文件 新建目录 mkdir -p /mydata/nacos/logs/ #新建logs目录 mkdir -p /mydata/nacos/conf/ #新建conf目录复制容器文件到宿主机…

B端全局导航:左侧还是顶部?不是随随便便,有依据在。

一、什么是全局导航 B端系统的全局导航是指在B端系统中的主要导航菜单&#xff0c;它通常位于系统的顶部或左侧&#xff0c;提供了系统中各个模块和功能的入口。全局导航菜单可以帮助用户快速找到和访问系统中的各个功能模块&#xff0c;提高系统的可用性和用户体验。 全局导航…

Kubernetes平台迁移

Kubernetes&&平台迁移 信息收集 信息收集

HJ41 称砝码

HJ41 称砝码 提示&#xff1a;文章 文章目录 前言一、背景二、 2.1 2.2 总结 前言 前期疑问&#xff1a; 本文目标&#xff1a; 一、背景 这个题目之前是没有做出来的&#xff0c;我把之前没做出来的代码也记录一下 二、 2.1 之前的代码 #include <stdio.h>int m…

代码随想录算法训练营:23/60

非科班学习算法day23 | LeetCode39:组合总和 &#xff0c;Leetcode40:组合总和|| 介绍 包含LC的两道题目&#xff0c;还有相应概念的补充。 相关图解和更多版本&#xff1a; 代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%9…

计算机的错误计算(二十五)

摘要 介绍&#xff08;不&#xff09;停机问题。给了一个算式&#xff0c;当计算机的输出为0时&#xff0c;一般需要提高计算精度继续计算&#xff0c;一直到获得非0值或有效数字。但是&#xff0c;由于事先不清楚算式的准确值是否为0或不为0&#xff0c;因此往往陷入两难境地…

java包装类。

包装类介绍 Java的包装类&#xff08;Wrapper Classes&#xff09;是Java语言为八种基本数据类型&#xff08;byte, short, int, long, float, double, char, boolean&#xff09;提供的对应类。这些包装类允许我们将基本数据类型当作对象来处理&#xff0c;并提供了许多有用的…

常见的网络协议在不同的模型中层次分布

常见的网络协议根据其功能和作用&#xff0c;分布在OSI七层参考模型或TCP/IP四层参考模型的不同层次中。以下是这些协议在不同模型中的层次分布&#xff1a; 在OSI七层参考模型中的层次分布 物理层&#xff1a; 这一层主要涉及物理连接的建立和物理信号的传输&#xff0c;但通…

【Java15】继承

继承是面向对象三大特征之一&#xff0c;也是软件代码服用的重要手段。 Java只允许单继承&#xff0c;即每个子类只有一个直接父类。 C中的多继承被Java舍弃了&#xff0c;原因是多继承一方面难以准确表述类之间的关系&#xff0c;另一方面很容易造成代码错误。总结起来就两个…

双系统ubuntu20.04扩容

windows端 打开磁盘管理器&#xff0c;选择需要的盘点击压缩卷 点击未分配的盘&#xff0c;新建简单卷&#xff0c;一致点击下一步即可&#xff0c;记住分配的大小容量 ubuntu端 lsblk 查看所有的磁盘&#xff0c;可以看到新增为nvme0n1p4、nvme1n1p2 win分配的格式为NTFS&a…

【Excel】 批量跳转图片

目录标题 1. CtrlA全选图片 → 右键 → 大小和属性2. 取消 锁定纵横比 → 跳转高度宽度 → 关闭窗口3. 最后一图拉到最后一单元格 → Alt吸附边框![](https://i-blog.csdnimg.cn/direct/d56ac1f41af54d54bb8c68339b558dd1.png)4. CtrlA全选图片 → 对齐 → 左对齐 → 纵向分布!…

全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法-模拟串口传感器和主机

全网最适合入门的面向对象编程教程&#xff1a;11 类和对象的 Python 实现-子类调用父类方法-模拟串口传感器和主机 摘要&#xff1a; 本节课&#xff0c;我们主要讲解了在 Python 类的继承中子类如何进行初始化、调用父类的属性和方法&#xff0c;同时讲解了模拟串口传感器和…

OpenHarmony 入门——单元测试UnitTest快速入门

引言 OpenHarmony 的单元测试&#xff08;UnitTest&#xff09;是一个关键的软件开发过程&#xff0c;它确保代码的各个部分能够按预期工作&#xff0c;OpenHarmony的测试框架中提供了很多种的单元测试&#xff0c;今天简单介绍下UnitTest 类型的TDD测试。 OpenHarmony 的TDD …

Nacos 国际化

项目需要&#xff0c;后端异常信息需要进行国际化处理。所有想有没有方便易用的可选项。 1、国际化配置调整&#xff0c;不需要重启系统 2、可支持添加不同或自定义语言包&#xff08;就是配置的资源文件&#xff09; 参考&#xff1a; Nacos实现SpringBoot国际化的增强_spr…

Android IP地址、子网掩码、默认网关、首选DNS服务器、备用DNS服务器校验

Android IP地址、子网掩码、默认网关、首选DNS服务器、备用DNS服务器校验 public String isIP(String ip) {String regex = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}";Pattern p = Pattern.compile(regex)