C++ / ACM 自学笔记 · v5.0

(八) 二叉树

除了顺序数组与按键查找,有些数据需要表达“谁是谁的孩子”。从父节点与左右孩子的记录开始,再观察不同遍历为什么产生不同访问次序。

二叉树的节点、孩子、子树和叶子

每个节点最多两个孩子,空孩子不计作节点;这里沿用本文根为第 1 层的约定。

1. 二叉树的结构与性质

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((A)) --> B((B)) A --> C((C))

如上图,每个节点最多有两个有序子节点的树结构称为二叉树。

左边和右边的子节点又被分别被称为:

  • 左子节点(left child)
  • 右子节点(right child)

严格定义:二叉树 是 n(n≥0)个节点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。

1.1 左右子树与五种结构形态
  • 每个节点最多有两棵子树(即度不超过 2)
  • 左子树和右子树有顺序,不能颠倒
  • 即使只有一棵子树,也要区分是左子树还是右子树

二叉树的五种形态:

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A[空二叉树] --> B[只有一个根节点] B --> C[根节点只有左子树] B --> D[根节点只有右子树] B --> E[根节点有左右子树]
1.2 节点的度、深度与高度

(1)节点的度

  • 节点的度:节点拥有的子树数目
  • 在二叉树中,节点的度只能是 0、1 或 2

(2)叶子节点

  • 叶子节点:度为 0 的节点(没有子节点的节点)

(3) 节点的层次和深度

  • 根节点的层次:第 1 层
  • 节点的深度:本文统一按节点所在层数计算,根的深度为 1;若按边数计算根深度为 0,公式应相应调整。

(4)树的高度

  • 树的高度:树中节点的最大层次

示例:

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((2)) A --> C((3)) B --> D((4)) B --> E((5))

对于上述二叉树,根节点有两个子树,五个节点的度数分别为 2,2,0,0,0,其中叶子节点,即度为 0 的节点为 3,4,5

根节点 1 的深度为 1,

子节点 2,3 的深度为 2

子节点 4,5 的深度为 3

树的最大高度为 3

1.3 常见二叉树类型与数量关系
1.3.1 满、完全、搜索与平衡二叉树

满二叉树(本文采用国内教材常见定义,对应 perfect binary tree):每层都达到最多节点数,高度为 h 时共有 \(2^h-1\) 个节点。

英文 full / proper binary tree 通常只要求每个节点有 0 或 2 个孩子,不要求所有叶子处于同一层。下面五个节点的图符合 full 的英文定义,但不是本文定义的满二叉树。

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((2)) A --> C((3)) B --> D((4)) B --> E((5))

完全二叉树(Complete Binary Tree):除最后一层外,其他层节点都满,且最后一层节点从左到右连续排列。

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((2)) A --> C((3)) B --> D((4)) B --> E((5)) C --> F((6))

由于是从左到右连续排列,因此下列这种不是完全二叉树:

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((2)) A --> C((3)) B --> D((4)) B --> N1( ) C --> E((5)) C --> F((6)) style N1 fill:#ffffff, stroke:#ffffff, color:#ffffff

二叉搜索树(BST, Binary Search Tree):左子树的所有节点值 < 当前节点值 < 右子树所有节点值。

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((8)) --> B((3)) A --> C((10)) B --> D((1)) B --> E((6)) C --> F(( )) C --> G((14)) E --> H((4)) E --> I((7)) G --> J((13)) G --> K(( )) style F fill:#ffffff, stroke:#ffffff, color:#ffffff style K fill:#ffffff, stroke:#ffffff, color:#ffffff

说明:

  • 以节点 8 为根:左子树(3,1,6,4,7)都 <8,右子树(10,14,13)都> 8
  • 以节点 3 为根:左子树(1)<3,右子树(6,4,7)> 3
  • 以节点 6 为根:左子树(4)<6,右子树(7)> 6

AVL 树:二叉搜索树,并且每个节点的左右子树高度差不超过 1。红黑树使用颜色等约束维持对数级高度,不要求每个节点的左右高度差都不超过 1。

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((6)) --> B((3)) A --> C((10)) B --> D((1)) B --> E((4)) C --> F((8)) C --> G((13)) E --> H(( )) E --> I((5)) F --> J(( )) F --> K(( )) G --> L(( )) G --> M((14)) style H fill:#ffffff, stroke:#ffffff, color:#ffffff style J fill:#ffffff, stroke:#ffffff, color:#ffffff style K fill:#ffffff, stroke:#ffffff, color:#ffffff style L fill:#ffffff, stroke:#ffffff, color:#ffffff

