序列化二叉树(C++)

目录

1 问题描述

1.1 示例1

1.2 示例2

2 解题思路

3 代码实现

4 代码解析

4.1 序列化函数 Serialize(TreeNode *root) 的起始部分

4.2 初始化队列,准备进行层序遍历

4.3 层序遍历二叉树并构造字符串

4.4 处理非空节点

4.5 转换字符串并返回

4.6 初始化根节点

4.7 遍历字符串,逐层构造二叉树

4.8 构造左子节点

4.9 构造右子节点

5 总结


1 问题描述

请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)
二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,可以根据层序遍历的方案序列化,如下图:

序列化二叉树(C++)_第1张图片

层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}"构造成如上的二叉树。

再举一个例子

序列化二叉树(C++)_第2张图片

层序序列化(即用函数Serialize转化)如上的二叉树转为"{5,4,#,3,#,2}",再能够调用反序列化(Deserialize)将"{5,4,#,3,#,2}构造成如上的二叉树。

当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。

数据范围:节点数 n≤100n≤100,树上每个节点的值满足 0≤val≤1500≤val≤150

要求:序列化和反序列化都是空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

1.1 示例1

输入:

{1,2,3,#,#,6,7}

返回值:

{1,2,3,#,#,6,7}

1.2 示例2

输入:

{8,6,10,5,7,9,11}

返回值:

{8,6,10,5,7,9,11}

2 解题思路

序列化时,首先检查根节点是否为空,若为空,则返回仅包含“#”的字符串。否则,使用队列进行层序遍历,将节点值按顺序存入字符串流,空节点用“#”表示,并以逗号分隔,最后转换为字符串并返回。反序列化时,首先检查字符串是否为空或仅包含“#”,若是,则返回空指针。否则,使用队列辅助构造二叉树,先创建根节点并入队,然后逐步读取字符串中的值,依次构造左右子节点,并将非空节点加入队列,直到字符串解析完毕,最终返回重建的二叉树。

3 代码实现

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
#include 
#include 
#include 
#include 
#include 
class Solution {
public:
    char* Serialize(TreeNode *root) {
        if (!root)
        {
            char* res = (char*)malloc(2);
            strcpy(res, "#");
            return res;
        }
        queue q;
        q.push(root);
        stringstream ss;
        while (!q.empty())
        {
            TreeNode* node = q.front();
            q.pop();

            if (node)
            {
                ss << node->val << ",";
                q.push(node->left);
                q.push(node->right);
            }
            else {
                ss << "#,";
            }
        }
        string resStr = ss.str();
        resStr.pop_back();
        char* res = (char*)malloc(resStr.size() + 1);
        strcpy(res, resStr.c_str());
        return res;
    }
    TreeNode* Deserialize(char *str) {
        if (!str || strcmp(str, "#") == 0) return nullptr;
        stringstream ss(str);
        string val;
        getline(ss, val, ',');
        TreeNode* root = new TreeNode(stoi(val));
        queue q;
        q.push(root);
        while (!q.empty())
        {
            TreeNode* node = q.front();
            q.pop();

            if (getline(ss, val, ','))
            {
                if (val != "#")
                {
                    node->left = new TreeNode(stoi(val));
                    q.push(node->left);
                }
            }
            if (getline(ss, val, ','))
            {
                if (val != "#")
                {
                    node->right = new TreeNode(stoi(val));
                    q.push(node->right);
                }
            }
        }
        return root;
    }
};

4 代码解析

4.1 序列化函数 Serialize(TreeNode *root) 的起始部分

char* Serialize(TreeNode *root) {
    if (!root)
    {
        char* res = (char*)malloc(2);
        strcpy(res, "#");
        return res;
    }

Serialize 函数用于将二叉树转换为字符串格式。如果根节点 root 为空,则返回仅包含 "#" 的字符串,表示空树。这里使用 malloc(2) 申请两个字节的内存,其中一个存储 #,另一个存储字符串结束符 \0。这样可以正确表示空树的序列化结果,并保证该字符串可以被正确解析。

4.2 初始化队列,准备进行层序遍历

queue q;
q.push(root);
stringstream ss;

创建 queue q 作为辅助数据结构,并将根节点入队。队列用于按层遍历树的所有节点。同时,创建 stringstream ss 用于存储序列化的字符串。stringstream 允许高效地处理字符串拼接操作,而不会产生额外的内存开销。

4.3 层序遍历二叉树并构造字符串

while (!q.empty())
{
    TreeNode* node = q.front();
    q.pop();

进入 while 循环,条件是 q 不为空,即队列中仍有未处理的节点。每次循环,从队列头部取出一个节点 node,然后将其从队列中移除。这样可以按照层序遍历的顺序依次处理树中的所有节点。

4.4 处理非空节点

if (node)
{
    ss << node->val << ",";
    q.push(node->left);
    q.push(node->right);
}

如果当前节点 node 不是 NULL,则将其 val 转换为字符串并存入 stringstream,同时添加 "," 作为分隔符。然后,将 nodeleftright 子节点依次入队,确保它们稍后也能被处理。这种做法可以保持节点的层次顺序,从而正确重建二叉树。

4.5 转换字符串并返回

string resStr = ss.str();
resStr.pop_back();
char* res = (char*)malloc(resStr.size() + 1);
strcpy(res, resStr.c_str());
return res;

ss.str()stringstream 转换为标准 string,存入 resStr 变量。由于序列化过程中最后会多出一个 ",",需要使用 resStr.pop_back() 删除它。接着,用 mallocres 申请适当大小的内存,并用 strcpy 复制 resStr 的内容到 res,然后返回 res

4.6 初始化根节点

stringstream ss(str);
string val;
getline(ss, val, ',');
TreeNode* root = new TreeNode(stoi(val));
queue q;
q.push(root);

使用 stringstream ss(str) 解析输入字符串,并通过 getline(ss, val, ',') 读取第一个值 val。然后使用 stoi(val) 将其转换为整数,创建根节点 root。同时,创建 queue q 并将 root 入队,以便后续逐层构造二叉树。

4.7 遍历字符串,逐层构造二叉树

while (!q.empty())
{
    TreeNode* node = q.front();
    q.pop();

进入 while 循环,条件是 q 不为空,即仍有未处理的节点。从队列头部取出当前节点 node,然后将其从队列中移除。每次循环都会处理 node 的左右子节点,并将新的子节点加入队列,以便后续继续构造二叉树。

4.8 构造左子节点

if (getline(ss, val, ','))
{
    if (val != "#")
    {
        node->left = new TreeNode(stoi(val));
        q.push(node->left);
    }
}

使用 getline(ss, val, ',') 读取字符串中的下一个值 val,表示当前节点的左子节点。如果 val 不是 "#",说明该位置存在有效节点,于是创建新的 TreeNode 并赋值给 node->left,然后将该新节点入队,以便后续处理。

4.9 构造右子节点

if (getline(ss, val, ','))
{
    if (val != "#")
    {
        node->right = new TreeNode(stoi(val));
        q.push(node->right);
    }
}

同样使用 getline(ss, val, ',') 读取字符串中的下一个值 val,表示当前节点的右子节点。如果 val 不是 "#",说明该位置存在有效节点,于是创建新的 TreeNode 并赋值给 node->right,然后将该新节点入队,以便后续处理。

5 总结

本文详细解析了二叉树的序列化和反序列化的实现方法。首先,通过层序遍历方式进行序列化,使用 # 代表空节点,并利用 queuestringstream 构造序列化字符串。反序列化时,首先检查输入是否为空,随后利用 queue 辅助构造二叉树,逐步恢复原树结构。整个过程的时间和空间复杂度均为 O(n)O(n),保证了高效性。代码解析部分逐步讲解了序列化和反序列化的每个关键步骤,使读者能够清晰理解并灵活运用该方法。

你可能感兴趣的:(c++,算法,数据结构)