297. 二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
二叉树的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。可以遍历树来完成上述任务。众所周知,我们一般有两个策略:BFS / DFS。
BFS 可以按照层次的顺序从上到下遍历所有的节点。
DFS 可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。根据根节点、左节点和右节点之间的相对顺序,可以进一步将DFS策略区分为:
- 先序遍历
- 中序遍历
- 后序遍历
前序遍历的实现
- 采用前序遍历的实现,比较简单
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
using namespace std;
/**
* Definition for a binary tree node.
*/
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
//方法1:DFS,
//采用前序遍历最简单,那就用前序遍历实现
//序列化
string serialize(TreeNode* root){
if(root==nullptr) return "#,";//遇到nullptr结点
string left=serialize(root->left);
string right=serialize(root->right);
return to_string(root->val)+','+left+right;//根|左|右
}
//反序列化
//先写一个分割字符串的函数split()
vector<string> split(const string& str,const string delim){
if(str=="") return {};
vector<string> res;
//为了利用strtok(char* str,char* delim)函数,先将string转为char*
char* cstr=new char[str.length()+1];
strcpy(cstr,str.c_str());
char* cdelim=new char[delim.length()+1];
strcpy(cdelim,delim.c_str());
char* p=strtok(cstr,cdelim);
while(p){
string s=p;
res.push_back(s);
p=strtok(NULL,cdelim);
}
delete cstr;
delete cdelim;
return res;
}
//辅助函数
TreeNode* buildTree(vector<string>& strVec,int& i){
if(strVec[i]=="#")
return nullptr;
TreeNode* root=new TreeNode(stoi(strVec[i]));
root->left=buildTree(strVec,++i);
root->right=buildTree(strVec,++i);
return root;
}
//最后反序列化
TreeNode* deSerialize(string& res){
int i=0;
string delim=",";
vector<string> result=split(res,delim);
TreeNode* root=buildTree(result,i);
return root;
}
//前序遍历
void preOrderBST(TreeNode* root){
if(root==nullptr)
return ;
cout<<root->val<<",";
preOrderBST(root->left);
preOrderBST(root->right);
}
//测试
int main(){
TreeNode* root=new TreeNode(-1);
root->left=new TreeNode(9);
root->right=new TreeNode(2);
root->left->left=new TreeNode(8);
root->left->right=new TreeNode(10);
root->right->left=nullptr;
root->right->right=nullptr;
string result=serialize(root);
cout<<result<<endl;
TreeNode* root2=deSerialize(result);
preOrderBST(root2);
system("pause");
return 0;
}
层序遍历的实现
- 采用层序遍历,比较麻烦
自己写的一份很难看,没有提前分割字符串,导致遍历起来很繁杂。。。
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string result;
if(root==nullptr){
//result+="null";
return "[]";
}
result+="[";
//层序遍历二叉树
queue<TreeNode*> q;
q.push(root);
TreeNode* cur;
while(q.size()){
cur=q.front();
q.pop();
if(cur!=nullptr){
//val可能有正负号,但是to_string能处理正负数的情景
result+=to_string(cur->val);
result+=',';
q.push(cur->left);
q.push(cur->right);
}else
result+="null,";
}
//result="[1,2,3,null,null,4,5,null,null,null,null,"
result.pop_back();
//result="[1,2,3,null,null,4,5,null,null,null,null"
//还需要把result结尾的null删除掉
int i=result.size()-1;
for(;i-4>=0;){
if(result[i-3]=='n'&&result[i]=='l'){
for(int j=0;j<=4;j++){
//尾部删除",null"
result.erase(i--);
}
}else
break;
}
//result="[1,2,3,null,null,4,5"
return result+"]";
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
int i=0;
int val=0;
bool minimum=false;
bool digitOrNull=false;
bool leftOrRight=true;
TreeNode*root;
TreeNode* cur;
queue<TreeNode*> q;
//判断"[]"的特殊情况
if(data[i]=='['&&data[i+1]==']')
return nullptr;
while(i<data.size()){
if(data[i]=='[')
i++;
//可能有正负号
if(data[i]=='-')
{
i++;
minimum=true;
digitOrNull=true;
}else if(data[i]=='+'){
i++;
minimum=false;
digitOrNull=true;
}else{//无符号正数
minimum=false;
digitOrNull=true;
}
//遇到数字,是否要考虑溢出呢?不用因为序列化时节点中的val就是int
if(data[i]>='0'&&data[i]<='9'){
digitOrNull=true;
val=val*10+(data[i]-'0');
i++;
}
//遇到null
if(data[i]=='n'){
i=i+4;
digitOrNull=false;
}
if(data[i]==','||data[i]==']'){
//','||']'前面是数字
if(digitOrNull){
//根结点
if(q.size()==0){
if(minimum)//负数
root=new TreeNode(-val);
else
root=new TreeNode(val);
q.push(root);
}else{//非根结点
//取队列头元素
cur=q.front();
//左结点
if(leftOrRight){
if(minimum){//负数
cur->left=new TreeNode(-val);
q.push(cur->left);
}else{
cur->left=new TreeNode(val);
q.push(cur->left);
}
//处理完左结点,下次处理右结点
leftOrRight=false;
}else{//右结点
if(minimum){//负数
cur->right=new TreeNode(-val);
q.push(cur->right);
}
else{
cur->right=new TreeNode(val);
q.push(cur->right);
}
//处理完右结点,下次处理左结点
leftOrRight=true;
//处理完右结点,队列头元素要删除
q.pop();
}
}
//把val清零
val=0;
}else{//','||']'前面是null
//根节点不会为null
cur=q.front();
if(leftOrRight){
cur->left=nullptr;
leftOrRight=false;
}else{
cur->right=nullptr;
leftOrRight=true;
q.pop();
}
}
i++;
}
}
return root;
}
};
参考https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/solution/shou-hui-tu-jie-gei-chu-dfshe-bfsliang-chong-jie-f/
重新写一份基于层序遍历的二叉树序列化与反序列化。
https://blog.csdn.net/qq_26437925/article/details/51914802
char* Serialize(TreeNode *root) {
if (root == NULL)
return NULL;
queue<TreeNode*> sta;
sta.push(root);
string s = "";
char str[10];
while (!sta.empty())
{
while (!sta.empty())
{
TreeNode* tmp = sta.front();
sta.pop();
if (tmp != NULL)
{
sta.push(tmp->left);
sta.push(tmp->right);
sprintf(str, "%d", tmp->val);
s += str;
}
else
{
s += "#";
}
s += " ";
}
}// while
int end = s.length() - 1;
while (s[end] == '#' || s[end] == ' ')
end--;
char *rs = new char[end + 2];
//strcpy(rs, s.c_str());
for (int i = 0; i <= end; i++)
rs[i] = s[i];
rs[end + 1] = '\0';
return rs;
}
TreeNode* Deserialize(char *str) {
if (str == NULL)
return NULL;
int len = strlen(str);
if (len == 0)
return NULL;
int numsCount = 0;
vector<TreeNode*> nums;
string s(str);
stringstream ss(s);
string word;
while (ss >> word)
{
// cout << word << endl;
// nums.push_back(word);
if (word == "#") {
nums.push_back(NULL);
}
else{
int tmp = atoi(word.c_str());
nums.push_back(new TreeNode(tmp));
}
numsCount++;
}
int q = 0; // BFS层次,一个一个的处理节点
int p = 1; // 相当于q的左孩子
while (p < numsCount)
{
if (nums[q] == NULL){
q++;
}
else{
if (p < cnt)
nums[q]->left = nums[p];
if (p + 1 < cnt)
nums[q]->right = nums[p + 1];
p += 2;
q++;
}
}
TreeNode* root = nums[0];
return root;
}
- 这里还写了一份代码,从标准输入中键入字符,然后重建二叉树,序列化,反序列化,
但是只能一次捕获一个字符作为节点的数,所以只能节点的数只能限制在0~9。所以这份代码并没有使用性,仅仅做个记录而已。
最重要的是提醒自己使用getchar捕获标准输入中的字符时,键入’\n’才开始从输入缓冲中每次取一个字符;
第二次调用createBST时,上一次留在输入缓存中的’\n’会被getchar()捕获到,所以需要判断if(’\n’!=ch)才开始重建。
//二叉树的序列化与反序列化
#include<iostream>
#include<string>
using namespace std;
typedef struct BSTNode{
int val;
BSTNode* left;
BSTNode* right;
BSTNode(int value):val(value),left(nullptr),right(nullptr){}
}BSTNode;
//从标准输入中创建二叉树
BSTNode* createBST(){
char ch=getchar();
if('\n'!=ch){
if('#'==ch)
return nullptr;
else{
BSTNode* root=new BSTNode(ch-'0');
root->left=createBST();
root->right=createBST();
return root;
}
}else{
createBST();
}
}
//前序遍历
void preOrderBST(BSTNode* root){
if(root==nullptr)
return ;
cout<<root->val<<",";
preOrderBST(root->left);
preOrderBST(root->right);
}
//序列化,前序遍历
void serializeBST(BSTNode* root,string& result){
if(root==nullptr){
result+='#';
return ;
}
result+=root->val+'0';
serializeBST(root->left,result);
serializeBST(root->right,result);
}
//反序列化,前序遍历
BSTNode* deSerializeBST(string& result,int i=0){
if('#'==result[i])
return nullptr;
BSTNode* root=new BSTNode(result[i]);
root->left=deSerializeBST(result,i+1);
root->right=deSerializeBST(result,i+1);
return root;
}
int main(){
//从标准输入中重建二叉树root1
BSTNode* root1=createBST();
cout<<"preOrderBST(root1):\n";
preOrderBST(root1);
cout<<'\n';
//从标准输入中重建二叉树root2
BSTNode* root2=createBST();
cout<<"preOrderBST(root2):\n";
preOrderBST(root2);
cout<<'\n';
//序列化
string result;
serializeBST(root2,result);
cout<<"serializeBST(root2):\n";
cout<<result<<'\n';
//反序列化
BSTNode* root3=deSerializeBST(result);
//查看反序列化的前序遍历结果
cout<<"root3=deSerializeBST(result):\n";
preOrderBST(root3);
cout<<'\n';
system("pause");
return 0;
}