当前位置: 首页 > news >正文

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

一、什么是序列化与反序列化?

在计算机中,很多数据结构都是存在于内存里的,比如链表、二叉树、图等。

这些结构在程序运行时很好使用,但如果我们想把它们:

  • 保存到文件中

  • 存入数据库

  • 通过网络发送给另一台电脑

  • 程序退出后下次还能恢复

就不能直接保存内存中的指针关系。

这时就需要用到序列化反序列化


二、序列化的概念

序列化,就是把一个对象或数据结构转换成一种可以存储、传输的格式。

比如一棵二叉树:

1 / \ 2 3 / \ 4 5

它在内存中是由很多节点和指针连接起来的。

但是指针地址不能直接保存,因为程序重新运行后,内存地址会改变。

所以我们需要把这棵树转换成字符串,例如:

1,2,#,#,3,4,#,#,5,#,#

这个字符串就可以保存到文件中,也可以通过网络传输。

这个过程就叫做序列化


三、反序列化的概念

反序列化,就是把序列化后的字符串重新还原成原来的数据结构。

例如我们拿到字符串:

1,2,#,#,3,4,#,#,5,#,#

可以重新构造出原来的二叉树:

1 / \ 2 3 / \ 4 5

这个过程就叫做反序列化


四、为什么二叉树需要序列化?

普通数组的序列化很简单,例如:

[1, 2, 3, 4, 5]

可以直接转换成:

"1,2,3,4,5"

但是二叉树比较特殊。

二叉树不仅有节点值,还有结构关系:

1 / 2

1 \ 2

这两棵树的节点值都是12,但结构完全不同。

所以序列化二叉树时,不能只保存节点值,还必须保存空节点的位置。


五、为什么要记录空节点?

假设我们只用前序遍历保存节点值。

下面这棵树:

1 / 2

前序遍历结果是:

1,2

而下面这棵树:

1 \ 2

前序遍历结果也是:

1,2

这样就无法判断21的左孩子还是右孩子。

所以我们需要用一个特殊符号表示空节点,例如#

第一棵树可以序列化成:

1,2,#,#,#

第二棵树可以序列化成:

1,#,2,#,#

这样结构就明确了。


六、题目分析:LeetCode 297

题目要求

给定一棵二叉树,要求我们设计两个函数:

string serialize(TreeNode* root); TreeNode* deserialize(string data);

其中:

  • serialize:把二叉树转换成字符串

  • deserialize:把字符串重新还原成二叉树

题目不要求必须使用 LeetCode 官方格式,只要能够保证:

deserialize(serialize(root))

可以还原原来的树结构即可。


七、解决思路:前序遍历 + 空节点标记

二叉树常见的遍历方式有:

  • 前序遍历:根 左 右

  • 中序遍历:左 根 右

  • 后序遍历:左 右 根

  • 层序遍历:按层遍历

这道题可以用很多方式实现。

这里我们使用最经典的一种方法:

前序遍历 + 空节点标记


1. 序列化过程

按照前序遍历的顺序:

根节点 -> 左子树 -> 右子树

如果当前节点为空,就记录#

例如二叉树:

1 / \ 2 3 / \ 4 5

前序序列化过程如下:

1 2 # # 3 4 # # 5 # #

最终得到字符串:

1,2,#,#,3,4,#,#,5,#,#

2. 反序列化过程

反序列化时,也按照前序顺序读取字符串。

遇到数字,就创建节点。

遇到#,说明当前位置是空节点,返回nullptr

例如字符串:

1,2,#,#,3,4,#,#,5,#,#

解析过程如下:

1 作为根节点 2 作为 1 的左孩子 # 表示 2 的左孩子为空 # 表示 2 的右孩子为空 3 作为 1 的右孩子 4 作为 3 的左孩子 # 表示 4 的左孩子为空 # 表示 4 的右孩子为空 5 作为 3 的右孩子 # 表示 5 的左孩子为空 # 表示 5 的右孩子为空

这样就能完整还原原来的二叉树。


八、C++ 完整代码

