二叉树构建算法全解析

🌳 二叉树构建算法全解析 | 三种方法详解+动图演示

📌 前言

二叉树是数据结构中的重要内容,今天我们将通过图解+代码的方式,详细讲解三种不同的二叉树构建方法。每种方法都配有完整示例和动图演示!


方法一:递归构建法 (Preorder)

🎯 核心思想

"先根后子树":按照根节点→左子树→右子树的顺序递归构建

📝 输入格式

"ABC##D##"#表示空节点)

🖼️ 图解构建过程

graph TD A[步骤1: 创建A] --> B[步骤2: 创建B] B --> C[步骤3: 创建C] C --> N1[步骤4: C左子树#] C --> N2[步骤5: C右子树#] B --> D[步骤6: 创建D] D --> N3[步骤7: D左子树#] D --> N4[步骤8: D右子树#]

🏗️ 构建结果

css 复制代码
    A
   /
  B
 / \
C   D

💻 代码关键点

cpp 复制代码
BTNode* buildTree1(const string& str, int& index) {
    if(index >= str.length() || str[index] == '#') {
        index++;
        return nullptr;  // 遇到#返回空
    }
    BTNode* node = new BTNode(str[index++]);  // 创建当前节点
    node->lchild = buildTree1(str, index);    // 递归构建左子树
    node->rchild = buildTree1(str, index);    // 递归构建右子树
    return node;
}

方法二:括号表示法构建二叉树 - 代码关键点深度解析

完整代码

cpp 复制代码
BTNode* buildTree2(string str) {
    stack<BTNode*> st;       // 父节点栈
    BTNode* p = nullptr;     // 当前节点指针
    bool flag = true;        // 方向标记(true=左,false=右)
    int i = 0;               // 字符串索引
    
    while(i < str.length()) {
        switch(str[i]) {
            case '(':
                st.push(p);
                flag = true;
                break;
            case ')':
                st.pop();
                break;
            case ',':
                flag = false;
                break;
            default:
                p = new BTNode(str[i]);
                if(r == nullptr) {
                    r = p;
                }
                else {
                    if(flag && !st.empty()) {
                        st.top()->lchild = p;
                    }
                    else if(!flag && !st.empty()) {
                        st.top()->rchild = p;
                    }
                }
                break;
        }
        i++;
    }
    return r;
}

关键变量说明

变量 类型 作用
st stack<BTNode*> 保存待处理子树的父节点(遇到"("时压栈,遇到")"时弹栈)
p BTNode* 当前正在处理的节点指针
flag bool 方向标记:true表示下一个节点是左孩子,false表示是右孩子
r BTNode* 类的成员变量,存储最终树的根节点

核心逻辑分解

1. 遇到字母(节点值)

cpp 复制代码
default:
    p = new BTNode(str[i]);  // 创建新节点
    if(r == nullptr) {       // 第一个节点是根节点
        r = p;
    }
    else {                   // 非根节点处理
        if(flag && !st.empty()) {       // 作为左孩子连接
            st.top()->lchild = p;
        }
        else if(!flag && !st.empty()) { // 作为右孩子连接
            st.top()->rchild = p;
        }
    }
    break;

内存变化示例(处理"A(B(D,E),C(,F))"中的'A'):

yaml 复制代码
执行前:
st: []    p: nullptr    r: nullptr

执行后:
st: []    p: A    r: A
内存:
   A
  / \
null null

2. 遇到'('

cpp 复制代码
case '(':
    st.push(p);    // 当前节点压栈(成为父节点)
    flag = true;   // 接下来处理左子树
    break;

处理逻辑

  • 将当前节点保存到栈中(后续子树的父节点)
  • 重置方向为左子树

示例(处理完'A'后遇到'('):

ini 复制代码
执行前:
st: []    p: A    r: A

执行后:
st: [A]   p: A    flag=true

3. 遇到','

cpp 复制代码
case ',':
    flag = false;  // 切换为右子树处理
    break;

处理逻辑

  • 表示左子树处理完毕
  • 准备处理右子树

示例(处理"B(D,E)"中的','):

ini 复制代码
执行前:
st: [A,B]  p: D    flag=true

执行后:
st: [A,B]  p: D    flag=false

4. 遇到')'

cpp 复制代码
case ')':
    st.pop();  // 子树处理完成,回到父节点
    break;

处理逻辑

  • 弹出栈顶元素(当前子树的父节点)
  • 准备处理父节点的兄弟节点

示例(处理"B(D,E)"后的')'):

yaml 复制代码
执行前:
st: [A,B]  p: E

执行后:
st: [A]    p: E

完整执行示例(处理"A(B(D,E),C(,F))")

步骤 字符 操作 栈状态 当前节点 方向 树结构
1 A 创建根节点A [] A true A
2 ( A入栈 [A] A true
3 B 创建B作为A的左孩子 [A] B true A(B)
4 ( B入栈 [A,B] B true
5 D 创建D作为B的左孩子 [A,B] D true A(B(D))
6 , 切换右子树 [A,B] D false
7 E 创建E作为B的右孩子 [A,B] E false A(B(D,E))
8 ) 弹出B [A] E -
9 , 切换右子树 [A] E false
10 C 创建C作为A的右孩子 [A] C false A(B(D,E),C)
11 ( C入栈 [A,C] C true
12 , 切换右子树 [A,C] C false
13 F 创建F作为C的右孩子 [A,C] F false A(B(D,E),C(,F))
14 ) 弹出C [A] F -
15 ) 弹出A [] F - 构建完成

内存管理注意事项

  1. 节点创建 :每次遇到新字母时new BTNode(),需要确保最终有对应的删除操作
  2. 栈的作用:确保在嵌套子树结构中能正确回溯到父节点
  3. 方向标记:正确处理左右子树的切换时机

边界情况处理

  1. 空输入while循环会自动跳过
  2. 多余括号:缺少对应处理(实际使用时需要增强鲁棒性)
  3. 根节点覆盖 :通过r==nullptr判断确保根节点只设置一次

这种构建方式特别适合处理带括号表示法的树结构字符串,通过维护栈和方向标记,可以高效地构建出正确的二叉树结构。

方法三:关系表构建法

🎯 核心思想

"两步走策略"

  1. 创建所有节点
  2. 建立连接关系

📝 输入格式

r 复制代码
6
A B C
B D E
C # F
D # #
E # #
F # #

🖼️ 构建流程图

graph TB subgraph 第一阶段 A[读取所有节点] --> B[存入map] end subgraph 第二阶段 C[重新读取] --> D[建立父子关系] end D --> E[确定根节点]

💡 关键技巧

  • map<char, BTNode*> 快速查找节点
  • seekg(0) 重置输入流
  • begin()->second 获取第一个节点作为根

💻 完整代码

cpp 复制代码
BTNode* buildTree3(istream& in){
        int n;
        in >> n;
        map<char,BTNode*> nodes;

        for(int i=0;i<n;i++){
            char parent,left,right;
            in >> parent >> left >> right ;
            if(nodes.find(parent)==nodes.end()){
                nodes[parent]=new BTNode(parent);
            }
            if(left!='#'&&nodes.find(left)==nodes.end()){
                nodes[left]=new BTNode(left);
            }
            if(right!='#'&&nodes.find(right)==nodes.end()){
                nodes[right]=new BTNode(right);
            }
        }

        in.seekg(0,ios::beg);
        in >> n;

        for(int i=0;i<n;i++){
            char parent,left,right;
            in >> parent >> left >> right;
            if(left!='#'){
                nodes[parent]->lchild=nodes[left];
            }
            if(right!='#'){
                nodes[parent]->rchild=nodes[right];
            }
        }
        r=nodes.begin()->second;
        return r;
    }

📊 方法对比总结

方法 适用场景 时间复杂度 空间复杂度
递归构建 先序遍历字符串 O(n) O(h)
括号表示法 带括号的表达式 O(n) O(n)
关系表构建 明确的父子关系表 O(n) O(n)

相关推荐
京东云开发者8 分钟前
云交易技术对接全景
程序员
京东云开发者9 分钟前
自己写插件-实现时间戳自由
程序员
Vacant Seat21 分钟前
贪心算法-跳跃游戏II
算法·游戏·贪心算法
夜松云30 分钟前
从对数变换到深度框架:逻辑回归与交叉熵的数学原理及PyTorch实战
pytorch·算法·逻辑回归·梯度下降·交叉熵·对数变换·sigmoid函数
八股文领域大手子34 分钟前
深入浅出限流算法(三):追求极致精确的滑动日志
开发语言·数据结构·算法·leetcode·mybatis·哈希算法
啊阿狸不会拉杆1 小时前
人工智能数学基础(一):人工智能与数学
人工智能·python·算法
一捌年1 小时前
java排序算法-计数排序
数据结构·算法·排序算法
freexyn2 小时前
Matlab自学笔记五十二:变量名称:检查变量名称是否存在或是否与关键字冲突
人工智能·笔记·算法·matlab
C语言魔术师3 小时前
70. 爬楼梯
算法·动态规划