一、题目描述
请实现两个函数,分别用来序列化和反序列化二叉树。你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
二、思路分析
注:思路分析中的一些内容和图片参考自力扣各位前辈的题解,感谢他们的无私奉献
思路
通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。题目要求的序列化和反序列化是可逆操作,即给出一棵二叉树可以推出唯一的序列化字符串,反之给出一个序列化字符串可以反推出这棵唯一的二叉树。因此,序列化的字符串应携带完整的二叉树信息。观察题目示例,序列化的字符串实际上是二叉树的层序遍历(BFS)结果,本文也采用层序遍历。
为完整表示二叉树,考虑将叶节点下的 n u l l null null 也记录。在此基础上,对于列表中任意某节点 n o d e node node,其左子节点 n o d e . l e f t node.left node.left 和右子节点 n o d e . r i g h t node.right node.right 在序列中的位置都是唯一确定的。如下图所示:
上图规律可总结为下表:
设 m m m 为列表区间 [ 0 , n ] [0, n] [0,n] 中的 n u l l null null 节点个数,则可总结出根节点、左子节点、右子节点的列表索引的递推公式:
序列化使用层序遍历实现。反序列化通过以上递推公式反推各节点在序列中的索引,进而实现。
序列化Serialize:
借助队列,对二叉树做层序遍历,并将越过叶节点的 n u l l null null 也打印出来。
算法流程:
①特例处理:若root
为空,则直接返回空列表[]
②初始化:队列queue
(包含根节点root
)、序列化列表res
③层序遍历:当queue
为空时跳出
----节点出队,记为node
----若node
不为空:①打印字符串node.val
②将左、右子节点加入queue
----否则(若node
为空):打印字符串null
④返回值: 拼接列表,用,
隔开,首尾添加中括号
案例分析:
复杂度分析:
时间复杂度 O ( N ) \rm{O(N)} O(N):N
为二叉树的节点数,层序遍历需要访问所有节点,最差情况下需要访问N+1
个null
,总体复杂度为O(2N+1)=O(N)
空间复杂度 O ( N ) \rm{O(N)} O(N):最差情况下,队列queue
同时存储 N + 1 2 \frac{N + 1}{2} 2N+1个节点(或N+1
个null
),使用O(N)
。列表res
使用O(N)
反序列化Deserialize:
基于本文开始推出的 n o d e node node、 n o d e . l e f t node.left node.left、 n o d e . r i g h t node.right node.right 在序列化列表中的位置关系,可实现反序列化。利用队列按层构建二叉树,借助一个指针 i i i 指向节点 n o d e node node 的左、右子节点,每构建一个 n o d e node node 的左、右子节点,指针 i i i 就向右移动 1 1 1 位。
算法流程:
①特例处理:若data
为空,直接返回null
②初始化:序列化列表vals
(先去掉首尾中括号,再用逗号隔开),指针i=1
,根节点root
(值为vals[0]
),队列queue
(包含root
)
③按层构建:当queue
为空时跳出
----节点出队,记为node
----构建node
的左子节点:node.left
的值为vals[i]
,并将node.left
入队
----执行i+=1
----构建node
的右子节点:node.left
的值为vals[i]
,并将node.left
入队
----执行i+=1
④返回值:返回根节点root
即可
案例分析:
复杂度分析:
时间复杂度 O ( N ) \rm{O(N)} O(N):N
为二叉树的节点数,按层构建二叉树需要遍历整个vals
,其长度最大为2N+1
空间复杂度 O ( N ) \rm{O(N)} O(N):最差情况下,队列queue
同时存储 N + 1 2 \frac{N + 1}{2} 2N+1 个节点,因此使用O(N)
额外空间
三、整体代码
整体代码如下
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
#define MAX_SIZE 80000
/** Encodes a tree to a single string. */
char* serialize(struct TreeNode* root) {
if (!root) return "[]";
struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * MAX_SIZE); //辅助队列
char* res = (char*)calloc(MAX_SIZE, sizeof(char)); //返回数组
int front = 0, rear = 0; //队首队尾元素下标
queue[rear++] = root; //根结点入队
char tmpChar[10] = {0}; //定义一个字符串数组,用于保存节点数值的字符串
sprintf(tmpChar, "%d", root->val);
res[0] = '['; //res的第一个字符为左边括号
strcat(res, tmpChar); //字符串拼接,将数值字符和逗号拼接起来
strcat(res, ",");
/* 开始BFS */
while (front < rear) {
int curRear = rear; //记录当前层的最后一个节点在队列中的位置
while (front < curRear) { //遍历当前层节点
if (queue[front]->left) { //当前节点的左孩子存在 */
queue[rear++] = queue[front]->left; //左孩子入队
//字符串数组保存节点值的字符,并将节点值的字符和逗号拼接到res后面
memset(tmpChar, 0, 10);
sprintf(tmpChar, "%d", queue[front]->left->val);
strcat(res, tmpChar);
strcat(res, ",");
} else { /* 当前节点的左孩子不存在,将null拼接到res后面 */
strcat(res, "null,");
}
if (queue[front]->right) { /* 当前节点的右孩子存在,操作流程和上面一样 */
queue[rear++] = queue[front]->right;
memset(tmpChar, 0, 10);
sprintf(tmpChar, "%d", queue[front]->right->val);
strcat(res, tmpChar);
strcat(res, ",");
} else {
strcat(res, "null,");
}
/* 当前层的下一节点 */
front++;
}
}
//找到最后的那个',' 它在最后的一个数字的后面,将其最变为']'
int len = strlen(res);
for (; len >= 0; len--) {
if (res[len] >= '0' && res[len] <= '9') {
len++;
break;
}
}
res[len] = ']';
res[len + 1] = '\0';
return res;
}
/** Decodes your encoded data to tree. */
struct TreeNode* deserialize(char* data) {
if (!data || strcmp(data, "[]") == 0) return NULL;
struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * MAX_SIZE); //辅助队列
int front = 0, rear = 0;
char num[10] = {0}; //记录字符串中的数字
memset(num, 0, 10);
int point = 0; //point为num的索引指针
int len = strlen(data); //字符串的长度
for (int i = 1; i < len; i++) { //遍历字符串
if (data[i] == ',' || data[i] == ']') { //如果是逗号或右括号,代表当前单词结束
struct TreeNode* tmp; //建立个二叉树结点指针,临时保存当前结点
if (strcmp(num, "null") == 0) { //当前单词为NULL
tmp = NULL;
} else { //当前单词不为空
tmp = (struct TreeNode*)malloc(sizeof(struct TreeNode));
if (!tmp) {
return NULL;
}
memset(tmp, 0, sizeof(struct TreeNode));
tmp->val = atoi(num);
}
//节点入队列 */
queue[rear++] = tmp;
/* num清零 */
point = 0;
memset(num, 0, 10);
} else { /* 当前字符不为单词结束 */
num[point++] = data[i];
}
}
/* point置为1 */
point= 1;
/* 返回结果的根节点 */
struct TreeNode* root = queue[0];
/* 层序构造二叉树 */
for (int i = 0; i < rear; i++) {
struct TreeNode* tmp = queue[i];
/* 当前节点不为空 */
if (tmp) {
/* 左孩子不为叶子节点 */
if(point < rear) {
tmp->left = queue[point];
}
/* 右孩子不为叶子节点 */
if (point + 1 < rear) {
tmp->right = queue[point + 1];
}
/* 指向下一节点的孩子节点 */
point += 2;
}
}
return root;
}
// Your functions will be called as such:
// char* data = serialize(root);
// deserialize(data);
运行,测试通过