题目来源
题目描述
、
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
}
};
题目解析
二叉树序列化的本质是对其进行编码,更重要的是对其结构进行编码。可以遍历树来完成上述任务。一般有两个策略:广度优先搜索和深度优先搜索
- 广度优先搜索可以按照层次从上往下遍历所有节点
- 深度优先搜索可以从一个根节点开始,一直延伸到某个叶,然后回到根,到达另一个分支。根据根节点、左节点和右节点之间的相对顺序,可以进一步将深度优先搜索策略区分为:
- 先序遍历
- 中序遍历
- 后序遍历
深度优先搜索
序列化:
- 递归遍历一棵树,重点关注当前节点,它的子树的遍历交给递归完成:“serialize函数,请帮我分别序列化我的左右子树,我等你返回的结果,再拼接一下。”
- 选择前序遍历,是因为 根|左|右、根∣左∣右 的打印顺序,在反序列化时更容易定位出根节点的值。
- 遇到 null 节点也要翻译成特定符号,反序列化时才知道这里是 null。
反序列化
- 前序遍历的序列化字符串,就像下图右一:
- 定义函数 buildTree 用于还原二叉树,传入由序列化字符串转成的 list 数组。
- 逐个 pop 出 list 的首项,构建当前子树的根节点,顺着 list,构建顺序是根节点,左子树,右子树。
- 如果弹出的字符为 “X”,则返回 null 节点。
- 如果弹出的字符是数值,则创建root节点,并递归构建root的左右子树,最后返回root。
总的来说
(1)怎么区分节点(节点和节点之间、节点与null节点)
- 需要使用
#
等特殊标记每一个null - 需要使用
,
等字符区分每一个节点(注意null也是一个节点)
(2)
- 序列化时:前序遍历,根节点–>左节点–>右节点
- 反序列时:前序遍历,先构建出根节点 ,然后构建左节点,再构建右节点(每构建一个节点,都需要从原始字符中移除)
实现一
class Codec {
void serialize(TreeNode *root, ostringstream &out) {
if(root){
out << root->val << ' ';
serialize(root->left, out);
serialize(root->right, out);
}else{
out << "# ";
}
}
TreeNode *deserialize(istringstream &in){
std::string val;
in >> val;
if(val == "#"){
return nullptr;
}
TreeNode *root = new TreeNode(stoi(val));
root->left = deserialize(in);
root->right = deserialize(in);
return root;
}
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
std::ostringstream out;
serialize(root, out);
return out.str();
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
std::istringstream in(data);
return deserialize(in);
}
};
实现二
class Codec {
static TreeNode *buildTree( std::list<std::string> & strList){
std::string strtmp = strList.front();
strList.pop_front();
if(strtmp == "X"){
return NULL;
}
TreeNode *node = new TreeNode(stoi(strtmp));
node->left = buildTree(strList);
node->right = buildTree(strList);
return node;
}
static std::list<std::string> split(std::string & data){
int len = data.size();
std::string curr;
std::list<std::string> dataArray;
for(char & ch : data){
if(ch == ','){
dataArray.push_back(curr);
curr.clear();
}else{
curr.push_back(ch);
}
}
if(!curr.empty()){
dataArray.push_back(curr);
curr.clear();
}
return dataArray;
}
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(root == NULL){ // 遍历到 null 节点
return "X";
}
std::string left = serialize(root->left);
std::string right = serialize(root->right);
return std::to_string(root->val) + "," + left + "," + right; // 按 根,左,右 拼接字符串
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
list<string> list = split(data);
TreeNode* res = buildTree(list);
return res;
}
};
广度优先搜索
序列化
- 维护一个队列,初始让根节点入队,考察出队节点:
- 如果出队的节点是NULL,将
x
推入res - 如果出队的节点是数值,将节点值推入res,并将它的左右节点入队
- 子节点 null 也要入列,它对应 “X”,要被记录,只是它没有子节点可入列
- 如果出队的节点是NULL,将
- 入列、出列…直到队列为空,就遍历完所有节点,res构建完毕,转成字符串就好。
反序列化
下图是BFS得到的序列化字符串,和DFS得到的不同,它是一层层的。除了第一个是根节点的值,其他节点值都是成对的,对应左右子节点。
- 依然先转成list数组,用一个指针 cursor 从第二项开始扫描。
- 起初,用list[0]构建根节点,并让根节点入列
- 节点出列,此时 cursor 指向它的左子节点值,cursor+1 指向它的右子节点值。
- 如果子节点值是数值,则创建节点,并认出列的父亲,同时自己也是父亲,入列。
- 如果子节点值为 ‘X’,什么都不用做,因为出列的父亲的 left 和 right 本来就是 null
- 可见,所有的真实节点都会在队列里走一遍,出列就带出儿子入列
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
std::ostringstream out;
std::queue<TreeNode*> q;
if(root){
q.push(root);
}
while (!q.empty()){
TreeNode *t = q.front(); q.pop();
if(t){
out << t->val << ' ';
q.push(t->left);
q.push(t->right);
}else{
out <<"# ";
}
}
return out.str();
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if(data.empty()){
return nullptr;
}
std::istringstream in(data);
std::queue<TreeNode *>q;
std::string val;
in >> val;
TreeNode *res = new TreeNode(stoi(val)), *curr = res;
q.push(curr);
while (!q.empty()){
TreeNode *t = q.front(); q.pop();
if(!(in >> val)){
break;
}
if(val != "#"){
curr = new TreeNode(stoi(val));
q.push(curr);
t->left = curr;
}
if(!(in >> val)){
break;
}
if(val != "#"){
curr = new TreeNode(stoi(val));
q.push(curr);
t->right = curr;
}
}
return res;
}
};