1. 题目描述

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

示例:

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

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

提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。

2. 解题思路 & 代码

2.1 DFS(递归)先序遍历

序列化

  1. 递归一棵树,只关注当前的单个节点就好
  2. 剩下的工作,交给递归完成
    “serialize 函数,你能不能帮我序列化我的左右子树?我等你的返回结果,再追加到我身上。”
  3. 为什么选择 前序遍历,因为在反序列化时, 根 ∣ 左 ∣ 右 根∣左∣右 ,更容易定位根节点值
  4. 遇到 null 节点也要翻译成一个特殊符号,反序列化时才知道这里对应 null 节点

    反序列化
    序列化时是前序遍历,所以序列化字符串呈现这样的排列:
    “ 根 ∣ ( 根 ∣ ( 根 ∣ 左 ∣ 右 ) ∣ ( 根 ∣ 左 ∣ 右 ) ) ∣ ( 根 ∣ ( 根 ∣ 左 ∣ 右 ) ∣ ( 根 ∣ 左 ∣ 右 ) ) ” “根∣(根∣(根∣左∣右)∣(根∣左∣右))∣(根∣(根∣左∣右)∣(根∣左∣右))” (()())(()())

    构建树的函数 buildTree
  5. buildTree 接收的 “状态” 是 list 数组,由序列化字符串转成
  6. 按照前序遍历的顺序:先构建根节点,再构建左子树,再构建右子树
    buildTree 关注当前节点,然后职责转交
  7. 将 list 数组的首项弹出,它是当前子树的 root 节点
  8. 如果它为 ‘X’ ,直接返回 null ,没有子树需要构建
  9. 如果它不为 ‘X’,则为它创建节点,并构建子树
  10. 递归调用 buildTree 构建左右子树
  11. 当前子树构建完毕,向上返回
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string. :type root: TreeNode :rtype: str """
        if not root:      # 遇到 null 节点
            return 'X,'
        leftSerialize = self.serialize(root.left)    # 左子树的序列化字符串
        rightSerialize = self.serialize(root.right)  # 右子树的序列化字符串
        return str(root.val) + ',' + leftSerialize + rightSerialize  # 根|左|右
        

    def deserialize(self, data):
        """Decodes your encoded data to tree. :type data: str :rtype: TreeNode """
        data = data.split(',')             # 创建 list 数组
        root = self.buildTree(data)        # 创建树,dfs 入口
        return root

    def buildTree(self, data):             # dfs 函数
        val = data.pop(0)                  # 当前考察的节点
        if val == 'X':                     # 是 X,返回 null 给父节点调用
            return None
        node = TreeNode(val)               # 创建 node 节点
        node.left = self.buildTree(data)   # 创建 node 的左子树
        node.right = self.buildTree(data)  # 创建 node 的右子树
        return node                        # 返回以 node 为根节点的子树给父节点调用
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))

2.1 BFS 层序遍历

序列化:

  1. null 也入列,说它是真实节点也行,它有对应的"X",只是没有子节点入列
  2. 考察出列节点
    1). 如果不为 null ,则将它的值推入 res 数组,并将它的左右子节点入列
    2). 如果是 null ,则将 ‘X’ 推入 res 数组
  3. 出列…入列…直到队列为空,所有节点遍历完,res 数组也构建完,转成字符串

反序列化
下图是BFS得到的序列化字符串:

  1. 除了第一个 ROOT 值,其他节点值都是成对的,分别对应左右子节点
  2. 从第二项开始遍历,每次考察两个节点值
  3. queue 初始放入 ROOT 。父节点出列,找出子节点入列

同时考察父节点,和两个子节点值

  1. 出列的父节点,它对应到指针指向的左子节点值,和指针右边的右子节点值
    1)如果子节点值不为 ‘X’ ,则为它创建节点,并认父亲,并作为未来父亲入列
    2)如果子节点值为 ‘X’,什么都不做
  2. 所有父节点(真实节点)都会在 queue 里走一次
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None

from collections import deque
class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string. :type root: TreeNode :rtype: str """
        if not root:
            return []
        queue = deque()
        queue.append(root)
        res = ''
        while queue:
            node = queue.popleft()   # collections 库中的queue 没有pop(0)操作,这是 list 的操作
            if node:                        # 出列的节点,带出子节点入列
                res = res + str(node.val) + ','
                queue.append(node.left)     # 不管是不是 null 节点都入列
                queue.append(node.right)
            else:
                res = res + 'X,'
        return res

    def deserialize(self, data):
        """Decodes your encoded data to tree. :type data: str :rtype: TreeNode """
        if not data:
            return None
        data = data.split(',')         # 序列化字符串转成 list 数组
        root = TreeNode(data.pop(0))   # 首项是根节点值,为其创建节点
        queue = [root]                 # 初始放入 root,待会出列考察
        while queue:
            node = queue.pop(0)        # 父节点出列考察
            if data:
                val = data.pop(0)            # pop 出左子节点
                if val != 'X':               # 左子节点是有效值
                    node.left = TreeNode(val) # 成为当前出列节点的左子节点
                    queue.append(node.left)   # 它是未来的父节点,入列等待考察
            if data:
                val = data.pop(0)             # pop 出右子节点
                if val != 'X':                # 右子节点是有效值
                    node.right = TreeNode(val)
                    queue.append(node.right)
        return root

        

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

参考:

  1. LeetCode题解