自平衡二叉搜索树通过相应的旋转或调整规则控制树高,保持对数级的查找、插入和删除代价。AVL 与红黑树使用的平衡约束不同。

平衡因子:某节点的左子树高度 - 右子树高度 AVL 平衡条件:所有节点的平衡因子绝对值 ≤ 1 常见自平衡二叉搜索树:AVL 树和红黑树使用不同约束与调整规则。

图中的 AVL 示例保持搜索树的大小关系,并且每个节点的左右子树高度差不超过 1。

1.3.2 节点数、高度与叶子数的关系
完全二叉树的层序编号与父子下标

此图采用从 1 开始的层序编号,父节点为 i/2,孩子为 2i、2i+1;编号超出节点数表示孩子不存在。

性质 1:在二叉树的第 i 层上至多有 \(2^{(i-1)}\) 个节点(\(i≥1\))

性质 2:深度为 \(k\) 的二叉树至多有 \(2^k - 1\) 个节点(\(k≥1\))

性质 3:对任何一棵非空二叉树 T,如果其叶子节点数为 \(n_0\),度为 \(2\) 的节点数为 \(n_2\),则: \(n_0 = n_2 + 1\)

性质 4:具有 \(n\) 个节点的完全二叉树的深度为 \(\lfloor\log_2 n\rfloor + 1\)(n ≥ 1)

性质 5:

对完全二叉树按层编号,则对任意节点 \(i\) 有:

  • 如果 \(i=1\),则是根节点
  • 如果 \(i>1\),则其父节点编号为 \(⌊i/2⌋\)
  • 左孩子编号为 \(2i\)(如果 \(2i≤n\))
  • 右孩子编号为 \(2i+1\)(如果 \(2i+1≤n\) )

性质 6:对于高度为 \(h\) 的二叉树:

  • 最少节点数:\(h\)(每层只有一个节点)
  • 最多节点数:\(2^h - 1\)(满二叉树)

性质 7:对于有 n 个节点的完全二叉树:

  • 高度:\(\lfloor\log_2 n\rfloor + 1\)(n ≥ 1)
  • 叶子节点数:\(⌈\frac{n}{2}⌉\)
  • 非叶子节点数:\(⌊\frac{n}{2}⌋\)
1.3.3 用二叉树性质计算节点数与高度

题目:一棵完全二叉树有 1000 个节点,求:

  1. 树的高度
  2. 叶子节点数
  3. 度为 1 的节点数

解答:

  1. 高度 = \(⌊log_21000⌋ + 1 = ⌊9.966⌋ + 1 = 9 + 1 = 10\)

  2. 叶子节点数 = $ ⌈⌉ = 500$

  3. 度为 1 的节点数:

    • \(n_0 = 500\)
    • \(n = n_0 + n_1 + n_2 = 1000\)
    • 又因为 \(n_0 = n_2 + 1\),所以 \(n_2 = 499\)
    • 因此 \(n_1 = 1000 - 500 - 499 = 1\)
1.4 二叉树的遍历

示例二叉树:

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((A)) --> B((B)) A --> C((C)) B --> D((D)) B --> E((E)) C --> F((F)) C --> G((G)) E --> H((H)) E --> I((I))
1.4.1 层序遍历

访问顺序:按层次从上到下,每层从左到右

二叉树层序遍历的访问编号

橙色小数字表示访问次序;树边表示父子关系,依次按层读取。

所以层序遍历为:A - B - C - D - E - F - G - H - I

1.4.2 前序遍历

访问顺序:根节点 → 左子树 → 右子树

二叉树前序遍历的访问编号

先根、再左、再右;用编号和下方序列表示访问顺序,树边只表示父子关系。

所以前序遍历为:A - B - D - E - H - I - C - F - G

1.4.3 中序遍历

访问顺序:左子树 → 根节点 → 右子树

二叉树中序遍历的访问编号

先左、再根、再右;当前节点要等左子树全部访问后才访问。

所以中序遍历:D - B - H - E - I - A - F - C - G

1.4.4 后序遍历

访问顺序:左子树 → 右子树 → 根节点

二叉树后序遍历的访问编号

先左、再右、再根;子树处理完成以后才访问其根节点。

所以后序遍历:D - H - I - E - B - F - G - C - A

遍历方式 应用场景
前序遍历 复制二叉树、前缀表达式
中序遍历 二叉搜索树的有序输出、中缀表达式
后序遍历 释放二叉树内存、后缀表达式、计算目录大小
层序遍历 查找最短路径、按层次处理节点

这四张图用于核对同一棵树的完整访问次序。执行时怎样暂停在子调用、返回后何时访问根、队列如何进出,会在后面的二叉树实现中用 5 个节点逐步展开;先理解这些动作,再按相同规则推到这棵 9 节点树。

