297\. 二叉树的序列化与反序列化

# 297. 二叉树的序列化与反序列化 (opens new window)

# Description

Difficulty: 困难

Related Topics: (opens new window), 深度优先搜索 (opens new window), 广度优先搜索 (opens new window), 设计 (opens new window), 字符串 (opens new window), 二叉树 (opens new window)

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

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

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

示例 1:

输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
1
2

示例 2:

输入:root = []
输出:[]
1
2

示例 3:

输入:root = [1]
输出:[1]
1
2

示例 4:

输入:root = [1,2]
输出:[1,2]
1
2

提示:

  • 树中结点数在范围 [0, 104] 内
  • -1000 <= Node.val <= 1000

# Solution

Language: JavaScript

二叉树的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。可以遍历树来完成上述任务。众所周知,我们一般有两个策略:广度优先搜索和深度优先搜索。

广度优先搜索可以按照层次的顺序从上到下遍历所有的节点

深度优先搜索可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。根据根节点、左节点和右节点之间的相对顺序,可以进一步将深度优先搜索策略区分为:

先序遍历

中序遍历

后序遍历

DFS(递归)

  • 递归遍历一棵树,重点关注当前节点,它的子树的遍历交给递归完成:
  • “serialize函数,请帮我分别序列化我的左右子树,我等你返回的结果,再拼接一下。”
  • 选择前序遍历,是因为 根|左|右根∣左∣右 的打印顺序,在反序列化时更容易定位出根节点的值。
  • 遇到 null 节点也要翻译成特定符号,反序列化时才知道这里是 null。

序列化的代码

const serialize = (root) => {
  if (root == null) {                  // 遍历到 null 节点
    return 'X';
  }
  const left = serialize(root.left);   // 左子树的序列化结果
  const right = serialize(root.right); // 右子树的序列化结果
  return root.val + ',' + left + ','+ right; // 按  根,左,右  拼接字符串
};

1
2
3
4
5
6
7
8
9

反序列化——也是递归

前序遍历的序列化字符串,就像下图

反序列化的代码

const deserialize = (data) => {
  const list = data.split(',');   // split成数组

  const buildTree = (list) => {   // 基于list构建当前子树
    const rootVal = list.shift(); // 弹出首项,获取它的“数据”
    if (rootVal == "X") {         // 是X,返回null节点
      return null;
    }
    const root = new TreeNode(rootVal); // 不是X,则创建节点
    root.left = buildTree(list);        // 递归构建左子树
    root.right = buildTree(list);       // 递归构建右子树
    return root;                        // 返回当前构建好的root
  };

  return buildTree(list); // 构建的入口
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

BFS 解法

序列化——很典型的 BFS

序列化的代码

const serialize = (root) => {
  const queue = [root];
  let res = [];
  while (queue.length) {
    const node = queue.shift(); // 考察出列的节点
    if (node) {                 // 是真实节点,带出子节点入列
      res.push(node.val);       // 节点值推入res
      queue.push(node.left);    // 子节点入列,不管是不是null节点都入列
      queue.push(node.right);    
    } else {                    // 是null节点,没有子节点入列
      res.push('X');            // X 推入res
    }
  }
  return res.join(',');  // 转成字符串
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

反序列化——也是BFS

下图是BFS得到的序列化字符串,和DFS得到的不同,它是一层层的。除了第一个是根节点的值,其他节点值都是成对的,对应左右子节点。

const deserialize = (data) => {
  if (data == 'X') return null;

  const list = data.split(',');  // 序列化字符串split成数组

  const root = new TreeNode(list[0]); // 获取首项,构建根节点
  const queue = [root];          // 根节点推入队列
  let cursor = 1;                // 初始指向list第二项

  while (cursor < list.length) { // 指针越界,即扫完了序列化字符串
    const node = queue.shift();  // 考察出列的节点

    const leftVal = list[cursor];      // 它的左儿子的值
    const rightVal = list[cursor + 1]; // 它的右儿子的值

    if (leftVal != 'X') {              // 是真实节点
      const leftNode = new TreeNode(leftVal); // 创建左儿子节点
      node.left = leftNode;                   // 认父亲
      queue.push(leftNode);                   // 自己也是父亲,入列
    }
    if (rightVal != 'X') {
      const rightNode = new TreeNode(rightVal);
      node.right = rightNode;
      queue.push(rightNode);
    }
    cursor += 2; // 一次考察一对儿子,指针加2
  }
  return root;  // BFS结束,构建结束,返回根节点
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * Encodes a tree to a single string.
 *
 * @param {TreeNode} root
 * @return {string}
 */
var serialize = function(root) {
    return JSON.stringify(root)
};

/**
 * Decodes your encoded data to tree.
 *
 * @param {string} data
 * @return {TreeNode}
 */
var deserialize = function(data) {
    return JSON.parse(data)
};

/**
 * Your functions will be called as such:
 * deserialize(serialize(root));
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * Encodes a tree to a single string.
 *
 * @param {TreeNode} root
 * @return {string}
 */
var serialize = function(root) {
    if (root == null) {                  // 遍历到 null 节点
        return 'X';
    }
    const left = serialize(root.left);   // 左子树的序列化结果
    const right = serialize(root.right); // 右子树的序列化结果
    return root.val + ',' + left + ','+ right; // 按  根,左,右  拼接字符串
};

/**
 * Decodes your encoded data to tree.
 *
 * @param {string} data
 * @return {TreeNode}
 */
var deserialize = function(data) {
    const list = data.split(',');

    const buildTree = (list) => {
        const rootVal = list.shift();
        if (rootVal === 'X') {
            return null
        }
        const root = new TreeNode(rootVal);
        root.left = buildTree(list);
        root.right = buildTree(list);
        return root;
    }
    return buildTree(list);
};

/**
 * Your functions will be called as such:
 * deserialize(serialize(root));
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
上次更新: 2022/8/25 下午2:49:04