1. 问题描述
2. 样例说明
3. 解法一:先序遍历
算法思想
- 使用先序遍历的方式序列化二叉树,用
#
表示空,!
标志一个结点值的结束。当遇到非空节点时取出其值的字符串表示,再在后面加个!
标示着结尾,以便我们反序列化时确定节点的值。 - 先用string存储得到的字符序列,再转换为题目要求的
char*
字符串;在反序列化时使用引用的下标实时指向当前待处理的位置。
代码
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
void inorder(TreeNode *root,string &s){ // 注意这里要使用&
if(!root){
s+="#"; // 若为空节点用'#'表示
return;
}
s+=to_string(root->val)+"!"; // "val" + "!"
inorder(root->left,s); // 遍历左子树
inorder(root->right,s); // 遍历右子树
}
char* Serialize(TreeNode *root) {
string s;
inorder(root,s);
char *ans=new char[s.size()+1]; // 分配内存空间
strcpy(ans,s.c_str()); //将s变为char型指针
return ans;
}
TreeNode* deserialize(char *str, int& p){
if(str[p]=='#'){ // 若为'#',返回空指针
++p; // 记住要向后走一步
return nullptr;
}
bool sign=1; // sign标志当前结点的正负
if(str[p]=='-') sign=0, p++; // 若有符号,标记并向后走一步
int val=0; // 当前结点的绝对值
while(str[p]!='!'){ // 直到遇到结束符号,一直向后走
val=val*10+str[p]-'0';
p++;
}
if(!sign) val=-val; // 若为负,更改val符号
p++; // 记住要向后走一步,此时str[p] == '!'
TreeNode *root=new TreeNode(val);
root->left = deserialize(str, p); // 向左递归
root->right = deserialize(str, p); // 向右递归
return root; // 返回根节点
}
TreeNode* Deserialize(char *str) {
int p=0; // 指向str中待处理的位置
return deserialize(str, p);
}
};
时间空间复杂度分析
时间复杂度:O(n), n为节点数,执行一次先序遍历的时间复杂度为O(n)
空间复杂度:O(n), 节点数为n, 每个结点所占的字符数为常数,故使用的空间是O(n)的,当然还要算上递归使用的栈空间,也是O(n)的。
4. 解法二:层次遍历
算法思想
- 序列化:同理,用
!
标示数值的结束,用#
标示空节点,序列化的过程很简单,用队列完成层序遍历,先将根节点入队,然后每次出队一个元素就尝试将其左右儿子加入队列,若无就在字符串后加上’#’,否则加上"val" + "!"即可。 - 反序列化:反序列化就是一个层序遍历的逆过程,我们先对序列化的字符串做一下预处理,根据
!
对每个结点进行分割,用一个vector来存储每个结点所对应的字符串,然后就可以开始层序遍历了。首先将根节点入队,然后每次尝试从vector中取出来个节点,若不为空,则创建节点后入队,以此类推知道遍历完字符串即完成反序列化。
代码
class Solution {
public:
char* Serialize(TreeNode *root) {
string ret;
if(!root) return nullptr;
queue<TreeNode*> q;
q.emplace(root); // 根结点入队
ret += to_string(root->val) + '!'; // 记录节点值
while(q.size()){
TreeNode* p = q.front(); q.pop(); // 队头出队
if(p->left){ // 若不为空,加上"val" + "!"
ret += to_string(p->left->val) + '!';
q.emplace(p->left);
}
else ret += '#'; // 为空这加上'#'
if(p->right){
ret += to_string(p->right->val) + '!';
q.emplace(p->right);
}
else ret += '#';
}
char* ans = new char[ret.size() + 1];
strcpy(ans, ret.c_str());
return ans;
}
TreeNode* Deserialize(char *str) {
if(!str) return nullptr;
string data = string(str);
vector<string> vec; // 先对data预处理一下
for(int i = 0; i < data.size(); i++){
if(data[i] == '#') vec.push_back(string("#")); // 为空则用"#"表示
else{
int j = i;
while(data[j] != '!') ++j;
vec.push_back(data.substr(i, j - i)); // 不为空则用"val"表示
i = j;
}
}
// stoi()函数可以将string转换为int
TreeNode* root = new TreeNode(stoi(vec[0])); // 创建根节点
queue<TreeNode*> q;
q.emplace(root); // 根节点入队
int k = 1;
while(k < vec.size()){
TreeNode* p = q.front(); q.pop();
TreeNode *l = nullptr, *r = nullptr;
if(vec[k] != "#"){ // 若左儿子非空
l = new TreeNode(stoi(vec[k]));
q.emplace(l);
}
p->left = l;
if(vec[k + 1] != "#"){ // 若右儿子非空
r = new TreeNode(stoi(vec[k + 1]));
q.emplace(r);
}
p->right = r;
k += 2; // 指针向后后移动两步
}
return root;
}
};
时间空间复杂度分析
时间复杂度:O(n), n为节点数,层序遍历一次的时间复杂度为O(n)
空间复杂度:O(n), 序列化使用了一个队列,反序列化使用了一个vector,故时间复杂度为O(n)