序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中,其核心作用是对象状态的保存。

反序列化是从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息在内存中重建对象。

本文用 C++ 代码简单描述二叉树的序列化和反序列化。

前序序列化

我们将二叉树序列化为字符串,规定:空节点用 # 表示,每条数据用 , 隔开

序列化操作还是很简单的,只是在前序遍历时额外添加一些字符串操作,代码如下:

void serializePre_in(TreeNode* nd, string &str)
{
    if(nd == nullptr)
    {
        str += "#,";//容易忽略
        return;
    }
    str += to_string(nd->data);
    str += ",";
    serializePre_in(nd->left, str);
    serializePre_in(nd->right, str);
}


string serializePre(TreeNode* root)
{
    if(root == nullptr)
        return "";
    string str;
    serializePre_in(root, str);
    return str;
}

前序反序列化

前序反序列化的思路是:1)将前序序列化得到的字符串还原成原始、独立的整形数据,并压入队列;2)既然是前序序列化得到的结果,那么反序列化也应该用前序的方式去依次消费队列。代码如下:

#include<sstream> //stringstream
TreeNode* deserializePre_in(queue<string> &strQue)
{
    string str = strQue.front();
    strQue.pop(); //消费队列
    if(str == "#")
        return nullptr;
    TreeNode* nd = new TreeNode(stoi(str));
    nd->left = deserializePre_in(strQue);
    nd->right = deserializePre_in(strQue);
    return nd;
}

TreeNode* deserializePre(string str)
{
    if(str.empty())
        return nullptr;
    TreeNode* root;
    string token;
    queue<string> strQue;
    stringstream ss(str); //这里容易忽略初始化
    while(getline(ss, token, ',')) //‘,’作为界定符,每次划分得到的字符串赋值给token
        strQue.push(token);
    root = deserializePre_in(strQue);
    return root;
}

思路很清晰,需要注意是第 21~23 的字符串处理,这里是在以 , 为界定符分割字符串。

后序序列化

后序序列化和前序序列化无异,仅仅变动了添加字符串动作的位置,代码如下:

void serializePost_in(TreeNode* nd, string &str)
{
    if(nd == nullptr)
    {
        str += "#,";
        return;
    }
    serializePost_in(nd->left, str);
    serializePost_in(nd->right, str);
    str += to_string(nd->data);
    str += ",";
}

string serializePost(TreeNode* root)
{
    if(root == nullptr)
        return "";
    string str;
    serializePost_in(root, str);
    return str;
}

后序反序列化

后序反序列化就需要迂回了。你可不能直接将创建节点的动作放在进入左右子树动作的后面,连父节点都没有,何谈直接进入子节点呢?

二叉树的非递归遍历 一文中,非递归后序遍历综合了以下三种顺序:

头结点 -> 左孩子 -> 右孩子 (序1

头结点 -> 右孩子 -> 左孩子 (序2

左孩子 -> 右孩子 -> 头结点 (序3

其中序 1 即前序,序 3 即后序。换句话说,后序遍历可以这样得到:先访问头结点,再访问右孩子,然后访问左孩子,最后将访问结果反序。文字可能不直观,还是用图来表达吧,如下:

那么这种关系能用于反序列化吗?当然,我们只需要将后序序列化结果进行反序处理(变成序 2),然后在创建节点后先进入右孩子,再进入左孩子即可。代码如下:

TreeNode* deserializePost_in(stack<string> &stk)
{
    string str = stk.top();
    stk.pop();
    if(str == "#")
        return nullptr;
    TreeNode* nd = new TreeNode(stoi(str));
    nd->right = deserializePost_in(stk); //注意先进右
    nd->left = deserializePost_in(stk);  //再进左
    return nd;
}

TreeNode* deserializePost(string str)
{
    if(str.empty())
        return nullptr;
    TreeNode* root;
    string token;
    stringstream ss(str);
    stack<string> stk;
    while(getline(ss, token, ','))
        stk.push(token);
    root = deserializePost_in(stk);
    return root;
}

这里我们用栈而非队列来存放处理好的字符串,可以很自然地完成反序步骤。

层序序列化

层序序列化和反序列化都是几乎直接在层序遍历代码上稍作修改,清晰易懂。

string serializeLayer(TreeNode* root)  
{  
    if(root == nullptr)  
        return "";  
    string str;  
    TreeNode* tmp;  
    queue<TreeNode*> que;  
    que.push(root);  
    while(!que.empty())  
    {  
        tmp = que.front();  
        que.pop();  
        if(tmp == nullptr)/**易忽略**/  
  {  
            str.append("#,");  
            continue;  
        }  
        str.append(to_string(tmp->data)).append(",");  
        que.push(tmp->left);  
        que.push(tmp->right);  
    }  
    return str;  
}  

层序反序列化

TreeNode* generatenode(queue<string> &que)  
{  
    string tmp = que.front();  
    que.pop();  
    if(tmp != "#")  
    {  
        TreeNode* nd = new TreeNode(stoi(tmp));  
        return nd;  
    }  
    else  
    return nullptr;  
}  
  
TreeNode* deserializeLayer(const string &str)  
{  
    if(str.empty())  
        return nullptr;  
    queue<string> que_str;  
    queue<TreeNode*> que_node;  
    stringstream ss(str);  
    string token;  
    while(getline(ss, token, ','))  
        que_str.push(token);  
  
    TreeNode *tmp, *root;  
    root = generatenode(que_str);  
    if(root == nullptr)  
        return nullptr;  
    que_node.push(root);  
    while(!que_str.empty())  
    {  
        tmp = que_node.front();  
        que_node.pop();  
        tmp->left = generatenode(que_str);  
        tmp->right = generatenode(que_str);  
        if(tmp->left != nullptr) // 只放非nullptr节点;  
            que_node.push(tmp->left);  
        if(tmp->right != nullptr)  
            que_node.push(tmp->right);  
    }  
    return root;  
}

那么,为什么没有中序序列化和反序列化呢?因为序列化和反序列化是唯一对应的,前者能唯一导出后者,后者能唯一导出前者。而中序存在歧义,不同的两棵树可能存在相同的中序序列化结果,如下图:

因此我们无法使用中序来完成序列化和反序列化。

文章参考:什么是序列化和反序列化 ,左程云算法课

文章作者: 极简
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 后端技术分享
数据结构与算法
喜欢就支持一下吧