题目描述:
请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。
二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)
二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。例如,可以根据层序遍历的方案序列化,如下图:
层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}"构造成如上的二叉树。
当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。
数据范围:节点数n≤100,树上每个节点的值满足0≤val≤150
要求:序列化和反序列化都是空间复杂度O(n),时间复杂度O(n)
示例1
输入:{1,2,3,#,#,6,7}
返回值:{1,2,3,#,#,6,7}
说明:如题面图
示例2
输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}
解法一:递归
思路:
通过前序遍历的方式构造字符串并恢复二叉树。
序列化过程:递归的截止条件:当节点为空时,返回'#',这是为了起到一个占位的作用,从而保证树的唯一性。否则单独前序遍历出来的字符串是无法恢复成一棵树的。
反序列化过程:借助辅助队列来存储分割后的前序遍历结果。先取出队首元素,将对象化成一个节点,然后将左子节点的值设为递归这个queue的函数返回结果,右子节点也是如此操作。
代码:
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
String Serialize(TreeNode root) {
if(root == null) return "#"; // 对所有的空节点也要存储占位符号"#"
return root.val + "," + Serialize(root.left) + "," + Serialize(root.right); // 递归前序遍历返回字符串
}
TreeNode Deserialize(String str) {
String[] s = str.split(","); // 切分字符串
Queue<String> q = new LinkedList<String>();
for(int i = 0; i != s.length; i++) q.offer(s[i]); // 将分割后的字符串数组顺序入队
return de(q); // 按照新的递归函数进行返回
}
TreeNode de(Queue<String> queue) {
String s = queue.poll(); // 递归函数每次从队首读出一个元素,因此读出的顺序也是前序
if(s.equals("#")) return null; // 对递归推出条件之一进行处理
TreeNode head = new TreeNode(Integer.valueOf(s)); // 前序首先建立一个节点
head.left = de(queue); // 然后递归左右子节点
head.right = de(queue);
return head;
}
}
解法二:迭代
思路:
迭代使用层序遍历的方式。
序列化过程:用队列存储每一层的节点信息,然后在遍历每一层的过程中构造字符串,同时保存下一层的节点信息,最终返回字符串。
反序列化过程:引入一个队列构建树,同样是先拿到一层的节点信息,然后配合队列弹出父子节点,最终返回树。
代码:
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
String Serialize(TreeNode root) {
String res = "";
if(root == null) return "#!"; // 如果根节点为空,则按照我们的格式要求直接返回"#!"便于我们反序列化处理
Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 引入队列,准备进行层序遍历建立字符串
queue.offer(root); // 先压入根节点
while(!queue.isEmpty()) { // 队列不空的前提下循环
int n = queue.size(); // 获得该层的节点数量
while(n > 0) { // 对当前一整层进行处理
TreeNode p = queue.poll(); // 取出队首
if(p != null) { // 队首空和非空的处理不同
res = res + p.val + "!"; // 非空时,用节点值+分隔符续接字符串
queue.offer(p.left); // 左右子节点入队
queue.offer(p.right);
}
else res = res + "#!"; // 队首空,则用#填充并+分隔符续接字符串
n--; // 更新当前层中还未处理的节点个数
}
}
return res;
}
TreeNode Deserialize(String str) {
String[] s = str.split("!"); // 切分字符串
if(s[0].equals("#")) return null; // 特殊情况直接判断处理
Queue<String> q = new LinkedList<String>(); // 层序的节点排序结果存储在队列q中
Queue<TreeNode> aux = new LinkedList<TreeNode>(); // 引入aux辅助队列
for(int i = 0; i != s.length; i++) q.offer(s[i]); // 将分割后的字符串数组顺序入队
String cha_num = q.poll(); // 首先取出来一个字符
TreeNode root = new TreeNode(Integer.valueOf(cha_num)); // 将取出来的字符初始化节点对象
TreeNode head = root;
aux.offer(root); // aux队列也初始化完成
while(!q.isEmpty()) { // 看我们的q队列是否全部字符处理干净了
int n = aux.size(); // aux存储树的每一层节点,n表示该层的节点数量
while(n > 0) {
root = aux.poll(); // 每次取aux队首开始处理
if(root != null) { // 队首空和非空的处理是不同的
String a = q.poll(); // aux取出q中的元素并对象化后,q中存在字符串元素就是aux中这一层节点对应的下一层左右子节点,
String b = q.poll(); // 每一对取出来的a,b都是可以和aux当前队首完全对应上的
TreeNode l = a.equals("#") ? null : new TreeNode(Integer.valueOf(a)); // 判断a是否为#决定是否要建立一个新节点
TreeNode r = b.equals("#") ? null : new TreeNode(Integer.valueOf(b)); // 判断b是否为#决定是否要建立一个新节点
root.left = l; // 当前队首指向刚刚对象化的l子节点
root.right = r; // 当前队首指向刚刚对象化的r子节点
if(l != null) aux.offer(l); // 通过判断l是否为空,来控制是否要压入新节点,保证aux中每次取出来的队首节点可以对应上q队列中新元素
if(r != null) aux.offer(r); // 通过判断r是否为空,来控制是否要压入新节点,保证aux中每次取出来的队首节点可以对应上q队列中新元素
}
n--;
}
}
return head;
}
}