首页/数据结构/04-tree/二叉树的序列化与反序列化 🔗 在 Obsidian 中打开
数据结构 · 树与二叉树

二叉树的序列化与反序列化

难度 ★★重要度 ★★ 考查频率 低 二叉树序列化408
速查
序列化 = 把二叉树压成字符串,反序列化 = 还原。核心是选对遍历方式 + # 标记空子树中序不能单独用,前序/层序都可以。

核心概念

序列化是将二叉树转化为可存储 / 传输的字符串,反序列化是将字符串还原为二叉树。关键是选择合适的遍历方式,并用特殊标记表示空节点。

三种常见序列化方式

遍历方式序列化序列适用场景
前序遍历根 → 左 → 右最常用,LeetCode 297
层序遍历逐层从左到右直观,BFS 实现
后序遍历左 → 右 → 根较少单独使用
核心要点中序遍历无法唯一确定二叉树,因此不单独用中序做序列化。

空节点标记

必须用特殊符号(如 #null)标记空子树,否则无法唯一还原。

序列化:1,2,#,#,3,4,#,#,5,#,#
反序列化时遇到 # 就知道该位置无节点
为什么必须标记空只有把「空」也写进序列,遍历序列才与树形结构一一对应。$n$ 个节点恰好有 $n+1$ 个空子树位置,所以带空标记的前序序列长度是 $2n+1$。

前序遍历序列化详解

序列化过程

function serialize(root):
    if root == null:
        return "#,"
    result = root.val + ","
    result += serialize(root.left)
    result += serialize(root.right)
    return result

示例二叉树:

        1
       / \
      2   3
         / \
        4   5

前序序列化结果:1,2,#,#,3,4,#,#,5,#,#

反序列化过程

function deserialize(data):
    nodes = data.split(",")  → ["1","2","#","#","3","4","#","#","5","#","#"]
    index = 0(全局指针)

    function build():
        val = nodes[index]
        index++
        if val == "#":
            return null
        node = new TreeNode(val)
        node.left  = build()    // 递归构建左子树
        node.right = build()    // 递归构建右子树
        return node

    return build()
关键index 必须是全局(或引用传递)指针 —— 递归构建左子树时它会前进,返回后不能回退,否则右子树会读错位置。

层序遍历序列化详解

序列化(BFS)

对上面同一棵示例树:

序列化结果:1,2,3,#,#,4,5,#,#,#,#

逐层遍历,空节点也写入结果(用 # 标记),直到队列中不再有真实节点。

反序列化(BFS)

nodes = ["1","2","3","#","#","4","5","#","#","#","#"]
         i=0  i=1  i=2  i=3  i=4  i=5  i=6  ...

root = TreeNode(1), 入队
取出 1: 左孩子=nodes[1]=2, 右孩子=nodes[2]=3, 入队
取出 2: 左孩子=nodes[3]=#, 右孩子=nodes[4]=#(跳过)
取出 3: 左孩子=nodes[5]=4, 右孩子=nodes[6]=5, 入队
取出 4: 左孩子=nodes[7]=#, 右孩子=nodes[8]=#(跳过)
取出 5: 左孩子=nodes[9]=#, 右孩子=nodes[10]=#(跳过)
完成
对照记忆前序序列化用栈 / 递归(DFS),层序序列化用队列(BFS)。反序列化时前序靠「全局下标」,层序靠「队列 + 每次取两个值」。

手算示例

例题 1:前序序列化

题目:对以下二叉树进行前序序列化。

        A
       / \
      B   C
     /   / \
    D   E   F

序列化过程(前序:根 → 左 → 右):

步骤访问节点输出说明
1AA,根节点
2BA,B,A 的左孩子
3DA,B,D,B 的左孩子
4D.left = #A,B,D,#,D 无左孩子
5D.right = #A,B,D,#,#,D 无右孩子
6B.right = #A,B,D,#,#,#,B 无右孩子
7C…,C,A 的右孩子
8E…,C,E,C 的左孩子
9E.left = #…,C,E,#,E 无左孩子
10E.right = #…,C,E,#,#,E 无右孩子
11F…,E,#,#,F,C 的右孩子
12F.left = #…,F,#,F 无左孩子
13F.right = #…,F,#,#F 无右孩子

最终结果:A,B,D,#,#,#,C,E,#,#,F,#,#

校验长度:$2n+1 = 2\times 6 + 1 = 13$ 个元素 ✓(6 个真实节点 + 7 个 #)。

例题 2:层序序列化

同一棵树,按完全二叉树下标思路排布(结点 $i$ 的左右孩子在 $2i+1$、$2i+2$):

nodes = [A, B, C, D, #, E, F, #, #, #, #, #, #]
下标      0  1  2  3  4  5  6

结果:A,B,C,D,#,E,F,#,#,#,#,#,#

易错层序序列化时,B 的两个孩子(D 与空)必须在 B 出队时立刻写入,不能等到最后补。手算时按「每出队一个真实结点,就写它的左右两个位置」逐个推进最稳。

中序遍历的局限性

中序遍历无法唯一确定二叉树。例如以下两棵树的中序结果完全相同:

树1:     A          树2:     A
        /                     \
       B                       B

中序都是:B,A(加 null 标记也一样:#,B,#,A,#,#)

但前序可以区分:树 1 → A,B,#,#,#,树 2 → A,#,B,#,#

本质原因中序序列不含「根在哪」的信息 —— 根被埋在序列中间,左右子树的分界无法从序列本身判断。前序 / 后序的第一个 / 最后一个元素永远是根,因此可递归切分。

408 考试重点

四类题型
  1. 手动模拟序列化:给定二叉树,写出前序 / 层序序列化结果。
  2. 手动模拟反序列化:给定序列,画出还原的二叉树。
  3. 判断序列合法性:给定一个序列,判断能否还原为合法二叉树。
  4. 唯一性问题:理解哪些遍历方式(组合)能唯一确定二叉树。
序列(组合)能否唯一确定
带空标记的前序
带空标记的后序
带空标记的层序
带空标记的中序
不带空标记的「前序 + 中序」
不带空标记的「前序 + 后序」

记忆卡片

前序与层序序列化的核心区别?
前序用递归(DFS,根→左→右);层序用队列(BFS,逐层从左到右)。两者都需用 # 标记空节点。
为什么不能只用中序做序列化?
中序无法唯一确定结构 —— 只有左孩子与只有右孩子的两棵树,中序结果相同。
前序反序列化的核心思路?
全局索引依次读值:遇 # 返回 null,否则建节点后递归构建左、右子树。
层序序列化中空节点的孩子要入队吗?
不需要。反序列化时对每个非空节点,从序列中取两个值作左、右孩子。
n 个节点的前序序列化有多少元素?
$2n+1$ 个:n 个真实节点 + $(n+1)$ 个 #
判断序列合法性的快速方法?
看「槽位」:初始 1 个槽,读到真实值消耗 1 槽、新增 2 槽;读到 # 消耗 1 槽。中途槽为 0 或结束时槽不为 0 → 非法。

交互动画 · 前序序列化逐步生成

A B C D E F # # # # # # #
(尚未开始)
点击「▶ 播放」逐步观察前序序列化 A,B,D,#,#,#,C,E,#,#,F,#,# 的生成
看动画抓重点橙色 = 当前正在写出的位置,绿色 = 已写入的真实节点,米色虚框 = 已写入的 #。注意 B 的右空位(#)在 D 的两个空位之后 才输出 —— 这正是递归返回的顺序。

相关知识点

(暂无关联知识点)

↑ 源笔记 front-matter 中 related 为空;本页右上「在 Obsidian 中打开」可跳回源笔记。