文章目录
三个相同题目:
297. 二叉树的序列化与反序列化
LCR 156. 序列化与反序列化二叉树
LCR 048. 二叉树的序列化与反序列化
题目要求:
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
一、基础知识
1.1 序列化和反序列基础知识
- 如果你的序列化结果中包含空指针的信息,且你只给出一种遍历顺序,也要分两种情况:
- 如果你给出的是前序、后序或层序,那么你可以还原出唯一的一棵二叉树。
- 如果你给出的是中序,那么你无法还原出唯一的一棵二叉树
- 如果你的序列化结果中不包含空指针的信息,且你只给出两种遍历顺序,也要分两种情况:
- 如果你给出的是前序和中序,或者后序和中序,那么你可以还原出唯一的一棵二叉树。
- 如果你给出前序和后序,那么你无法还原出唯一的一棵二叉树。
我们序列化二叉树时使用包含空指针信息的,这样我们只需要按照一种顺序序列化二叉树并且可以直接还原二叉树了。空指针的值我们用#
表示。转换思想在详细说明里解释。
- 这样我们使用一种遍历顺序序列化二叉树后,这个序列和二叉树就是一一对应的了。换句话说,我们将一个复杂树结构转成一个直接比对的字符串了。
- 我们可以在脑子里灌入这样的思想:二叉树和一个序列唯一对应,如果不在意二叉树的结构的问题,要判断两颗二叉树相同,实际上就是转换成判断序列是否相同即可。二叉树就是序列!
序列可以用string存储。
1.2 string与int转化STL函数
to_string
to_string(int)
:传入一个整型,转换成string类型。
stoi
stoi(string)
:传入一个string类型,转换成一个整型。
二、详解说明
本文使用的二叉树结构:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
注意在序列化过程中:
- 需要使用其他符号区分不同值的边界,如逗号。
- 在实现时的逗号,可以这样划分:[123,][#,][#,],在实现时我们分模块实现就能更简单。
123,#,#,
- 反序列化求值时注意负数。
- 前序,后序和层序实现方法大同小异。
- 能实现反序列化且的机理实际上就是非叶节点都有左右子树,叶节点左右子树就在自己附近直接生成,它往上返回实际上就代表了该非叶结点确定了一个儿子,依次往上发现只要前面只有俩就必然可以是它左右子树了。然后实现的时候用逗号分割,实际上在反序列化的时候这里只是逗号点麻烦。
2.1 基于前序遍历的序列化和反序列化
对于一个前序序列而言:在二叉树中表示是这样的 [ 根 ] , [ 左子树 ] , [ 右子树 ] [根],[左子树],[右子树] [根],[左子树],[右子树](逗号区分不同值的边界)
- 我们很明显可以知道,当前序列的首字符是根,我们得到左子树后连接在根的左子树上,得到右子树后连接在根的右子树上。
- 由于空指针我们用
#
表示,只有空指针没有儿子 - 之后的后根和层序是差不多的。
反序列化使用递归的方法:
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(!root) return "#,";
return to_string(root->val) + "," + serialize(root->left) + serialize(root->right);
//string : 根 + 左子树序列 + 右子树序列 -->(前序序列)
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string s) {
data = s;
return Deserialize();
}
private:
string data;
int idx = 0;
TreeNode * Deserialize(){
if(data[idx] == '#') {idx += 2;return nullptr;}//#, 模块
int start = idx;
while(data[idx++] != ',');
TreeNode * root = new TreeNode(stoi(data.substr(start,idx-start-1)));//当前序列的首字符是根
root->left = Deserialize();//先生成左子树,连接上
root->right = Deserialize();//然后生成右子树,连接上
return root;
}
};
- 为何在实现的时候不直接使用函数
deserialize
?- 因为它每次传参是拷贝的,速度很慢
2.2 基于后序遍历的序列化和反序列化
反序列化使用栈实现的方法:(前序也可以,不过不方便)
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(!root) return "#,";
return serialize(root->left) + serialize(root->right) + to_string(root->val) + ",";
//string : 左子树序列 + 右子树序列 + 根 -->(后序序列)
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
stack<TreeNode *> sta;
for(int i = 0;i < data.size();){//我们要注意string的最后一个是',',因此要提前退出
if(data[i] == '#') {sta.push(nullptr);i += 2;}
else{
int start = i;
while(data[i++] != ',');
TreeNode * cur = new TreeNode(stoi(data.substr(start,i-start-1)));
cur->right = sta.top();sta.pop();
cur->left = sta.top();sta.pop();
sta.push(cur);
}
}
return sta.top();
}
};
2.3 基于层序遍历的序列化和反序列化
反序列化使用队列实现的方法:
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string s = "";
queue<TreeNode *> q;
q.push(root);
while(!q.empty()){
TreeNode * cur = q.front();q.pop();
if(!cur){s += "#,";}
else{
s += to_string(cur->val) + ",";
q.push(cur->left);
q.push(cur->right);
}
}
return s;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
queue<TreeNode *> q;
int i = 0;
TreeNode * ans = deal(data,i);
q.push(ans);
for(;i < data.size();){
TreeNode * root = q.front();q.pop();
root->left = deal(data,i);
root->right = deal(data,i);
if(root->left) q.push(root->left);
if(root->right) q.push(root->right);
}
return ans;
}
private:
TreeNode * deal(string & data,int & i){//生成一个结点
if(data[i] == '#'){
i += 2;
return nullptr;
}
int start = i;
while(data[i++] != ',');
return new TreeNode(stoi(data.substr(start,i-start-1)));
}
};
// 1 , 2 , 3 , # , # , # , # ,
三、例题——652. 寻找重复的子树
在了解序列化之后,我们知道,序列和二叉树一一对应。
因此我们寻找重复子树可以直接寻找构造的序列来判断二叉树是否有相同。
本题并不需要反序列化,毕竟只需要存储二叉树结点,root
是知道的,直接存就行了。
class Solution {
public:
unordered_map<string,int> st;
vector<TreeNode *> ans;
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
find(root);
return ans;
}
private:
string find(TreeNode * root){//为防止重复遍历,我们用后序遍历,这样可以减少重复。边后序遍历边构造root的串串
if(!root) return "#,";
//构造root的string序列!
string s = find(root->left) + find(root->right) + to_string(root->val) + ",";
if(st[s] == 1) ans.push_back(root);
st[s]++;
return s;
}
};