1.4.5 遍历序列与二叉树的唯一性
  • 节点标识互不相同时,前序 + 中序或后序 + 中序可以唯一确定二叉树。
  • 一般情况下只有前序 + 后序不能唯一确定;重复值需要额外的节点身份或约束。

2. 按层序插入与删除的二叉树实现

2.1 二叉树节点类

题目原文(自拟示例): 输入 n 个互不相同的整数,按层序放入普通二叉树,再输入一个待删除值。输出四种遍历结果,并删除目标后输出层序结果;允许空树,找不到目标时树不变。

思路分析: 层序插入寻找第一个空位;删除时把最后一个节点的值复制到目标位置,再摘除最后节点。这个操作维护普通树结构,不维护 BST 的大小关系。

节点类拥有左右子树,二叉树对象拥有根节点;所有节点由 new 创建,不允许共享同一个子节点或拷贝整个所有权结构。

树节点拥有孩子与析构顺序

每个孩子只由一个父节点拥有,根由树对象拥有;析构根会递归销毁左右子树,不能把同一节点交给两个父节点。

这里的 TreeNode(const TreeNode&) = delete; 与赋值函数末尾的 = delete 禁止这两种拷贝操作;它们不是释放内存的 delete 表达式。节点拥有左右子树的释放责任,若只复制地址,就可能让两份对象重复释放同一子树。后面的 BinaryTree 也按同样理由禁止直接拷贝。

/*
 * 二叉树节点类
 *
 * 定义了二叉树的基本结构,包含节点值和左右子节点指针
 */
class TreeNode {

public:
    int val;         // 节点值
    TreeNode *left;  // 左子节点指针
    TreeNode *right; // 右子节点指针

    /*
     * 默认构造函数
     *
     * 初始化节点值为0,左右子节点指针为空
     */
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(const TreeNode &) = delete;
    TreeNode &operator=(const TreeNode &) = delete;

    /*
     * 带参数的构造函数
     *
     * 参数 x:用于初始化节点的值
     */
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}

    ~TreeNode() {
        delete left;
        delete right;
    }
};
2.2 BinaryTree 的接口与成员操作

题目原文(自拟): 输入 n(1<=n<=10000)和 n 个 int 范围的整数,按输入顺序层序插入一棵普通二叉树;重复值不插入。输出层序、前序、中序、后序遍历。再读一个待删除值,用最深最右叶子的值替换目标节点并移除该叶子,最后输出层序遍历;目标不存在则结构不变。遍历同层或同父节点时先左后右,输出格式见后面样例。

思路分析: 普通二叉树没有搜索树的大小约束。插入依据层序的首个空位,遍历依据访问顺序,删除则根据末尾叶子的替换规则;三个过程分别实现,不能以 BST 删除规则代替。

下面代码与上一段节点类合用,还需要 <iostream>、<queue> 和 using namespace std;。

代码组合方式: 在同一个 .cpp 中依次放置 TreeNode 定义、下面的 BinaryTree 类定义、各成员函数实现和唯一的 main。成员实现不能单独运行;BinaryTree:: 表示它属于这个类。

2.2.1 先确定类的接口与所有权

TreeNode 析构时递归释放孩子,BinaryTree 只需释放根。禁止浅拷贝,避免两个对象重复释放同一棵树。成员声明先列出接口,下面再分开实现。

#include <iostream>
#include <queue>
using namespace std;

// 先放入上一小节的 TreeNode 定义;root 拥有整棵树。
class BinaryTree {
public:
    TreeNode *root;
    BinaryTree() : root(nullptr) {}
    BinaryTree(const BinaryTree &) = delete;
    BinaryTree &operator=(const BinaryTree &) = delete;
    ~BinaryTree() { delete root; }
    bool insert(int value);
    void levelOrder();
    void preOrder(TreeNode *node);
    void inOrder(TreeNode *node);
    void postOrder(TreeNode *node);
    TreeNode *deleteNode(TreeNode *root, int key);
};
2.2.2 层序寻找插入位置

队列按层扫描,记下第一个缺孩子的节点。即使已找到空位,也继续扫描全树,检查值是否重复;不存在重复才真正插入。单次插入 O(n),连续插入 n 个不同值的总量可能为 O(n²)。

队列按层扫描,记下第一个缺孩子的节点

队列按层扫描,记下第一个缺孩子的节点。

记下空位后继续检查重复,全部检查通过才连接新节点

