【LeetCode-树】二叉树的序列化与反序列化

题目描述

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:

你可以将以下二叉树:

    1
   / 
  2   3
     / 
    4   5

序列化为 "[1,2,3,null,null,4,5]"

说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。
题目链接: https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/

思路1

记录树的前序遍历序列(空节点也要记录,使用'#'表示),节点之间使用分隔符|进行分割。例如,示例中的树,其前序序列为1|2|#|#|3|4|#|#|5|#|#|,分割符的存在使得处理节点值是负数以及节点值是多位数字时更加方便。得到序列后,在反序列化时,首先将序列化的字符串按照分隔符|分开,然后根据序列使用先序创建二叉树的方法创建二叉树即可。代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root==nullptr) return "";

        string s = "";
        serializeCore(root, s);
        return s;
    }

    void serializeCore(TreeNode* root, string& s){
        if(root==nullptr){
            s += '#';
            s += '|';
            return;
        }

        s += to_string(root->val)+'|';
        serializeCore(root->left, s);
        serializeCore(root->right, s);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()) return nullptr;

        vector<string> tokens;
        int start = 0;
        for(int i=0; i<data.size(); i++){ // 将字符串序列分割成字符串vector
            if(data[i]=='|'){
                string temp = data.substr(start, i-start);
                start = i+1;
                tokens.push_back(temp);
            }
        }

        int cur = 0;
        TreeNode* root = deserializeCore(tokens, cur);
        return root;
    }

    TreeNode* deserializeCore(vector<string> tokens, int& cur){
        if(cur>=tokens.size()) return nullptr;
        if(tokens[cur]=="#") return nullptr;

        TreeNode* node = new TreeNode(atoi(tokens[cur].c_str()));
        cur++;
        node->left = deserializeCore(tokens, cur);
        cur++;
        node->right = deserializeCore(tokens, cur);
        return node;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
// 超时

该方法由于超时未通过,大概是因为分割字符串需要大量时间。

思路2

思路2的思想大致和思路1相同。思路1超时的原因是先对字符串进行分割,然后根据分割后的字符串数组建树,而分割字符串需要耗费大量时间。为了加快速度,可以边建树边分割字符串。我们使用两个指针left和right标记当前字符串的范围,用substr提取出当前字符后建树。代码如下(序列化代码和思路1一样,对反序列化代码进行了更改):

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root==nullptr) return "";

        string s = "";
        serializeCore(root, s);
        return s;
    }

    void serializeCore(TreeNode* root, string& s){
        if(root==nullptr){
            s += '#';
            s += '|';
            return;
        }

        s += to_string(root->val)+'|';
        serializeCore(root->left, s);
        serializeCore(root->right, s);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()) return nullptr;

        int left=0, right=0;    // left, right标记当前字符的范围
        TreeNode* root = deserializeCore(data, left, right);
        return root;
    }

    TreeNode* deserializeCore(string data, int& left, int& right){
        if(right>=data.length()) return nullptr;
        while(right<data.length() && data[right]!='|') right++;
        string curValStr = data.substr(left, right-left);
        if(curValStr=="#") return nullptr;

        TreeNode* node = new TreeNode(atoi(curValStr.c_str()));
        left = right+1; right++;
        node->left = deserializeCore(data, left, right);
        left = right+1; right++;
        node->right = deserializeCore(data, left, right);
        return node;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
  • 时间复杂度:O(n)
    n为节点个数
  • 空间复杂度:O(n)
    n为节点个数。

思路3

前面两种方法通过dfs建树,思路3使用bfs建树。首先保存树的层序遍历序列(空节点保存为#,节点之间使用|分割)。得到层序遍历代码后,再通过队列重建树,代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root==nullptr) return "";

        string s = "";
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            TreeNode* curNode = q.front(); q.pop();
            if(curNode==nullptr){
                s += "#";
                s += "|";
                continue;
            }else{
                s += to_string(curNode->val);
                s += "|";
                q.push(curNode->left);
                q.push(curNode->right);
            }
        }
        return s;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()) return nullptr;

        vector<string> tokens;
        int start = 0;
        for(int i=0; i<data.size(); i++){ // 将字符串序列分割成字符串vector
            if(data[i]=='|'){
                string temp = data.substr(start, i-start);
                start = i+1;
                tokens.push_back(temp);
            }
        }

        queue<TreeNode*> q;
        TreeNode* root = new TreeNode(atoi(tokens[0].c_str()));
        q.push(root);
        int i=1;
        while(i<tokens.size()){
            TreeNode* curNode = q.front();
            if(tokens[i]!="#"){
                TreeNode* leftNode = new TreeNode(atoi(tokens[i].c_str()));
                curNode->left = leftNode;
                q.push(leftNode);
            }
            i++;
            if(tokens[i]!="#"){
                TreeNode* rightNode = new TreeNode(atoi(tokens[i].c_str()));
                curNode->right = rightNode;
                q.push(rightNode);
            }
            i++;
            q.pop();
        }
        return root;
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

总结

  • 二叉树还可以通过先序+中序序列或者中序+后序序列还原,但这种方法成立的前提是树中没有重复的元素,而这个题不能保证这个条件的成立,所以不能使用这种方法;
  • 如果遍历过程中考虑了空节点(也就是将空节点记录到了遍历序列中),那么只靠单一的遍历序列(先序、中序、后序、层序中的一个)就能还原二叉树。