一、题目描述与要求
序列化二叉树_牛客题霸_牛客网 (nowcoder.com)
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。
二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)
二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,可以根据层序遍历的方案序列化,如下图:
层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}"构造成如上的二叉树。
当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。
数据范围:节点数 n≤100,树上每个节点的值满足 0≤val≤150
要求:序列化和反序列化都是空间复杂度O(n),时间复杂度 O(n)
示例
示例1:
输入:{1,2,3,#,#,6,7}
返回值:{1,2,3,#,#,6,7}
说明:如题面图
示例2:
输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}
二、解题思路
根据题目要求,我们要对给定二叉树先进行序列化(先序/中序/后序/层次)【这里选择的是先序遍历】,从而获得对应的先序遍历序列,其中空子树用‘#’代替。然后再根据这个序列去还原出原来的二叉树,也就是反序列。
首先我们先将二叉树进行序列化,也就是实现先序遍历。
存储遍历序列结果可以选择字符数组或者字符串,因为树的大小不可知,如果采用字符数组的话需要提前定义较大容量,可能会出现浪费空间的情况,因此我们可以选择先利用string存储,然后在根据字符串的长度来为字符数组申请空间(因为函数要求返回数据为char*),然后将string的内容复制过去即可。记得添加上结束符!
先判断当前是否为空树,如果是的话直接返回“#”即可;
然后调用先序遍历函数对二叉树进行先序遍历并获取序列;
接着把获取的序列存到字符数组中,添加结束符,返回即可。
先序遍历二叉树并获取序列函数实现:【递归】
首先判断当前结点是否为空,如果是的话则将‘#’添加到str中,同时返回父节点;(递归结束标志)
然后获取结点中的数据,因为结点值为int,所以需要进行类型转换成string存入temp,然后将temp添加到str后面,同时由于结点值是数字,可能会有多位数,因而我们在添加完每一个结点值后添加分隔符‘;’,用于区分结点;
然后分别递归遍历左子树和右子树,直至整颗二叉树遍历结束,即可获得先序遍历序列str。
【需要注意的是,这里的函数参数str是引用方式传递,因为在递归过程中我们需要修改str的值,如果不用&,在递归函数内部修改str的值并不会影响到函数外部的指针,因此无法正确更新str】
解决完序列化二叉树之后,我们就需要利用所得的序列来还原二叉树。
首先判断序列是否等于“#”,如果是的话代表这一二叉树为空树,则直接返回空;
否则的话定义结点res用来接收还原后的二叉树的根结点用于返回。
【由于在递归中可能多次修改str的值,所以为了保持指针的引用,在每次递归调用时传递&str;通过传递&str,可以在递归函数中更新str的值,这样在返回上层递归时,指针的位置会继续向后移动,指向下一个要解析的字符。而如果只是传递str,在递归函数内部修改str的值并不会影响到函数外部的指针,因此无法正确更新遍历的位置。因此函数参数为char *&str】
想要获取每个结点值,那么肯定需要对所给的字符数组进行遍历,但是由于从string赋值给数组的结果可以知道,数组中每个元素存储的并不是单个结点的值,这也就是在序列化时需要添加分隔符的原因,因此比起利用for循环遍历数组,我们可以利用指针遍历数组会更方便。
首先判断当前字符是否为'#',是的话代表当前结点为空节点,因而让指针后移到下一个字符,然后返回空;
否则的话,我们需要取获取当前结点的值,因为结点值可能是多位数,所以我们利用while循环找到分隔符,并且利用变量val来获取结点值,比如结点值为12,那么在数组中就是12;因而根据这一规律计算原结点值,同时指针后移;
然后利用结点的构造函数构建结点,如果此时一定访问到字符数组末尾,则返回当前结点;
否则的话指针后移(因为当前为分割符);
然后分别构造对应的左子树与右子树,最后返回根结点。
三、具体代码
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
#include <string>
class Solution {
public:
//对二叉树进行先序遍历 并存储所得序列
void PreOrderTraver(TreeNode* root,string& str){
//如果当前结点为空,则用#表示 并返回父节点
if(root==nullptr){
str+='#';
return;
}
string temp=to_string(root->val);
//将结点中的值存入字符串中,因为结点值是数字,会有多位数 因而加一个标识符用来区别结点
str+=temp+';';
//对左右子树进行遍历
PreOrderTraver(root->left, str);
PreOrderTraver(root->right, str);
}
char* Serialize(TreeNode *root) {
//如果是空树则直接返回#
if(root==nullptr) return "#";
string res;//存储先序遍历后的序列
PreOrderTraver(root, res);
//把获取的结果string转成char*类型 因为题目要求返回char*
char* charRes=new char[res.length()+1];
strcpy(charRes,res.c_str());//将内容复制过去
charRes[res.length()]='\0';//添加结束符
return charRes;
}
TreeNode* Restore(char *&str){
//因为字符数组中存储的内容并不是单个结点的值
//因此我们需要利用指针去对字符数组进行访问
if(*str=='#'){
str++;
return nullptr;
}
int val=0;
while(*str!=';'&&*str!='\0'){
val=val*10+((*str)-'0');
str++;
}
TreeNode* root=new TreeNode(val);
if(*str=='\0') return root;
else str++;
root->left=Restore(str);
root->right=Restore(str);
return root;
}
//反序列——根据先序遍历所得的序列还原二叉树
TreeNode* Deserialize(char *str) {
//空树则返回空
if(str=="#") return nullptr;
//获取还原结果
//由于在递归中可能多次修改str的值,所以为了保持指针的引用,在每次递归调用时传递&str
//通过传递&str,可以在递归函数中更新str的值,这样在返回上层递归时,指针的位置会继续向后移动,指向下一个要解析的字符。
//而如果只是传递str,在递归函数内部修改str的值并不会影响到函数外部的指针,因此无法正确更新遍历的位置。
TreeNode* res=Restore(str);
return res;
}
};