#include <iostream> #include <sstream> #include <string> using namespace std; // LeetCode 中已经定义好了 TreeNode,这里写出来方便理解 struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; class Codec { public: // 序列化:将二叉树转换成字符串 string serialize(TreeNode* root) { string result; dfsSerialize(root, result); return result; } // 反序列化:将字符串重新转换成二叉树 TreeNode* deserialize(string data) { stringstream ss(data); return dfsDeserialize(ss); } private: // 前序遍历序列化 void dfsSerialize(TreeNode* root, string& result) { if (root == nullptr) { result += "#,"; return; } // 先保存当前节点 result += to_string(root->val) + ","; // 再保存左子树 dfsSerialize(root->left, result); // 最后保存右子树 dfsSerialize(root->right, result); } // 前序遍历反序列化 TreeNode* dfsDeserialize(stringstream& ss) { string token; getline(ss, token, ','); // 如果是空节点标记,返回 nullptr if (token == "#") { return nullptr; } // 创建当前节点 TreeNode* root = new TreeNode(stoi(token)); // 递归构造左子树 root->left = dfsDeserialize(ss); // 递归构造右子树 root->right = dfsDeserialize(ss); return root; } };

九、代码详细解释

1. serialize 函数

string serialize(TreeNode* root) { string result; dfsSerialize(root, result); return result; }

这个函数负责把二叉树转换成字符串。

真正的递归逻辑在dfsSerialize中完成。


2. dfsSerialize 函数

void dfsSerialize(TreeNode* root, string& result) { if (root == nullptr) { result += "#,"; return; } result += to_string(root->val) + ","; dfsSerialize(root->left, result); dfsSerialize(root->right, result); }

这个函数使用前序遍历:

根节点 -> 左子树 -> 右子树

如果当前节点为空,就加入:

#,

如果当前节点不为空,就加入节点值。

例如节点值是3,就加入:

3,

3. deserialize 函数

TreeNode* deserialize(string data) { stringstream ss(data); return dfsDeserialize(ss); }

这个函数把字符串放入stringstream中,方便我们按照逗号逐个读取。

例如字符串:

1,2,#,#,3,#,#,

每次读取一个片段:

1 2 # # 3 # #

4. dfsDeserialize 函数

TreeNode* dfsDeserialize(stringstream& ss) { string token; getline(ss, token, ','); if (token == "#") { return nullptr; } TreeNode* root = new TreeNode(stoi(token)); root->left = dfsDeserialize(ss); root->right = dfsDeserialize(ss); return root; }

这个函数的核心思想是:

  • 读取一个字符串片段

  • 如果是#,说明当前位置为空

  • 如果是数字,创建一个新节点

  • 递归构建它的左子树和右子树

因为序列化时使用的是前序遍历,所以反序列化时也必须按照前序顺序还原。


十、示例演示

输入二叉树:

root = [1,2,3,null,null,4,5]

对应结构:

1 / \ 2 3 / \ 4 5

序列化结果:

1,2,#,#,3,4,#,#,5,#,#,

反序列化后重新得到:

1 / \ 2 3 / \ 4 5

十一、为什么这种方法一定能还原原树?

关键在于:我们记录了空节点。

前序遍历本身只能确定访问顺序,但不能唯一确定树的结构。

加入空节点标记后,每个节点的左右孩子信息都被完整保存了。

例如:

1,2,#,#,3,#,#

表示:

1 / \ 2 3

而:

1,2,#,3,#,#,#

表示:

1 / 2 \ 3

虽然节点值相似,但因为空节点的位置不同,所以可以还原出不同的结构。

这就是空节点标记的作用。


十二、复杂度分析

假设二叉树中有n个节点。

时间复杂度

序列化时,每个节点访问一次。

反序列化时,每个节点也只创建一次。

所以时间复杂度为:

O(n)

空间复杂度

序列化字符串需要保存所有节点和空节点。

递归调用栈在最坏情况下,也就是树退化成链表时,需要O(n)的空间。

所以空间复杂度为:

O(n)

十三、层序遍历能不能做?

当然可以。

LeetCode 官方显示二叉树时,常用的是层序遍历格式:

[1,2,3,null,null,4,5]

也可以用 BFS 层序遍历来序列化。

例如:

1 / \ 2 3 / \ 4 5

层序序列化结果可以是:

1,2,3,#,#,4,5,#,#,#,#

不过层序遍历实现时需要用队列,而且要处理末尾多余的空节点。

相比之下,前序遍历写法更简洁,逻辑也更适合递归理解。


十四、常见错误总结

