剑指offer 学习笔记 序列化二叉树

面试题37:序列化二叉树。实现两个函数,分别用来序列化和反序列化。

我们可以先把一棵二叉树序列化成一个前序遍历序列和一个中序遍历序列,然后在反序列化时通过这两个序列重构出二叉树。但该思路有两个缺点:一是该方法要求二叉树中没有值重复的节点;二是只有当两个序列中所有数据都读出后才能开始反序列化,如果两个遍历序列的数据是从一个流中读出来的,那么可能需要等待较长时间。

实际上,如果二叉树的序列化是从根节点开始的,那么相应的反序列化在根节点的数值读出来的时候就可以开始了,因此,我们可以根据前序遍历的顺序来序列化二叉树,因为前序遍历是从根节点开始的。

我们可以用特殊字符如$来表示nullptr,节点间用,隔开,如:
在这里插入图片描述
我们以上例分析如何反序列化,第一个读出的数字是1,这是根节点的值,之后是左子树的根节点2,接着读出4,接着两个$,表明4是叶子节点,接下来回到值为2的节点,由于下一个字符是$,说明2的右孩子是nullptr,至此左子树构建完毕,回到根节点构建右子树:

#include <iostream>
#include <deque>
using namespace std;

struct BinaryTreeNode {
    int m_nValue;
    BinaryTreeNode* m_pLeft;
    BinaryTreeNode* m_pRight;
};

BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder) {    // 创建树
    BinaryTreeNode* root = new BinaryTreeNode();
    root->m_nValue = *startPreorder;
    root->m_pLeft = root->m_pRight = nullptr;

    if (startPreorder == endPreorder) {    // 当递归到前序遍历只含一个元素时
        if (startInorder == endInorder && *startPreorder == *startInorder) {    // 此时若中序遍历也只含一个元素并且这个元素值与前序遍历的元素值相同时,递归到底成功返回
            return root;
        } else {    // 否则当前序和中序遍历的个数不相等(前序遍历只含一个元素但中序遍历有多个元素)或值不相等(前序遍历和中序遍历元素数都为1但这两个值不等)时
            throw exception("Invalid input.");    // 说明输入的前序和中序遍历不匹配
        }
    }

    int* rootInorder = startInorder;
    while (rootInorder < endInorder && *rootInorder != *startPreorder) {    // 遍历中序序列找到根节点
        ++rootInorder;
    }

    if (rootInorder == endInorder && *rootInorder != *startPreorder) {    // 若以上循环结束仍未找到根节点,说明输入有误
        throw exception("Invalid input");
    }

    int leftLength = rootInorder - startInorder;    // 左子树长度
    int* leftPreorderEnd = startPreorder + leftLength;    // 左子树尾边界
    if (leftLength > 0) {    // 当左子树仍存在时
        root->m_pLeft = ConstructCore(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);    // 继续递归左子树
    }
    if (leftLength < endPreorder - startPreorder) {    // 左子树长度小于当前遍历的树节点数-1(去掉根节点)时,说明存在右子树
        root->m_pRight = ConstructCore(leftPreorderEnd + 1, endPreorder, rootInorder + 1, endInorder);    // 继续递归右子树
    }

    return root;
}

BinaryTreeNode* Construct(int* preorder, int* inorder, int length) {    // 创建树
    if (preorder == nullptr || inorder == nullptr || length <= 0) {
        return nullptr;
    }

    return ConstructCore(preorder, preorder + length - 1, inorder, inorder + length - 1);
}

void Serialize(BinaryTreeNode* pRoot, ostream &stream) {
    if (pRoot == nullptr) {
        stream << "$,";
        return;
    }

    stream << pRoot->m_nValue << ",";
    Serialize(pRoot->m_pLeft, stream);
    Serialize(pRoot->m_pRight, stream);
}

bool ReadStream(istream& stream, int* number)
{
    if (stream.eof()) {    // 检测stream是否读到头
        return false;
    }

    char buffer[32];    // 用字符串保存数字
    buffer[0] = '\0';

    char ch;
    stream >> ch;    // 先读取一个字符,读取到的是数字的第一位或"$"
    int i = 0;
    while (!stream.eof() && ch != ',')
    {
        buffer[i++] = ch;    // 读取序列化的串中的数字,直到读到","
        stream >> ch;    // 读到","时,下次循环不会进入,但","已经被读取但没有存在数组中
    }

    bool isNumeric = false;
    if (i > 0 && buffer[0] != '$')    // 读入的只可能是"$"或数字,当不为"$"时
    {
        *number = atoi(buffer);    // 将逗号前的数字部分转换为整型
        isNumeric = true;
    }

    return isNumeric;
}

void Deserialize(BinaryTreeNode** pRoot, istream& stream) {
    int num;

    if (ReadStream(stream, &num)) {
        *pRoot = new BinaryTreeNode();
        (*pRoot)->m_nValue = num;
        (*pRoot)->m_pLeft = nullptr;
        (*pRoot)->m_pRight = nullptr;
        Deserialize(&((*pRoot)->m_pLeft), stream);
        Deserialize(&((*pRoot)->m_pRight), stream);
    }    
}

void PrintFromTopToBottom(BinaryTreeNode* pTreeRoot) {    // 从上到下打印树验证反序列化结果
    if (pTreeRoot == nullptr) {
        return;
    }

    deque<BinaryTreeNode*> dequeTreeNode;
    dequeTreeNode.push_back(pTreeRoot);

    while (dequeTreeNode.size()) {
        BinaryTreeNode* pNode = dequeTreeNode.front();    // 获取此次循环队首节点,由于在pop之后还要检查队首节点的子节点,因此需保存它
        dequeTreeNode.pop_front();
        cout << pNode->m_nValue << " ";

        if (pNode->m_pLeft != nullptr) {
            dequeTreeNode.push_back(pNode->m_pLeft);
        }
        if (pNode->m_pRight != nullptr) {
            dequeTreeNode.push_back(pNode->m_pRight);
        }
    }
}

int main() {
    int preorder[] = { 1,2,4,3,5,6 };
    int inorder[] = { 4,2,1,5,3,6 };
    BinaryTreeNode* pRoot = Construct(preorder, inorder, 6);    // 根据前序和中序创建树,树中不能有相同值的节点
    Serialize(pRoot, cout);    // 序列化树

    BinaryTreeNode* pDeserializedTreeRoot = nullptr;    // 反序列化树的根节点
    Deserialize(&pDeserializedTreeRoot, cin);    // 反序列化树
    cout << "反序列化树从上到下打印:" << endl;
    PrintFromTopToBottom(pDeserializedTreeRoot);    // 从上到下打印树验证反序列化结果
}
posted @   epiphanyy  阅读(5)  评论(0编辑  收藏  举报  
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2019-03-10 Oracle数据库插入大量数据
2019-03-10 Linux查看文件大小
2019-03-10 Mysql可变长度列VARCHAR、BLOB和TEXT问题
2019-03-10 数据库表名和列名大小写问题
2019-03-10 Oracle数据库DDL操作
点击右上角即可分享
微信分享提示