剑指Offer:[第28天 搜索与回溯算法(困难)]--->序列化二叉树

本文介绍了如何实现二叉树的序列化和反序列化,主要采用层序遍历的方法。序列化过程中,将二叉树的节点值和null值按层存储为字符串,反序列化时根据层序关系构建二叉树。时间复杂度和空间复杂度均为O(N),N为二叉树的节点数。
摘要由CSDN通过智能技术生成


一、题目描述

请实现两个函数,分别用来序列化和反序列化二叉树。你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示: 输入输出格式与 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+1null,总体复杂度为O(2N+1)=O(N)
空间复杂度 O ( N ) \rm{O(N)} O(N):最差情况下,队列queue同时存储 N + 1 2 \frac{N + 1}{2} 2N+1个节点(或N+1null),使用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);

运行,测试通过
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知初与修一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值