描述
实现二叉树的序列化与反序列化。
分析
序列化二叉树的方式有很多,最经典的就是将一颗二叉树转化为字符串,这样方便存储到文件。反序列化时只需解析这个字符串,提取出每个节点的内容信息,将二叉树还原。二叉树序列化有许多应用,比如 OJ 平台在判断你提交的二叉树是否正确时,往往是将你的二叉树序列化成字符串,然后与正确答案的序列化字符串进行比较。那么如何将一颗二叉树转化成字符串呢?字符串可以认为是线性结构。以某种二叉树遍历的顺序将二叉树线性化,可以很容易建立节点与字符的映射关系。下面将分别实现先序遍历和层序遍历下的二叉树序列化及反序列化。
先序遍历
序列化
按照先序遍历的顺序将二叉树每个节点的值域转换成字符串,并添加到整个序列化字符串的后面。每两个节点之间添加 “_” 加以区分,如果节点为 null.
反序列化
将字符串以 “_” 进行分割,并用先序遍历的顺序依次创建新节点加入树中。可以用一个队列存储这些分割后子串,方便管理。
代码实现
class Serialize {
public static String serializePreorder(Node head) {
if (head == null) { // 如果遇到空节点,则在序列化字符串中添加 "null"
return "null_";
}
String res = head.element + "_"; // 将节点的域值添加到序列化字符串后面
res += serializePreorder(head.left); // 递归遍历左子树
res += serializePreorder(head.right); // 递归遍历右子树
return res;
}
public static Node deserializeByPreString(String str) {
String[] serialization = str.split("_"); // 将字符串以 "_" 分割成子串
Queue<String> nodeQueue = new LinkedList<String>(); // 字串入队
for (int i = 0; i < serialization.length; i++) {
nodeQueue.offer(serialization[i]);
}
return deserialPreorder(nodeQueue); // 调用 deserialPreorder 还原二叉树
}
private static Node deserialPreorder(Queue<String> nodeQueue) {
String node = nodeQueue.poll(); // 队首子串出队
if (node.equals("null")) { // 子串值为 null,代表当前节点为空姐点,直接返回 null
return null;
}
Node thisNode = new Node(Integer.valueOf(node)); // 用字串的值初始化一个节点
thisNode.left = deserialPreorder(nodeQueue); // 递归建立左子树
thisNode.right = deserialPreorder(nodeQueue); // 递归建立右子树
return thisNode;
}
// 二叉树节点类
static class Node {
public int element;
public Node left;
public Node right;
public Node (int x) {
this(x, null, null);
}
private Node(int x, Node right, Node left) {
this.element = x;
this.right = right;
this.left = left;
}
}
public static void main(String[] args) {
// 建树
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
// 序列化字符串
String s = serializePreorder(n1);
System.out.println(s);
// 反序列化
Node head = deserializeByPreString(s);
// 打印还原后的每个节点
System.out.print(head.element + " ");
System.out.print(head.left.element + " ");
System.out.print(head.right.element + " ");
System.out.print(head.left.left.element + " ");
System.out.print(head.left.right.element + " ");
System.out.println(head.right.right.element);
}
}
out:
1_2_4_null_null_5_null_null_3_null_null_
1 2 3 4 5
层序遍历
按照层序遍历的顺序将二叉树每个节点的值域转换成字符串,并添加到整个序列化字符串的后面。每两个节点之间添加 “_” 加以区分,如果节点为 null.
反序列化
将字符串以 “_” 进行分割,并用层序遍历的顺序依次创建新节点加入树中。
代码实现
class Serialize {
public static void main(String[] args) {
// 建树
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
String s = serializeLevelorder(n1);
System.out.println(s);
// 反序列化
Node head = deserializeByLevelString(s);
// 打印还原后的每个节点
System.out.print(head.element + " ");
System.out.print(head.left.element + " ");
System.out.print(head.right.element + " ");
System.out.print(head.left.left.element + " ");
System.out.println(head.left.right.element + " ");
}
// 按层序遍历顺序序列化
public static String serializeLevelorder(Node head) {
if (head == null) { // 如果是一颗空树,则直接返回 "null_"
return "null_";
}
Queue<Node> nodes = new LinkedList<>();
String rst = "";
nodes.offer(head); // 根节点加入队列
Node node; // 当前节点
while (!nodes.isEmpty()) { // 队列为空则退出循环
node = nodes.poll(); // 队首节点出队
if (node == null) { // 如果当前节点是空节点,则在序列化字符串中添加 "null_"
rst += "null_";
continue; // 空节点没有指针域,直接跳过本次循环
}
rst += (node.element + "_"); // 当前节点非空,将节点值域转化成字符串添加到序列化字符串后面
nodes.offer(node.left); // 当前节点左儿子入队
nodes.offer(node.right); // 当前节点右儿子入队
}
return rst;
}
// 通过层序序列化字符串反序列化
public static Node deserializeByLevelString(String str) {
if (str.equals("null_")) // 如果字符串为 "null_" 代表树为空,直接返回 null
return null;
String[] serialization = str.split("_"); // 将字符串以 "_" 分割成子串
int idx = 0;
Queue<Node> nodes =new LinkedList<>();
nodes.offer(new Node(Integer.valueOf(serialization[idx++]))); // 第一个子串转化成节点入队
Node root = nodes.peek(); // 记住树根节点
while (!nodes.isEmpty()) { // 队列空则退出循环
Node thisNode = nodes.poll(); // 取出当前节点
thisNode.left = getNode(serialization, idx++); // 设置当前节点的左儿子
thisNode.right = getNode(serialization, idx++); // 设置当前节点的右儿子
if (thisNode.left != null) { // 如果左儿子不为空,则入队
nodes.offer(thisNode.left);
}
if (thisNode.right != null) { // 如果右儿子不为空,则入队
nodes.offer(thisNode.right);
}
}
return root; // 返回根节点
}
// 将字符串转化成 Node
private static Node getNode(String[] serialization, int idx) {
if (serialization[idx].equals("null")) { // 字符串为 "null" 代表是空节点,返回 null
return null;
}
return new Node(Integer.valueOf(serialization[idx])); // 返回转化后的节点
}
out:
1_2_3_4_5_null_null_null_null_null_null_
1 2 3 4 5