二叉树的序列化和反序列化(建树)
提示:搞清楚建树的步骤
递归序的本质要搞清楚,三次见面:
(1)二叉树,二叉树的归先序遍历,中序遍历,后序遍历,递归和非递归实现
题目
请你将二叉树序列化,并根据序列化的队列结果,反序列化,把二叉树还原
下面这个树,序列化之后,是1 2 null null 3 null null
根据队列:1 2 null null 3 null null
反序列化,把树恢复建出来
本题所用节点与树:
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int v){
value = v;
}
}
//构造一颗树,今后方便使用
public static Node generateBinaryTree(){
//树长啥样呢
// 1
// 2 3
Node head = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
head.left = n2;
head.right = n3;
return head;
}
DFS查看二叉树
//打印树的DFS
//先序遍历打印
public static void prePrint(Node head){
if(head == null) return;//不管是头还是叶节点,这就是递归的终止条件
//先打印头,再打印左子树,再打印右子树
System.out.print(head.value +" ");
prePrint(head.left);
prePrint(head.right);
}
public static void inPrint(Node head){
if(head == null) return;
//中序遍历,先打印左边,再打印头,再打印右边
inPrint(head.left);
System.out.print(head.value +" ");
inPrint(head.right);
}
public static void postPrint(Node head){
if(head == null) return;
//后序遍历打印,先打印左边,再打印右边,最后打印头
postPrint(head.left);
postPrint(head.right);
System.out.print(head.value +" ");
}
一、为什么要序列化二叉树
因为,程序中,往往有的进程,需要先暂停,保存,方便停电之后又从原来执行过的地方恢复继续执行
感觉是想浏览器,你原来浏览了哪些地方,关闭电脑前
需要将进程(树)保存好,那就需要序列化,然后存入文件
下次要用的时候,咱们再将其反序列化,恢复出来继续使用。
二、DFS方式二叉树如何序列化?如何反序列化?
DFS序列化二叉树
序列化有几种方式:先序序列化,中序序列化,后序序列化
无非就是在DFS遍历过程中,遇到null也要保存
序列化比遍历打印就多出null来
这样才能保证回头我们可以恢复树
有了递归序:
(1)二叉树,二叉树的归先序遍历,中序遍历,后序遍历,递归和非递归实现
第一次见面就保存,叫先序序列化
第二次见面就保存,叫先中序序列化
第三次见面就保存,叫后序序序列化
【反正遍历过程中遇到null照样继续加】
不放用先序序列化做例子:
(1)第一次见面1点,加入队列ans;
(2)遍历1点的左子,第一次见面2点,加入ans;
(3)遍历2点的左子,null,加入ans;
(4)遍历2点的右子,null,加入ans;
(5)遍历点1的右子3点,第一次见面3点,加入ans;左右子null均加入
完成序列化。
三种序列化方式手撕代码:
先序序列化
//先序序列化
public static Queue<String> preSerial(Node head){
if (head == null) return null;
Queue<String> queue = new LinkedList<>();//内部是字符串,我们回头可以建
serialPre(head, queue);
return queue;
}
public static void serialPre(Node head, Queue<String> queue){
if (head == null) queue.add(null);
else {
//第一次见面就玩--先序
queue.add(String.valueOf(head.value));
//继续遍历左右子
serialPre(head.left, queue);
serialPre(head.right, queue);
}
}
public static void test(){
Node head = generateBinaryTree();
//先序打印看看
System.out.println("先序打印:");
prePrint(head);
System.out.println();
System.out.println("先序序列化");
Queue<String> ans = preSerial(head);
for(String s:ans) System.out.print(s +" ");
}
public static void main(String[] args) {
test();
}
先序打印:
1 2 3
先序序列化
1 2 null null 3 null null
中序序列化
//中序序列化
public static Queue<String> inSerial(Node head){
if (head == null) return null;
Queue<String> queue = new LinkedList<>();
serialIn(head, queue);
return queue;
}
public static void serialIn(Node head, Queue<String> queue){
//沿途遍历见到的元素加入queue,DFS的骚操作
if (head == null) queue.add(null);
else {
//第二次见面才能加哦,所以要先去看左树
serialIn(head.left, queue);
queue.add(String.valueOf(head.value));//2次见面玩叫中序
serialIn(head.right, queue);
}
}
后序序列化
//后序序列化
public static Queue<String> postSerial(Node head){
if (head == null) return null;
Queue<String> queue = new LinkedList<>();
serialPost(head, queue);
return queue;
}
public static void serialPost(Node head, Queue<String> queue){
//沿途遍历见到的元素加入queue,DFS的骚操作
if (head == null) queue.add(null);
else {
//第三次见面才能加哦,所以要先去看左树和右树
serialPost(head.left, queue);
serialPost(head.right, queue);
queue.add(String.valueOf(head.value));//3次见面玩叫后序
}
}
测试一把:
public static void test(){
Node head = generateBinaryTree();
//先序打印看看
System.out.println("先序打印:");
prePrint(head);
System.out.println();
System.out.println("先序序列化");
Queue<String> ans = preSerial(head);
for(String s:ans) System.out.print(s +" ");
System.out.println("\n------------------");
//中序打印看看
System.out.println("中序打印:");
inPrint(head);
System.out.println();
System.out.println("中序序列化");
Queue<String> ans2 = inSerial(head);
for(String s:ans2) System.out.print(s +" ");
System.out.println("\n------------------");
//后序打印看看
System.out.println("后序打印:");
postPrint(head);
System.out.println();
System.out.println("后序序列化");
Queue<String> ans3 = postSerial(head);
for(String s:ans3) System.out.print(s +" ");
System.out.println("\n------------------");
}
public static void main(String[] args) {
test();
}
看结果:
先序打印:
1 2 3
先序序列化
1 2 null null 3 null null
------------------
中序打印:
2 1 3
中序序列化
null 2 null 1 null 3 null
------------------
后序打印:
2 3 1
后序序列化
null null 2 null null 3 1
------------------
根据一个DFS序列化的队列结果,如何恢复二叉树?
你是怎么序列化的就怎么反序列化,
无非就是消耗结果中的元素,建头节点,然后建左树,建右树
然后将左右树挂在头节点上,返回头节点。
建节点可能有null,可能是节点:
//建一个节点,可能是null,可能有value
public static Node creatNode(String value){
return value == null ? null : new Node(Integer.valueOf(value));
}
反序列化这个过程需要:也是DFS的过程
如果是:先建头节点,然后建左树,然后建右树——先序反序列化过程。
如果是:先建左树,再建头节点,再建右树——中序反序列化过程。
如果是:先建左树,再建右树,再建头节点——后序反序列化过程。
下面我们分别实现:
如果是:先建头节点,然后建左树,然后建右树——先序反序列化过程。
//如果是:先建头节点,然后建左树,然后建右树——先序反序列化过程。
public static Node buildPreFromSerial(Queue<String> ans){
if (ans == null || ans.size() == 0) return null;//没有点
//DFS建树:依次让那些点去建树,挂
return bulidPre(ans);
}
public static Node bulidPre(Queue<String> ans){
String value = ans.poll();//弹
if (value == null) return null;
//建点的顺序,决定了你是先序反序列化,中序反序列化,还是后序反序列化
Node head = creatNode(value);//先建头节点--先序反序列化
head.left = bulidPre(ans);//建左树
head.right = bulidPre(ans);//建右树
return head;//从x开始建好左右子,返回x就是答案。
}
public static void test2(){
Node head = generateBinaryTree();
System.out.println("先序打印结果:");
prePrint(head);
System.out.println("\n先序序列化:");
Queue<String> ansPre = preSerial(head);
for(String s:ansPre) System.out.print(s +" ");
System.out.println("\n先序反序列化建树");
Node headPre = buildPreFromSerial(ansPre);
System.out.println("先序打印反序列化后的结果:");
prePrint(headPre);
System.out.println("\n-------------");
}
public static void main(String[] args) {
// test();
test2();
}
目前只成功了这先序序列化的反序列化代码:
先序打印结果:
1 2 3
先序序列化:
1 2 null null 3 null null
先序反序列化建树
先序打印反序列化后的结果:
1 2 3
-------------
如果是:先建左树,再建头节点,再建右树——中序反序列化过程。
头直接就是null,咱要怎么实现中序建树呢
没想通
如果是:先建左树,再建右树,再建头节点——后序反序列化过程。
这个暂时不会
三、BFS方式如何序列化二叉树?如何反序列化?
BFS方式如何序列化二叉树
有上上面DFS序列化的经验,不就是序列化的时候,要把null加入吗?
反序列化的时候,也会是同样的递归代码,消耗序列化结果,建头节点,挂左右子,即可
BFS宏观调度,左右子谁遇到空就加null
//BFS序列化
public static Queue<String> bfsSerial(Node head){
if (head == null) return null;
Queue<String> ans = new LinkedList<>();
Queue<Node> queue = new LinkedList<>();//bfs的
queue.add(head);
ans.add(String.valueOf(head.value));//头入结果
while (!queue.isEmpty()){
Node cur = queue.poll();
if (cur.left != null){
queue.add(cur.left);
ans.add(String.valueOf(cur.left.value));//加结果
}else ans.add(null);//这里要注意,null也要加
if (cur.right != null){
queue.add(cur.right);
ans.add(String.valueOf(cur.right.value));
}else ans.add(null);
}
return ans;
}
//BFS反序列化
public static void test3(){
Node head = generateBinaryTree();
System.out.println("bsf打印:");
bfsBinaryTreePrint(head);
System.out.println("\n序列化后打印:");
Queue<String> ans = bfsSerial(head);
for(String s:ans) System.out.print(s +" ");
}
public static void main(String[] args) {
// test();
// test2();
test3();
}
BFS方式序列化二叉树的结果,如何反序列化?
怎么通过BFS序列化,就怎么消耗结果又造回来
建头,左树,右树,挂头完成
//BFS反序列化
public static Node bfsBackBuildTree(Queue<String> ans){
if (ans == null || ans.size() == 0) return null;
//BFS宏观控制
LinkedList<Node> queue = new LinkedList<>();
Node head = creatNode(ans.poll());
queue.add(head);
while (!queue.isEmpty()){
Node cur = queue.poll();
//消耗2个,建左右树
cur.left = creatNode(ans.poll());
cur.right = creatNode(ans.poll());
//按层加入左右子--长队bfs
if (cur.left != null) queue.add(cur.left);
if (cur.right != null) queue.add(cur.right);
}
return head;
}
public static void test3(){
Node head = generateBinaryTree();
System.out.println("bsf打印:");
bfsBinaryTreePrint(head);
System.out.println("\n序列化后打印:");
Queue<String> ans = bfsSerial(head);
for(String s:ans) System.out.print(s +" ");
System.out.println("\nBFS反序列化后打印:");
Node headBFS = bfsBackBuildTree(ans);
bfsBinaryTreePrint(headBFS);
}
public static void main(String[] args) {
// test();
// test2();
test3();
}
总结
提示:重要经验:
1)涉及图或者二叉树的BFS的话,先把BFS代码写出来,咱再改为自己的代码,这个BFS代码尤其重要。
2)二叉树的序列化和反序列化,包括DFS序列化和BFS序列化方式,都要加null,你怎么序列化,就同样的代码反序列化,无非就是消耗元素建新节点。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。