完整教程:C++ 二叉树序列化与分层遍历:基础、变式与实战详解

        二叉树的序列化(字符串表示)和分层遍历(层序遍历)是算法面试的高频基础题,不仅考察对树遍历的理解,更考验边界处理和灵活变通能力。本文结合笔记中的 10 道例题,从序列化到分层遍历的基础实现、3 种变式(自底向上、锯齿形),详细拆解解题思路和代码,帮你彻底掌握这类题型。


目录

一、例题 1:二叉树序列化(606. 根据二叉树创建字符串 - 力扣(LeetCode))—— 树转字符串

题目核心要求

解题思路:递归分治

代码实现

核心注意事项

二、例题 2-5:分层遍历(基础 + 3 种变式)

2.1 基础版:自上而下分层遍历(102. 二叉树的层序遍历 - 力扣(LeetCode))

题目要求

解题思路:队列 + 层大小控制

代码实现

2.2 变式 1:自底向上分层遍历(LeetCode 107)

核心思路

代码实现

2.3 变式 2:锯齿形分层遍历(103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode))

题目要求

解题思路:标志位控制逆序

代码实现

2.4 分层遍历的 3 种实现方法对比

2.4 C++ vs C 实现差异

结尾总结


一、例题 1:二叉树序列化(606. 根据二叉树创建字符串 - 力扣(LeetCode))—— 树转字符串

题目核心要求

        给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。

        空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

