题目
请实现两个函数,分别用来序列化和反序列化二叉树。
你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
思路
通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。题目要求的序列化和反序列化是可逆操作 。因此,序列化的字符串应携带完整的二叉树信息 。
观察题目示例,序列化的字符串实际上是二叉树的 “层序遍历”(BFS)结果,本题也采用层序遍历。
为完整表示二叉树,考虑将叶节点下的 null 也记录。在此基础上,对于列表中任意某节点 node ,其左子节点 node.left 和右子节点 node.right 在序列中的位置都是唯一确定的。
序列化 Serialize :
StringBuilder对象代表一个字符序列可变的字符串,当一个StringBuilder被创建以后,通过StringBuilder提供的append()(添加字符)、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuilder生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。
String,StringBuilder,StringBuffer的区别:
StringBuilder sb = new StirngBuilder(); sb.append("a").append("b").append("c").append("d");
String str = ""; str += "a"; str += "b"; str += "c"; str += "d";
1.运行速度由快到慢为:StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。StringBuilder和StringBuffer,字符串是存放在char[]中的,char[]是存放在堆中的。 相比String每次+都重新创建一个String对象,重新开辟一段内存不同,StringBuilder和StringBuffer的append都是直接把String对象中的char[]的字符直接拷贝到StringBuilder和StringBuffer的char[]上,效率比String的+高得多。当然,当StringBuilder和StringBuffer的char[]长度不够时,也会重新开辟一段内存。
2.在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。
所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
总结:
String:适用于少量的字符串操作的情况;
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况;
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
借助队列,对二叉树做层序遍历,并将越过叶节点的 null 也打印出来。
- 特例处理: 若 root 为空,则直接返回空字符串 " " ;
- 初始化: 序列化字符串build;队列 queue (根节点 root 先入队);
- 层序遍历: 当 queue 为空时跳出;
- 节点出队,记为 t ;
- 若 t 不为空:① 将 t 的左、右子节点入队,② 添加字符 t.val;
- 否则(若 t 为空):添加 "null" ;
- 返回值: build.toString();
反序列化 Deserialize :
1.split() 方法根据匹配给定的正则表达式来拆分字符串
注意: " . " 、 " $ "、 " | " 和 " * " 等转义字符,必须得加 " \\ "。
注意:多个分隔符,可以用" | "作为连字符。
语法:
stringObj.split(String regex, int limit);
stringObj -- 必选项。要被分解的 String 对象。该对象不会被 split 方法修改。
regex -- 可选项。正则表达式分隔符。如果忽略该选项,返回包含整个字符串的单一元素数组。
limit -- 可选项。分割的份数。该值用来限制返回数组中的元素个数。
返回值:字符串数组。
2.int num = Integer.parseInt(str)将字符串转换成成整数。
利用队列按层构建二叉树,借助一个指针 i 指向节点 t 的左、右子节点,每构建一个 t 的左、右子节点,指针 i 就向右移动 1 位。
- 特例处理: 若 data 为空,直接返回 null ;
- 初始化: 序列化字符串数组 s(用逗号隔开);队列 queue(包含 root );根节点 root (值为 s[0] );指针 i = 1 ;
- 按层构建: 当 queue 为空时跳出;
- 节点出队,记为 t;
- 构建 t 的左子节点:t.left 的值为 s[i] ,并将 t.left 入队;执行 i++;
- 构建 t 的右子节点:t.right 的值为 s[i] ,并将 t.right 入队;执行 i++;
- 返回值: 返回根节点 root 即可;
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.序列化成字符串
public String serialize(TreeNode root) {
if(root == null) {
return "";
}
StringBuilder build = new StringBuilder();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()) {
TreeNode t = queue.poll();
if(t != null) {
queue.add(t.left);
queue.add(t.right);
build.append(t.val + ",");
} else {
build.append("null,");
}
}
return build.toString();
}
// Decodes your encoded data to tree.反序列化字符串
public TreeNode deserialize(String data) {
if(data == null || data.length() <= 0) {
return null;
}
String[] s = data.split(",");//切割函数
Queue<TreeNode> queue = new LinkedList<>();
TreeNode root = new TreeNode(Integer.parseInt(s[0]));//将字符串解析成整数
queue.add(root);
int i = 1;
while(!queue.isEmpty()) {
//出队
TreeNode t = queue.poll();
//构建左子节点
if(!s[i].equals("null")) {
TreeNode left = new TreeNode(Integer.parseInt(s[i]));
t.left = left;
queue.add(left);
}
i++;
//构建右子节点
if(!s[i].equals("null")) {
TreeNode right = new TreeNode(Integer.parseInt(s[i]));
t.right = right;
queue.add(right);
}
i++;
}
return root;
}
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));