二叉树的最大宽度计算

【学习Vlog】深入理解二叉树的最大宽度计算:从输入解析到递归遍历

大家好!今天我要分享一个关于二叉树最大宽度计算的编程实践。通过这个例子,我们可以学到如何从字符串输入构建二叉树,以及如何用递归方式统计每层节点数。下面是我的学习笔记和代码分析~

🌳 二叉树最大宽度问题

最大宽度指的是二叉树中节点最多的那一层的节点数量。例如:

markdown 复制代码
     1
    / \
   2   3
  / \   \
 4   5   7

这棵树的最大宽度是3(第二层有3个节点)

📝 完整代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <algorithm>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

// 构建二叉树的函数
TreeNode* buildTree(const vector<int>& nodes, int index) {
    if (index >= nodes.size() || nodes[index] == -1) { // -1表示null
        return nullptr;
    }
    TreeNode* root = new TreeNode(nodes[index]);
    root->left = buildTree(nodes, 2 * index + 1);
    root->right = buildTree(nodes, 2 * index + 2);
    return root;
}

// 统计每层节点数的递归函数
void getWidth(TreeNode* root, int level, vector<int>& count) {
    if (root == nullptr) return;
    
    if (level >= count.size()) {
        count.push_back(0);
    }
    count[level]++;
    
    getWidth(root->left, level + 1, count);
    getWidth(root->right, level + 1, count);
}

// 计算最大宽度的主函数
int widthOfBinaryTree(TreeNode* root) {
    if (root == nullptr) return 0;
    
    vector<int> count;
    getWidth(root, 0, count);
    
    return *max_element(count.begin(), count.end());
}

int main() {
    string input;
    getline(cin, input);
    
    // 解析输入数组
    vector<int> nodes;
    size_t start = input.find('[');
    size_t end = input.find(']');
    string arrStr = input.substr(start + 1, end - start - 1);
    
    size_t pos = 0;
    while ((pos = arrStr.find(',')) != string::npos) {
        string token = arrStr.substr(0, pos);
        if (token == "null") {
            nodes.push_back(-1);
        } else {
            nodes.push_back(stoi(token));
        }
        arrStr.erase(0, pos + 1);
    }
    // 处理最后一个元素
    if (!arrStr.empty()) {
        if (arrStr == "null") {
            nodes.push_back(-1);
        } else {
            nodes.push_back(stoi(arrStr));
        }
    }
    
    TreeNode* root = buildTree(nodes, 0);
    cout << widthOfBinaryTree(root) << endl;
    
    return 0;
}

🔍 关键知识点解析

1. 递归统计层节点数

cpp 复制代码
void getWidth(TreeNode* root, int level, vector<int>& count) {
    if (root == nullptr) return;
    
    // 如果当前层级的计数还未初始化
    if (level >= count.size()) {
        count.push_back(0);
    }
    count[level]++; // 当前层级节点数+1
    
    // 递归处理左右子树
    getWidth(root->left, level + 1, count);
    getWidth(root->right, level + 1, count);
}
  • level参数记录当前节点所在的层级(根节点为0层)
  • count向量动态记录各层节点数
  • 采用前序遍历的方式访问每个节点

✨利用count动态数组联系起各层关系

ps.虽然在树的概念中root所在的是第一层,但是此处为了符合count数组的索引特性,采用从0开始

2. 输入字符串解析技巧

cpp 复制代码
size_t start = input.find('[');
size_t end = input.find(']');
string arrStr = input.substr(start + 1, end - start - 1);
  • 使用find()定位方括号位置,并返回索引
  • substr()提取方括号内的内容
  • size_t是无符号整数类型,适合表示字符串索引

3. 逗号分隔值处理

cpp 复制代码
while ((pos = arrStr.find(',')) != string::npos) {
    string token = arrStr.substr(0, pos);
    // 处理token...
    arrStr.erase(0, pos + 1); // 删除已处理的部分
}
  • string::npos表示未找到的特殊值,即如果find函数没有在字符数组中找到逗号,返回npos
  • stoi()将字符串转为整数
  • erase()移除已处理的部分字符串

🔍 深入解析:处理输入字符串的最后一个元素

在解析输入字符串时,我们需要特别注意处理最后一个元素的情况。让我们仔细分析这段关键代码:

cpp 复制代码
// 处理最后一个元素
if (!arrStr.empty()) {
    if (arrStr == "null") {
        nodes.push_back(-1);
    } else {
        nodes.push_back(stoi(arrStr));
    }
}

