看LeetCode的第297道题目,给出一个二叉树的根节点,要求你把它序列化为一个字符串,然后把这个字符串反序列化为原来二叉树。
我们拿出一个最简单的二叉树,假设我们使用后序遍历递归,就是左子树-右子树-根节点,那么我们需要遍历根节点的左子树,左子树的节点拿到,读取它的值,将它append到开始建立的一个StringBuilder字符串中。然后我们再遍历右子树,拿到它的值,把它append到StringBuilder字符串中。然后遍历根节点,把它的值append到StringBuilder中。如果在递归的过程中,其中某个根节点的某个左右子树为null,那么我们也要将它append到StringBuilder中,符号位“#”。在StringBuilder中,每个节点的值需要用逗号“,”隔开。
因此我们可以得到下面的serialize()函数:
public class Solution{
String SEP = ",";
String NULL = "#";
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root, sb);
return sb;
}
public void serialize(TreeNode root, StringBuilder sb) {
//这里有一个问题,到底先append分隔符SEP还是空值NULL
//如果root为null,先append分隔符SEP,为“,#”,否则为“#,”。不管怎么样,貌似都没问题,但是我觉得第一种更保险一些;这里
//假设是二叉树[1,2,3],先append分隔符SEP,结果为“,#,#,2,#,#,3,1”,否则为"#,#,2,#,#,3,1"
if(root == null) {
sb.append(NULL).append(SEP);
return;
}
serialize(root.left, sb);
serialize(root.right, sb);
//后序遍历位置
sb.append(root.val).append(SEP);
}
}
然后需要对上面序列化的二叉树进行解序列化。如何把一个字符串还原成一个二叉树呢?
正常来说,要想从一个后序遍历字符串还原二叉树还需要中序遍历字符串,可是刚刚我们在序列化的时候对空节点也进行了存储。首先,我们需要将字符串按照逗号分隔开,存进一个List。
怎么分隔开,这里我们使用String的split()函数。好,注意,那么刚刚我们的疑问就解决了。StringBuilder字符串先append空节点NULL,然后append分隔符SEP。为什么呢?因为对于“,#”,对它使用split()函数后分为”” 和 “#”,而对于“#,”对它使用split()函数后只有一个”#”。所以当我们以后从List拿出这个空字符串””的时候,不就要多处理一下吗?
要从List构造二叉树,首先要构造节点。一般来说我们需要构造三个节点,根节点,左节点和右节点,分别我root,left,right。构造节点,需要先构造根节点,才能构造左右节点。按照这个顺序,要从List中构造节点,只能从List末尾构造起,因为它的根节点的值放在后面,然后是右节点,然后是左节点。我们把这个构造节点的动作不断递归,就可以自顶向下构造出一个二叉树啦。
但是,要从List的末尾构造节点,我们就需要把List翻转吗?其实Java里面有一个LinkedList,我们使用它的removeLast(),就可以从List的末尾开始构造二叉树。
因此我们的deserialize()函数如下:
public TreeNode deserialize(String data) {
LinkedList<String> list = new LinkedList<>();
for(s : data.split(SEP)) {
list.addLast(s);
}
return deserialize(list);
}
public TreeNode deserialize(LinkedList<String> list) {
if(list.isEmpty()) {
return null;
}
String s = list.removeLast();
if(s.equals(NULL)) {
return null;
}
TreeNode root = new TreeNode(Integer.parseInt(s));
root.right = deserialize(list);
root.left = deserialize(list);
return root;
}