在根 1、孩子 2 和 3 的树中插入 4:队列先处理 1,再处理 2,此时记下 2 的左空位;随后仍处理 3,确认没有值 4,才创建并连接新节点。若插入的是 3,处理到现有节点 3 就返回 false,之前记下的空位不发生写入。记录位置和实际插入是两个时机,不能混在一起。

// 队列按层扫描,记下第一个缺孩子的节点。
bool BinaryTree::insert(int value) {
    if (!root) {
        root = new TreeNode(value);
        return true;
    }
    queue<TreeNode *> pending;
    pending.push(root);
    TreeNode *vacancy = nullptr;
    bool leftVacancy = false;
    while (!pending.empty()) {
        TreeNode *node = pending.front();
        pending.pop();
        if (node->val == value)
            return false;
        if (node->left)
            pending.push(node->left);
        else if (!vacancy) {
            vacancy = node;
            leftVacancy = true;
        }
        if (node->right)
            pending.push(node->right);
        else if (!vacancy) {
            vacancy = node;
            leftVacancy = false;
        }
    }
    TreeNode *added = new TreeNode(value);
    if (leftVacancy)
        vacancy->left = added;
    else
        vacancy->right = added;
    return true;
}
2.2.3 队列驱动层序遍历

取出队头并输出,再把左、右孩子依次入队。队列始终保存尚未访问的节点;每个节点处理一次,时间 O(n),队列空间 O(w),w 为最大层宽。

取出队头并输出,再把左、右孩子依次入队

取出队头并输出,再把左、右孩子依次入队。

// 取出队头并输出,再把左、右孩子依次入队。
void BinaryTree::levelOrder() {
    // 检查根节点是否为空
    if (!root)
        return;
    queue<TreeNode *> q;
    // 将根节点入队
    q.push(root);
    // 当队列不为空时继续遍历
    while (!q.empty()) {
        // 取出队首节点并出队
        TreeNode *current = q.front();
        q.pop();
        // 输出当前节点值
        cout << current->val << " ";
        // 将左右子节点依次入队
        if (current->left)
            q.push(current->left);
        if (current->right)
            q.push(current->right);
    }
    // 遍历完成后换行
    cout << endl;
}
2.2.4 前序:根、左、右

空指针立即返回。先访问当前根,再递归左子树和右子树;递归深度为树高 h,时间 O(n),调用栈 O(h)。

三种递归遍历只改变访问根的位置

绿色标出根节点的访问位置,子树内部递归采用相同规则。

进入调用、左子树返回、右子树返回分别对应三种访问时机

用根 1、孩子 2 和 3、节点 2 的孩子 4 和 5 来跟踪调用。前序在刚进入 2 时输出 2;中序等左侧 4 整棵子树处理完、返回 2 后才输出 2;后序还要等右侧 5 返回后才输出 2。子调用运行时,父调用停在调用语句处,返回后接着下一句,不能把“返回父节点”理解为重新执行整个父函数。三种输出依次为 1 2 4 5 3、4 2 5 1 3、4 5 2 3 1。

// 空指针立即返回。
void BinaryTree::preOrder(TreeNode *node) {
    // 递归终止条件:如果当前节点为空,则直接返回
    if (node == nullptr)
        return;

    // 访问当前节点:打印节点值
    cout << node->val << " ";

    // 递归遍历左子树
    preOrder(node->left);

    // 递归遍历右子树
    preOrder(node->right);
}
2.2.5 中序:左、根、右

先完整处理左子树,返回后输出根,最后处理右子树。二叉搜索树把较小值放在左侧、较大值放在右侧,所以它的中序遍历恰好从小到大输出;普通二叉树的中序则按结构给出左、根、右的访问次序。

// 先完整处理左子树,返回后输出根,最后处理右子树。
void BinaryTree::inOrder(TreeNode *node) {
    // 递归终止条件:如果当前节点为空,则直接返回
    if (node == nullptr)
        return;

    // 递归遍历左子树
    inOrder(node->left);

    // 访问当前节点:打印节点值
    cout << node->val << " ";

    // 递归遍历右子树
    inOrder(node->right);
}
2.2.6 后序:左、右、根

孩子处理完才访问根。删除整棵树也沿这个顺序:先通过父节点的 left、right 找到并释放两棵子树,最后释放父节点。这样读取孩子指针时,父节点仍然存在。

// 孩子处理完才访问根。
void BinaryTree::postOrder(TreeNode *node) {
    // 递归终止条件:如果当前节点为空,则直接返回
    if (node == nullptr)
        return;

    // 递归遍历左子树
    postOrder(node->left);

    // 递归遍历右子树
    postOrder(node->right);

    // 访问当前节点:打印节点值
    cout << node->val << " ";
}
2.2.7 用最后一个节点替换目标并删除叶子

