二叉树的序列化与反序列化
通俗的说就是给定一个二叉树的根节点,用某种方法将树结构的信息存到一个字符串中,并且还可以用这一个字符串还原这棵树。
本文介绍两种方法,分别为递归法和迭代法。
剑指 Offer 37. 序列化二叉树
递归法
- 序列化:将树信息存到一个字符串中
将树的先序遍历结果存到字符串中,并将nullptr
用特殊符号#
来表示。注意节点的值要从整型变为字符串。
则存储的字符串信息为:msg = "1,2,#,#,3,4,#,#,5,#,#"
逗号的意义时,还原串信息时作为两个节点信息的分隔符
- 反序列化:通过之前存放树信息的那个字符串将树还原
细节处理,可以先把msg尾部添加一个 ',' 便于统一处理
msg中1,2,3,4都是字符,如果两位数,三位数,就是字符串。
需要先把他们翻译回整形,这样递归建树时便于处理。同时去除逗号这个对还原树操作无用信息。
特殊字符#,可以翻译成数据范围外的整型值
可以用整形数组存放处理之后的信息
则原 msg = "1,2,#,#,3,4,#,#,5,#,#," (第一步在最后加个逗号)
变为 (字符'#' 用32位极小值表示)
vector<int> msg = [1 2 -2147483648 -2147483648 3 4 -2147483648 -2147483648 5 -2147483648 -2147483648]
最后用这个msg信息开始建树即可
- 代码
/**
* 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 "#";
return to_string(root->val) + ',' + serialize(root->left) + ',' + serialize(root->right);
}
TreeNode* __deserialize(vector<int> &msg, int &ind) {
if (msg[ind] == INT32_MIN) {
++ind;
return nullptr;
}
TreeNode *s = new TreeNode(msg[ind++]);
s->left = __deserialize(msg, ind);
s->right = __deserialize(msg, ind);
return s;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
string t;
data += ',';
vector<int> msg;
for (int i = 0; data[i]; ++i) {
if (data[i] == ',') {
if (t == "#") msg.push_back(INT32_MIN);
else msg.push_back(stoi(t));
t = "";
continue;
}
t += data[i];
}
int ind = 0;
return __deserialize(msg, ind);
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
迭代法
- 序列化:将树信息存到一个字符串中
用广义表的形式存放树的信息
注意:
生成广义表用递归法,通过广义表还原树用迭代法
广义表还原树时,不用递归(系统栈),但要手动开辟栈结构辅助
相比递归法字符串的长度,广义表的长度更长。因为迭代需要存储树的回溯信息。
其实空间换时间——广义表相对长一点信息(空间),递归函数调用的开销(时间)
则存储的字符串信息为:msg = "1(2(,),3(4(,),5(,)))"
- 反序列化:通过之前存放树信息的那个字符串将树还原
手动开辟一个栈,存放节点指针,模拟系统栈
用一个变量flag_r表示当前节点是栈顶指针(父节点)的左子树还是右子树
用一个临时字符串str存单个节点信息
'(' :
Ⅰ.遇到这个字符则证明要处理当前节点的子节点了,所以当前节点信息已经通过str保存完整了。
Ⅱ.既然完整就用str这份信息新建一个树节点(转换为整型)。同时清空str
Ⅲ.看当前新节点是哪个节点的左子树还是右子树(栈顶元素指向当前点的父节点)
Ⅳ.将新节点压入栈中,表示要处理它的子节点了。同时flag_r = false,下一步处理左子树
')' : 已经处理完了当前父节点的两个孩子,栈顶元素弹出
',' : 表示要处理右孩子了,flag_r = true,并且str清空
其它:节点值的信息用str连接存下来
- 代码
/**
* 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 "";
return to_string(root->val) + "(" + serialize(root->left) + "," + serialize(root->right) + ")";
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
stack<TreeNode *> s;
TreeNode *ans = nullptr;
bool flag_r = false;
string str;
for (int i = 0; data[i]; ++i) {
if (data[i] == '(') {
TreeNode *root = new TreeNode(stoi(str));
if (!s.empty()) {
if (flag_r) s.top()->right = root;
else s.top()->left = root;
} else ans = root;
s.push(root);
flag_r = false;
str = "";
} else if (data[i] == ')') {
s.pop();
} else if (data[i] == ',') {
flag_r = true;
str = "";
} else str += data[i];
}
return ans;
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
} else str += data[i];
}
return ans;
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));