为什么需要这段代码?

  1. 循环处理的局限性

    • 前面的while循环是通过查找逗号来分割字符串的
    • 当处理到最后一个元素时,字符串中可能不再有逗号
    • 例如输入 [1,2,3,null,5],处理完"3"和"null"后,剩下的"5"后面没有逗号
  2. 确保完整性

    • 这段代码确保不会遗漏输入数组的最后一个元素
    • 是整个解析过程的收尾工作

代码执行逻辑分析:

  1. if (!arrStr.empty())

    • 检查字符串是否还有剩余内容
    • 这是必要的防御性编程,避免对空字符串进行操作
  2. 处理"null"情况:

    cpp 复制代码
    if (arrStr == "null") {
        nodes.push_back(-1);
    }
    • 将"null"转换为-1(这是我们约定的空节点表示)
    • 例如输入中的最后一个元素是"null"
  3. 处理数字情况:

    cpp 复制代码
    else {
        nodes.push_back(stoi(arrStr));
    }
    • 使用stoi将字符串转换为整数
    • 例如输入中的最后一个元素是"5"

实际案例演示:

案例1 :输入 [1,2,3]

  • 处理流程:
    1. 提取"1",剩余"2,3"
    2. 提取"2",剩余"3"
    3. while循环结束,处理最后的"3"
  • 最终nodes内容:[1, 2, 3]

案例2 :输入 [1,null,3,null]

  • 处理流程:
    1. 提取"1",剩余"null,3,null"
    2. 提取"null"(转为-1),剩余"3,null"
    3. 提取"3",剩余"null"
    4. while循环结束,处理最后的"null"
  • 最终nodes内容:[1, -1, 3, -1]

为什么这样设计?

  1. 鲁棒性

    • 能够处理各种合法的输入格式
    • 包括以null结尾的数组
  2. 一致性

    • 确保所有元素(包括最后一个)都按照相同规则处理
    • 保持代码逻辑的统一性
  3. 边界条件处理

    • 专门处理输入数组只有一个元素的情况
    • 例如输入 [5] 也能正确解析

常见错误规避:

如果没有这段代码:

  • 最后一个元素会被遗漏
  • 导致构建的二叉树不完整
  • 可能引发数组越界或计算错误

扩展思考:

这种处理方式体现了几个重要的编程原则:

  1. 完整性:确保处理所有可能的输入情况
  2. 防御性编程:通过empty()检查避免运行时错误
  3. 一致性:对null和数字的处理与前面保持一致

💡 学习收获

  1. 递归思维:理解如何用递归遍历树结构并统计信息
  2. 字符串处理:掌握C++中字符串查找、分割和转换的方法
  3. 动态统计:学习如何动态扩展vector来记录层级信息
  4. 算法库应用 :使用max_element快速找到最大值

🚀 优化思考

当前实现使用了递归遍历,对于非常深的树可能会导致栈溢出。可以考虑改用**层序遍历(BFS)**来实现,这样既避免了递归深度问题,又能直观地统计每层节点数。

大家有没有其他计算二叉树宽度的方法呢?欢迎在评论区分享你的想法!如果觉得这篇内容有帮助,别忘了点赞收藏哦~

#编程 #算法学习 #二叉树 #C++ #LeetCode

相关推荐
雾月5535 分钟前
LeetCode 1292 元素和小于等于阈值的正方形的最大边长
java·数据结构·算法·leetcode·职场和发展
天天扭码2 小时前
总所周知,JavaScript中有很多函数定义方式,如何“因地制宜”?(ˉ﹃ˉ)
前端·javascript·面试
知来者逆2 小时前
计算机视觉——速度与精度的完美结合的实时目标检测算法RF-DETR详解
图像处理·人工智能·深度学习·算法·目标检测·计算机视觉·rf-detr
阿让啊2 小时前
C语言中操作字节的某一位
c语言·开发语言·数据结构·单片机·算法
এ᭄画画的北北2 小时前
力扣-160.相交链表
算法·leetcode·链表
天天扭码3 小时前
深入讲解Javascript中的常用数组操作函数
前端·javascript·面试
爱研究的小陈3 小时前
Day 3:数学基础回顾——线性代数与概率论在AI中的核心作用
算法
mazhimazhi3 小时前
GC垃圾收集时,居然还有用户线程在奔跑
后端·面试
Java技术小馆3 小时前
SpringBoot中暗藏的设计模式
java·面试·架构