层序扫描同时找到目标、最后访问的节点及其父节点。最后一个节点若有孩子,就不可能最后出队,因此一定是叶子;把它的值复制到目标后,解除父节点的对应孩子指针并删除叶子。空树、单节点和目标不存在分别处理,最后把返回的根指针赋给 tree.root,树便指向删除后的根。

层序扫描同时找到目标、最后访问的节点及其父节点

层序扫描同时找到目标、最后访问的节点及其父节点。

逐个出队确定目标与末尾叶子,再复制值并解除父指针

层序值为 [1,2,3,4,5,6]、删除值 2 时,扫描 2 只记下目标,不能立刻释放;扫描到最后的 6,才知道替换值及其父节点 3。把 6 复制到目标节点,再将原节点 6 的父指针 3->left 置空,最后释放该叶子。结果层序为 [1,6,3,4,5];只复制值而不解除指针,会留下指向已释放内存的连接。

// 层序扫描同时找到目标、最后访问的节点及其父节点。
TreeNode *BinaryTree::deleteNode(TreeNode *root, int key) {
    if (root == nullptr)
        return nullptr;
    if (!root->left && !root->right) {
        if (root->val == key) {
            delete root;
            return nullptr;
        }
        return root;
    }

    // 用于查找目标节点和最后节点
    TreeNode *target = nullptr; // 要删除的节点(只删值)
    TreeNode *last = nullptr;   // 最深最右节点
    TreeNode *parent = nullptr; // 最深最右节点的父节点

    queue<TreeNode *> q;
    q.push(root);

    while (!q.empty()) {
        TreeNode *node = q.front();
        q.pop();

        // 记录最后一个节点
        last = node;

        // 找到目标节点
        if (node->val == key) {
            target = node;
        }

        // 入队孩子,并记录父节点
        if (node->left) {
            parent = node;
            q.push(node->left);
        }
        if (node->right) {
            parent = node;
            q.push(node->right);
        }
    }

    // 若未找到目标节点,直接返回
    if (target == nullptr) {
        return root;
    }

    // 将最深节点值放到目标位置
    target->val = last->val;

    // 删除最深节点
    if (parent->right == last) {
        delete parent->right;
        parent->right = nullptr;
    } else if (parent->left == last) {
        delete parent->left;
        parent->left = nullptr;
    }

    return root;
}
2.2.8 组合调用与输出

先读入节点值,调用四种遍历,再读入要删除的值并写回返回的根。下面的输出对应后面的样例;重复输入值会被 insert 拒绝。

// 将类定义和上面各成员实现放在 main 之前。
int main() {
    BinaryTree tree;
    int n;
    cin >> n;
    while (n--) {
        int x;
        cin >> x;
        tree.insert(x);
    }
    cout << "层序遍历:";
    tree.levelOrder();
    cout << "前序遍历:";
    tree.preOrder(tree.root);
    cout << endl;
    cout << "中序遍历:";
    tree.inOrder(tree.root);
    cout << endl;
    cout << "后序遍历:";
    tree.postOrder(tree.root);
    cout << endl;
    int value;
    cin >> value;
    tree.root = tree.deleteNode(tree.root, value);
    cout << "删除节点后,层序遍历:";
    tree.levelOrder();
}

输入:

5
1 2 3 4 5
2

输出:

层序遍历:1 2 3 4 5
前序遍历:1 2 4 5 3
中序遍历:4 2 5 1 3
后序遍历:4 5 2 3 1
删除节点后,层序遍历:1 5 3 4

层序插入构建的是如下二叉树

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((2)) A --> C((3)) B --> D((4)) B --> E((5))

因此

层序遍历为:1 2 3 4 5

前序遍历为:1 2 4 5 3

中序遍历为:4 2 5 1 3

后序遍历为:4 5 2 3 1

删除后二叉树:

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#17324d","primaryBorderColor":"#2563eb","lineColor":"#94a3b8","background":"#f8fafc","secondaryColor":"#ecfdf5","tertiaryColor":"#ecfdf5","fontFamily":"Microsoft YaHei, sans-serif","fontSize":"16px","edgeLabelBackground":"#f8fafc"},"flowchart":{"curve":"linear"}}}%% graph TD A((1)) --> B((5)) A --> C((3)) B --> D((4)) B --> E(( )) style E fill:#ffffff, stroke:#ffffff, color:#ffffff

练习题: 洛谷 P4913 — 二叉树深度、洛谷 P3884 — 二叉树问题。

查阅笔记正文与全部题解 · Ctrl / ⌘ K