关于二叉树的序列化和反序列化的概念解释
第一次接触这个可能会比较懵,什么叫序列化和反序列化?
- 序列化:系统为了保存二叉树的存储结构从而把二叉树变成了一串字符串记录在文件等地方
- 反序列化:就是将字符串去恢复成二叉树
序列化的整个思路
- 遇见NULL的时候用#占位,因为没有‘#’就很难区分左右的节点
- 每一个字符保证用一个字符隔开,我这边用的是_,因为不这样做的话就会分不清变量
- 按照前序遍历的顺序记录的话,反序列也要按照一样的顺序
#include <iostream>
#include <cstring>
#include <sstream>
using namespace std;
class Node{
public:
Node * left;
Node * right;
int value;
Node(int data)
{
this->value = data;
this->left = NULL;
this->right = NULL;
}
};
string treeSerialize(Node * head)
{
string res;
//如果已经为NULL的话就需要记录一下用#占位
if(head == NULL)
return "#_";
stringstream ss;
ss << head->value;
ss >> res ;
res += '_';
res += treeSerialize(head->left);
res += treeSerialize(head->right);
return res;
}
int main()
{
Node * Head = new Node(1);
Head->left = new Node(2);
Head->right = new Node(3);
Head->left->left = new Node(4);
Head->right->left = new Node(5);
cout << treeSerialize(Head) << endl;
}
打印结果:1_2_4_#_#_#_3_5_#_#_#_
逆序的过程
逆序的思路:
- 首先逆序也是通过递归来实现的
- 对于c++ string是没有split函数的所以第一步需要把所有的元素放在一个队列里,也是比较方便(prepare函数的用法
- 由于采用的是先序遍历的方式去序列化,那么反序列化也会按照相应的顺序执行
- 如果该节点为NULL就之间返回到上一级递归当中,由于左子树的生成在右子树的前面,也就是说在遇到‘#’之前都会生产左子树
- 按照这样的顺序就可以恢复树的结构,先左后右
queue<string> prepare(string Tree)
{
queue<string> leaf;
int i = 0;
while(i < Tree.size())
{
if(Tree[i] != '_')
leaf.push(Tree[i]);
i++;
}
return leaf;
}
Node * unTreeSerialize(queue<string> TreeNode)
{
Node * leaf;
stringstream ss;
string nodeStr = TreeNode.poll();
if(nodeStr == '#')
return NULL;
else
{
ss << nodeStr ;
int nodeVal;
nodeVal << ss;
leaf = new Node(nodeVal);
leaf->left = unTreeSerialize(TreeNode);
leaf->right = unTreeSerialize(TreeNode);
}
}
上面是前序遍历的过程,下面来看按层序列化的方法
首先讲一下二叉树按层遍历的方法
- 首先准备一个queue
- 第一次添加的是根节点
- 从第二次开始,放在一个while循环里面,如果queue的size 大于0就不会停止
- 每一次都在队列里面拿出一个node
- 之后,分别判断该节点的左子节点和右子节点存在不,如果存在就都添加进来
代码实现如下:
int string2int(string val)
{
stringstream ss; int value;
ss << val;
ss >> value;
return value;
}
string int2string(int val)
{
stringstream ss; string value;
ss << val;
ss >> value;
return value;
}
string treeLevelSerialize(Node * head)
{
queue<Node *> que;
stringstream ss;
//添加第一个子节点
que.push(head);
string res;
while(que.size())
{
Node * node = que.pop();
if(node->left == NULL)
{
res += "#_";
}
else
{
que.push(node->left);
res += int2string(node->value);
}
if(node->right == NULL)
{
res += "#_";
}
else
{
que.push(node->right);
res += int2string(node->value);
}
}
return res;
}
Node * generateNodeByString(String val) {
Node * node;
if (val.equals("#")) {
return null;
}
return node = new Node(string2int(val));
}
Node * treeLevelUnserialize(string Tree)
{
queue<string> que= prepare(Tree);
queue<Node *> Tree ;
int index = 0;
Node * head = generateNodeByString(que.pop());
if(head != NULL)
Tree.push(head);
while(Tree.size() > 0)
{
Node * node = Tree.pop();
node->left = generateNodeByString(que.pop());
node->right = generateNodeByString(que.pop());
/*为什么每个节点为NULL的时候就不添加进去
因为这个队列里面的每个数
都代表了一个拥有子树的节点,即便子树有可能是null
但是为null的节点是不可能有子树的
等于说是每一次从序列化的字符串里面取出2个左子节点或右子节点
如果该节点为null就直接跳过两个null的子节点(因为不存在)
*/
if(node->left != NULL)
{
Tree.push(node->left);
}
if(node->right != NULL)
{
Tree.push(node->right);
}
}
return head;
}