剑指Offer_#37 序列化二叉树

剑指Offer_#37 序列化二叉树

Contents

题目

请实现两个函数,分别用来序列化和反序列化二叉树。
示例: 
你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化为 "[1,2,3,null,null,4,5]"

思路分析

题意分析

  1. 用哪种遍历方式进行序列化?
    这一题给出的示例,序列化时采用层序遍历(BFS),与书上不一样,书中是用前序遍历。但是实际上根据代码模板里边的说明:

    Your Codec object will be instantiated and called as such:
    Codec codec = new Codec();
    codec.deserialize(codec.serialize(root));

    在Leetcode平台中,它测试的方法是:对一个二叉树先做序列化,再做反序列化,然后看结果是否跟输入的二叉树相同。
    而没有去分别测试序列化的结果,反序列化的结果。
    所以说,要通过这一题,无论用哪种遍历方式都是可以的,重点在于,serializedeserialize函数是互逆的。
    看到题目的例子,我下意识认为序列化必须是层序遍历,感觉具有一定的迷惑性...层序遍历也是写法之一,但是代码较为复杂,也可以选择使用代码较简单的前序遍历。

  2. 剑指 Offer 07. 重建二叉树的区别?

    • 7题是通过前序遍历和中序遍历两个序列建立二叉树,而本题只需要用一种序列就可以重建二叉树。
    • 原因是:7题中给出的序列不包含null节点,一个序列中没有足够的信息量来重建二叉树,必须依赖两个序列重建。

思路

解法1:层序遍历(BFS)

  1. 序列化
    参考剑指Offer_#32_从上到下打印二叉树的写法,改动的地方是

    • 函数返回值变为String,使用StringBuilder来迭代式的构造字符串。
    • while循环中,可以向队列中加入是null的子节点;同时也要针对节点为null的情况做特殊处理,用一个“null”字符串来表示。
  2. 反序列化
    依然要借助队列来辅助。整体思路如下:

    • 队列存储的是待构造的节点val已经设置,但左右指针还未设置好),每次取出头部节点,设置它的leftright指针。
    • 同时遍历序列化字符串二叉树节点队列
      • 每次从队列取出一个节点node,对应的从序列化字符串里取出它的左右子节点,连接到node上。
      • 然后将左右子节点加入队列(因为他们的左右指针还未设置好)
    • 一个关键点:对于“null”的处理
      • 序列化字符串中遇到null的时候,不需要做任何处理。
      • 因为:
        • null节点不需要被连接到node,因为初始化时,node的左右子节点默认就是null;
        • null节点不需要设置左右子节点,所以不需要加入队列。

解法2:前序遍历

  1. 序列化
    树的前序遍历。唯一特殊的地方是,需要将结果转换成String再返回。
  2. 反序列化
    递归遍历vals和树。
    • 出口条件: 遍历到$,返回null
    • 递推工作
      • 构建一个节点node,移动指向vals元素的指针index
      • 构建当前节点的左右子节点
      • 返回node

解答

解答1:层序遍历

public class Codec {
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null) return "[]";
        StringBuilder res = new StringBuilder("[");
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        while(!q.isEmpty()){
            TreeNode node = q.poll();
            if(node != null){
                res.append(node.val + ",");
                q.offer(node.left);
                q.offer(node.right);
            }
            //如果当前访问的节点是null,那么就不需要添加其左右子节点到队列
            else
                res.append("null,");
        }
        res.deleteCharAt(res.length() - 1);
        res.append("]");
        return res.toString();
    }
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        String[] vals = data.substring(1,data.length() - 1).split(",");
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        //由于第索引为0的是根节点,已经入队列,所以while循环当中是从索引1开始的
        int i = 1;
        while(!q.isEmpty()){
            TreeNode node = q.poll();
            //构建左子节点
            //如果左子节点对应的值是“null”,则不做任何处理,直接跳过
            if(!vals[i].equals("null")){
                TreeNode leftNode = new TreeNode(Integer.parseInt(vals[i]));
                node.left = leftNode;
                q.offer(leftNode);
            }
            i++;
            //构建右子节点
            if(!vals[i].equals("null")){
                TreeNode rightNode = new TreeNode(Integer.parseInt(vals[i]));
                node.right = rightNode;
                q.offer(rightNode);
            }
            i++;
        }
        return root;
    }
}

解答2:前序遍历

public class Codec {
    String[] vals;
    int index = 0;//vals[]的索引


    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null) return "[]";
        StringBuilder res = new StringBuilder();
        res.append("[");
        serializeRecur(root,res);
        res.deleteCharAt(res.length() - 1);
        res.append("]");
        return res.toString();
    }

    void serializeRecur(TreeNode root,StringBuilder res){
        if(root == null){
            res.append("$,");
            return;
        }
        //形参res指向的内存空间不变,所以可以修改原本的res变量
        res.append(root.val + ",");
        serializeRecur(root.left,res);
        serializeRecur(root.right,res);
        return;
    }

    // Decodes your encoded data to tree.
   public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        String[] vals = data.substring(1,data.length() - 1).split(",");
        this.vals = vals;
        return deserializeRecur();
    }

   TreeNode deserializeRecur(){
        if(vals[index].equals("$")){
            index++;
            return null;
        }
        else{
            //构建完当前的节点,就增加index,指向下一个节点的值
            TreeNode node =new TreeNode(Integer.parseInt(vals[index++]));
            node.left = deserializeRecur();
            node.right = deserializeRecur();
            return node;
        }
    }
}

Java基础:值传递和引用传递

上面的两个递归函数serializeRecurdeserializeRecur当中体现了Java参数传递的知识。

deserializeRecur的一种错误写法

// Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        String[] vals = data.substring(1,data.length() - 1).split(",");
        this.vals = vals;
        TreeNode root = new TreeNode();
        deserializeRecur(root);
        return root;
    }
    
    void deserializeRecur(TreeNode node){
        if(vals[index].equals("$")){
            node = null;
            index++;
            return;
        }
        else{
            //错误就在这句,这里把node形参指向了一个新对象,那么之后的所有操作,都和调用处传入的root无关了,root不会被改变
            node = new TreeNode(Integer.parseInt(vals[index++]));
            deserializeRecur(node.left);
            deserializeRecur(node.right);
        }
        
    }

这是我第一次照猫画虎照着书上的C++代码写下来的, 但是Java跟C++不同啊,C++代码是:

void Deserialize(BinaryTreeNode** pRoot, istream& stream)
{
    int number;
    if(ReadStream(stream, &number))
    {
        *pRoot = new BinaryTreeNode();
        (*pRoot)->m_nValue = number;
        (*pRoot)->m_pLeft = nullptr;
        (*pRoot)->m_pRight = nullptr;

        Deserialize(&((*pRoot)->m_pLeft), stream);
        Deserialize(&((*pRoot)->m_pRight), stream);
    }
}

C++当中,pRoot是一个指针,*pRootpRoot指向的内存空间,这里用赋值语句,修改的就是指针指向的内容;
但是在java当中,node并不等同于指针,赋值语句不会修改node原本指向的内存空间,而是把node指向一个新创建的TreeNode对象的内存空间。
关于这部分知识,可以参考

posted @ 2020-07-11 15:37  Howfar's  阅读(166)  评论(0编辑  收藏  举报