1. 没有记录空节点

错误写法:

1,2,3,4,5

这种只记录节点值,无法恢复树的结构。

二叉树序列化必须保存空节点信息。


2. 序列化和反序列化顺序不一致

如果序列化使用前序遍历:

根 左 右

反序列化也必须按照前序顺序解析。

不能序列化时用前序,反序列化时却按照层序处理。


3. 忘记处理负数

题目中说明:

-1000 <= Node.val <= 1000

所以节点值可能是负数。

使用stoi(token)可以正常处理负数。


4. 忘记处理空树

如果根节点为空:

root = []

序列化结果可以是:

#,

反序列化时读取到#,直接返回nullptr


十五、总结

二叉树的序列化与反序列化,本质上是:

把内存中的树结构转换成字符串,再从字符串恢复成原来的树结构。

这道题的核心不是遍历本身,而是如何完整保存树的结构信息。

最重要的一点是:

序列化二叉树时,必须记录空节点。

本文使用的方案是:

前序遍历 + 空节点标记

优点是:

  • 思路清晰

  • 代码简洁

  • 容易实现

  • 可以唯一还原二叉树结构

对于 LeetCode 297 这道题来说,这是一种非常经典且高效的解法。


十六、核心记忆

可以把这道题记成一句话:

序列化就是把树变成字符串,反序列化就是把字符串还原成树;为了保证能还原结构,必须把空节点也记录下来。

http://www.jsqmd.com/news/859765/

相关文章:

  • 2026 在线考试系统哪个好?功能、客户、方案、优势与服务全对比
  • ElevenLabs潮州话语音接入全链路方案(含潮汕八邑口音适配白皮书)
  • 操作简便吗?8款一键生成论文工具梯队榜,毕业护航!
  • 初次接入Taotoken,从注册到发出第一个请求的全流程耗时
  • 2026 科技改变财税:税慧盟,构建智能财税新生态 - 品牌企业智选官
  • ElevenLabs老挝文语音效果翻倍的3个隐藏参数:声调补偿权重、SIL分段阈值、Lao orthographic normalization开关(内部测试版配置文件限时放送)
  • 当“数字孪生”有了坐标、时序和一棵“会落叶的树”:NNU‑Campus‑Geo3DGS 数据集深度解读
  • 2025 年欧美明星人形机器人企业接连倒闭,中国企业融资却屡创新高,赛道冰火两重天!
  • 如何3步免费下载百度文库文档:PDF保存终极指南
  • ElevenLabs湖北话语音API调用性能暴跌47%?这才是真实原因——Nginx代理配置+方言token缓存策略深度优化方案
  • 国内主流燕窝线上品牌实测排行 品质与性价比对比 - 互联网科技品牌测评
  • Fastboot Enhance:如何通过图形化界面高效管理Android设备分区与Payload文件?
  • AI时代,那些还在知乎认真回答问题的人
  • 使用Taotoken CLI工具一键为团队所有网站项目配置统一API接入点
  • 利用 QiWe API 实现企业微信机器人消息双向交互
  • CANN-ops-math推理优化-昇腾NPU数学算子调优实战
  • SubAgent 进阶:LLM 策略、工具借用与 Skill 嵌套
  • 如何让Switch手柄在Windows电脑上完美工作:终极解决方案指南
  • OpenStack系列第二期:认证与镜像管理
  • 终极免费实时屏幕翻译工具:Translumo完全使用指南
  • 智能选岗APP实测:AI帮你筛岗位、查竞争比、规划全年考试,全程免费
  • 2026 成都高端西装定制权威评测:天府之国的商务休闲智慧 - 西装爱好者
  • 力扣——146.LRU缓存详解
  • 自媒体矩阵工具选型避坑!多个平台发布指南,新手也能选对工具
  • IDEA 如何配置 Live Template 快速生成常用代码片段?
  • 终极指南:使用Visual Studio Uninstaller彻底清理开发环境的5个关键步骤
  • 新手教程,在Windows虚拟机中从零开始使用Taotoken调用GPT模型
  • 上海黄浦区刑事律师法律服务观察与执业方向分析(2026) - 法律资讯
  • 2026MISC躲猫猫题目复盘
  • CANN 上跑 Llama3-70B:我踩了 5 个坑,这些经验值 3000 字