将二叉树转换为符合规则的字符串,关键约束:

  • 根节点 + 左子树(括号包裹) + 右子树(括号包裹)
  • 左子树为空但右子树非空时,必须保留左子树的空括号(如 1()(2)
  • 右子树为空时,可省略括号(如 1(2)

解题思路:递归分治

        二叉树的序列化本质是「前序遍历 + 格式约定」,核心思想是将树分解为「根节点、左子树、右子树」三部分递归处理:

  1. 终止条件:节点为空,返回空字符串(无需额外标记);
  2. 根节点处理:直接将节点值转为字符串(C++11 to_string() 高效便捷);
  3. 左子树处理
    • 左子树非空:用括号包裹左子树序列化结果;
    • 左子树为空但右子树非空:必须保留空括号 ()(避免歧义);
  4. 右子树处理
    • 右子树非空:用括号包裹右子树序列化结果;
    • 右子树为空:直接省略括号(简化字符串)。

代码实现

class Solution {
public:
    string tree2str(TreeNode* root) {
    string ret;
        if(root == nullptr)
        return ret;
    ret += to_string(root->val);
    if(root->left != nullptr)
    {
        ret += "(";
        ret += tree2str(root->left);
        ret += ")";
    }else if(root->right != nullptr)
    {
        ret += "()";
    }
    if(root->right != nullptr)
    {
        ret += "(";
        ret += tree2str(root->right);
        ret += ")";
    }
     return ret;
    }
};

核心注意事项

  • 括号歧义问题:左空右不空时保留空括号是核心(如 1()(2) 不能写成 1(2),否则无法区分右子树归属);
  • 效率优化:使用 += 拼接字符串(避免频繁创建临时对象),OJ 平台普遍支持 C++11 特性;
  • 特殊测试用例:空树、单节点树、只有左子树、只有右子树(如 root=1, right=2 需输出 1()(2))。

二、例题 2-5:分层遍历(基础 + 3 种变式)

        分层遍历的核心是用「队列」实现「先进先出(FIFO)」,按层处理节点。基础实现、自底向上、锯齿形等变式,我们逐一拆解。

2.1 基础版:自上而下分层遍历(102. 二叉树的层序遍历 - 力扣(LeetCode)

题目要求

        返回 vector<vector<int>> 类型结果,每层节点单独存入一个子数组(如输入 [3,9,20,null,null,15,7],输出 [[3],[9,20],[15,7]])。

解题思路:队列 + 层大小控制

核心是「上一层带下一层」,通过记录每层节点数确保仅处理当前层节点:

  1. 初始化队列,将根节点入队;
  2. 循环:当队列非空时,记录当前层节点数 cengsize = q.size()
  3. 遍历当前层:循环 cengsize 次,出队节点并收集值,同时将非空子节点入队;
  4. 每层处理完毕后,将子数组加入结果。

代码实现

class Solution {
public:
    vector> levelOrder(TreeNode* root) {
        vector> vv;
        queue q;
        if(root == nullptr)
            return vv;
        q.push(root);
        while(!q.empty())
        {
            int cengsize = q.size();
            vector v;
            for(int i = 0; i < cengsize ;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                if(front->left)
                {
                    q.push(front->left);
                }
                if(front->right)
                {
                    q.push(front->right);
                }
                v.push_back(front->val);
            }
            vv.push_back(v);
        }
       return vv;
    }
};

2.2 变式 1:自底向上分层遍历(LeetCode 107)

核心思路

        在基础版之上,通过 reverse 函数反转结果数组(自上而下 → 自下而上),无需修改遍历逻辑。

代码实现

class Solution {
public:
    vector> levelOrderBottom(TreeNode* root) {
        vector> vv;
        queue q;
        if(root == nullptr)
            return vv;
        q.push(root);
        while(!q.empty())
        {
            int cengsize = q.size();
            vector v;
            for(int i = 0; i < cengsize ;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                if(front->left)
                {
                    q.push(front->left);
                }
                if(front->right)
                {
                    q.push(front->right);
                }
                v.push_back(front->val);
            }
            vv.push_back(v);
        }
        reverse(vv.begin(),vv.end());
       return vv;
    }
};

2.3 变式 2:锯齿形分层遍历(103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)

题目要求

        奇数层(从根开始计数,第 1 层)正序输出,偶数层逆序输出(如输入 [3,9,20,null,null,15,7],输出 [[3],[20,9],[15,7]])。

解题思路:标志位控制逆序

  1. 使用队列进行标准的层序遍历。
  2. 按顺序收集每一层的节点值。
  3. 在每层遍历结束后,判断是否需要反转

代码实现

class Solution {
public:
    vector> zigzagLevelOrder(TreeNode* root) {
        vector> vv;
        queue q;
        if(root == nullptr)
            return vv;
        q.push(root);
        int k = 0;
        while(!q.empty())
        {
            int cengsize = q.size();
            vector v;
            for(int i = 0; i < cengsize ;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                    if(front->left)
                {
                    q.push(front->left);
                }
                    if(front->right)
                {
                    q.push(front->right);
                }
                    v.push_back(front->val);
            }
            if(k % 2 == 1 )
                reverse(v.begin(),v.end());
            k++;
            vv.push_back(v);
        }
       return vv;
    }
};

2.4 分层遍历的 3 种实现方法对比

       3 种分层控制方法,各有优劣:

方法核心思路优点缺点
层大小控制法(推荐)记录每层开始时的队列大小无需额外空间,效率最高逻辑需注意循环次数
双队列法用两个队列分别存储当前层和下一层节点思路直观,易于理解额外占用队列空间
分隔标记法在队列中插入 nullptr 作为层分隔符代码简洁需处理标记节点,边界容易出错

2.4 C++ vs C 实现差异

  • C 语言难点:需手动管理二维数组内存,必须预先估算每层节点数,实现复杂
  • C++ 优势vector 自动管理内存,无需预先确定数组大小,代码简洁优雅,适合 OJ 场景。

结尾总结

二叉树的序列化与分层遍历是二叉树操作中的基础且核心的技能。

  • 序列化的关键在于理解并应用递归分治思想,将复杂的树结构分解为根、左子树、右子树三个部分进行处理,并严格遵守特定的格式规则(如空括号的保留与省略),以确保序列化结果的唯一性和正确性。
  • 分层遍历则巧妙地利用了队列(queue)这种数据结构,通过 “先进先出” 的特性实现了对二叉树节点的按层访问。其核心在于控制每一层的遍历范围,通常通过记录每一层开始时队列的大小来实现。在此基础上,我们可以轻松实现自底向上(通过反转结果)和锯齿形(通过标志位控制方向)等变式遍历,展现了算法的灵活性和可扩展性。

        掌握了这些基础操作,你不仅能够解决诸如 LeetCode 606、102、107、103 等经典问题,更重要的是建立了对树结构进行复杂操作的思维模式,为后续学习更高级的树算法(如最近公共祖先、树的重建等)打下了坚实的基础。在实际面试中,这类题目也常常作为考察候选人基础算法能力和代码实现能力的入门环节。

        希望这篇文章对你有帮助,如果你有任何问题或建议,欢迎在评论区留言。谢谢阅读(求攒攒 收藏 关注)!

posted @ 2025-12-23 21:38  yangykaifa  阅读(3)  评论(0)    收藏  举报