题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过某种符号表示空节点(#),以 !表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
小白看这两段解释肯定看不明白,别问我怎么知道的。看图,下图是一个前序遍历的序列化示例。来自于剑指offer书上。只不过#换成了$。
通过序列化的这个串,很容易反序列化重建成二叉树,因为空节点都标记了出来,之前做过二叉树的重建就能知道,根据前序和中序遍历的结果重建二叉树时,使用到了两个序列,因为你需要判断节点位置,而当空节点被标记时,就能很清楚节点的位置,因此反序列化时一个序列就能重建二叉树了。
在频繁操作字符串时,最好使用StringBuffer或者StringBuilder。由于序列号的前中后序以及层次都可以修改,所以递归,栈,队列都可以使用。
1.递归写法
不仅使用了StringBuilder代替了String,并且还在反序列化时,没有进行切割动作,而是选取一个数组范围进行比较,看是否有包含#,这就导致一个角标变量应该设置为全局变量。
public class Solution { String Serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); if (root == null) { sb.append("#!");//当今节点为空,就添加一个#做标记 } else { sb.append(root.val + "!");//前序遍历根节点 sb.append(Serialize(root.left));//然后调用左子树递归 sb.append(Serialize(root.right));//最后调用右子树递归 } return sb.toString(); } int index = 0;//字符串数组的角标 TreeNode Deserialize(String str) { TreeNode node = null; if (str == null || str.length() == 0) return node; int start = index; while (str.charAt(index) != '!') index++;//这index++是为了跳过当前字符,此时指向每个字符后面的! if (!str.substring(start, index).equals("#")) { node = new TreeNode(Integer.parseInt(str.substring(start, index))); index++; // 这条语句位置别放错了,插入一个节点后,index++,然后再递归 node.left = Deserialize(str); node.right = Deserialize(str); } else { index++;//否则当前字符串为#,就是空的,直接index++,不操作。 } return node; } }
2.非递归写法,我自己是没写出来。摘的另一篇题解,一叶浮尘大佬的回答,供自己学习。
链接:https://www.nowcoder.com/questionTerminal/cf7e25aa97c04cc1a68c8f040e71fb84?answerType=1&f=discussion 来源:牛客网 import java.util.LinkedList; import java.util.Queue; public class Solution { String Serialize(TreeNode root) { StringBuilder result = new StringBuilder(""); if(root != null){ //先访问根节点 result.append(String.valueOf(root.val)); //offer poll peek Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root);//需要根据队列中存储的元素来作为访问下一个节点的依据。 while(!queue.isEmpty()){ //把队列元素的左右子树进行访问,然后加入到队列中。 TreeNode head = queue.poll(); if(head.left!= null || head.right!= null){ if(head.left == null) result.append(",#"); else{ result.append("," + String.valueOf(head.left.val)); queue.offer(head.left); } if(head.right == null) result.append(",#"); else{ result.append("," + String.valueOf(head.right.val)); queue.offer(head.right); } }else{ //该节点为叶子节点,不需要进行任何处理。 } } } return result.toString(); } TreeNode Deserialize(String str) { TreeNode root = null; if(!str.equals("")){ String[] list = str.split(","); //先建立根节点 root = new TreeNode(Integer.parseInt(list[0])); Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); //把需要插入左右节点的元素放在队列中管理,一旦左右子树建立之后就从队列中移除 int index = 1; while(!queue.isEmpty() && index < list.length){ TreeNode temp = queue.poll(); if(!list[index].equals("#")){ TreeNode newLeft = new TreeNode(Integer.parseInt(list[index])); temp.left = newLeft; index ++; queue.offer(newLeft); }else index ++;//所有的if都应该配备else,不然真的很容易出错误 if(index < list.length && !list[index].equals("#")){ TreeNode newRight = new TreeNode(Integer.parseInt(list[index])); temp.right = newRight; index ++; queue.offer(newRight); }else index++;//所有的if都应该配备else,不然真的很容易出错误 } } return root; } }