二叉树的最大宽度计算

【学习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

相关推荐
@解忧杂货铺6 分钟前
基于用户的协同过滤推荐算法实现(Java电商平台)
算法·机器学习·推荐算法
爱coding的橙子8 分钟前
每日算法刷题Day31 6.14:leetcode二分答案2道题,结束二分答案,开始枚举技巧,用时1h10min
算法·leetcode·职场和发展
@ chen10 分钟前
龟兔赛跑算法(Floyd‘s Cycle-Finding Algorithm)寻找重复数
算法
晨曦学习日记11 分钟前
leetcode题解538:把二叉搜索树转换为累加树
算法
孟大本事要学习15 分钟前
算法第13天|继续学习二叉树:平衡二叉树(递归)、二叉树所有路径(递归)、左叶子之和(递归)
学习·算法
无聊的小坏坏1 小时前
一文详解前缀和:从一维到二维的高效算法应用
数据结构·算法
roman_日积跬步-终至千里1 小时前
【系统设计【1】】系统设计面试方法论:从0到百万用户的需求到架构的推演
面试·架构
泽02021 小时前
C++之模板进阶
开发语言·c++·算法
倔强青铜三1 小时前
就离谱!Python相对路径竟暗藏杀机?90%开发者踩过的坑!
人工智能·python·面试
蒙奇D索大1 小时前
【数据结构】图论最短路圣器:Floyd算法如何用双矩阵征服负权图?
数据结构·算法·矩阵·图论·图搜索算法