二叉树构建算法全解析

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

📌 前言

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


方法一:递归构建法 (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)

相关推荐
CodeSheep13 分钟前
稚晖君又开始摇人了,有点猛啊!
前端·后端·程序员
app出海创收老李14 分钟前
海外独立创收日记(1)-我是如何从0到1在Google Play获得睡后被动收入的?
android·程序员
要开心吖ZSH26 分钟前
软件设计师备考-(十六)数据结构及算法应用(重要)
java·数据结构·算法·软考·软件设计师
带娃的IT创业者39 分钟前
如何开发一个教育性质的多线程密码猜测演示器
网络·python·算法
Aczone282 小时前
硬件(六)arm指令
开发语言·汇编·arm开发·嵌入式硬件·算法
luckys.one6 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
~|Bernard|8 小时前
在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
算法·conda
战术摸鱼大师8 小时前
电机控制(四)-级联PID控制器与参数整定(MATLAB&Simulink)
算法·matlab·运动控制·电机控制
Christo38 小时前
TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
人工智能·算法·机器学习·数据挖掘
好家伙VCC9 小时前
数学建模模型 全网最全 数学建模常见算法汇总 含代码分析讲解
大数据·嵌入式硬件·算法·数学建模