C++实现树 - 03 二叉排序树

简介: 上一讲我们介绍了二叉树的代码实现以及其遍历方法,这一讲我们来看一看进阶版的二叉树 —— 二叉排序树(二叉搜索树)。第一次接触这一讲知识点的朋友一时反应不过来不用太焦虑,其实是非常正常的,看讲解以及代码的同时动手画一画或者实现以下会让你更容易理解~
写在前面:
上一讲我们介绍了二叉树的代码实现以及其遍历方法,这一讲我们来看一看进阶版的二叉树 —— 二叉排序树(二叉搜索树)。第一次接触这一讲知识点的朋友一时反应不过来不用太焦虑,其实是非常正常的,看讲解以及代码的同时动手画一画或者实现以下会让你更容易理解~

性质

二叉排序树它和普通的二叉树不同:
(1)如果根结点的左子树不为空,那么它左子树的所有结点的值都小于根结点。
(2)如果根结点的右子树不为空,那么它右子树的所有结点的值都大于根结点。
(3)根结点的左子树和右子树同样遵循上面两条性质。
根据性质我们可以知道,这颗排序二叉树的实现必然逃不了递归了,接下来我们来看看排序二叉树的代码是如何实现的。

二叉排序树的构建及插入

假设我们要构建起下面这颗二叉排序树,要进行如何操作呢:
在这里插入图片描述
(1)构建根结点 8 。
在这里插入图片描述
(2)插入结点 2 ,可以发现比根结点小,故插入根结点的左子树。
在这里插入图片描述
(3)插入结点 9 ,可以发现比根结点大,故插入根结点的右子树。
在这里插入图片描述
(4)插入结点 11 ,可以发现比根结点右孩子的值还要大,所以就插入到根结点右孩子的右子树。
在这里插入图片描述
(5)插入结点 23 ,可以发现比之前的值都要大,所以直接插到最右边的子树。
在这里插入图片描述
(6)插入结点 1 ,可以发现比之前的值都要小,所以直接插入到最左子树。
在这里插入图片描述
(7)最后插入结点 4 ,可以发现比根结点的左孩子要大,并且其左孩子没有右子树,所以直接插入即可。
在这里插入图片描述
整个操作的思想就是递归的思想,不断判断要插入结点的值和当前结点的值是大还是小,然后进入对应分支。

struct tree_node {
    int data;
    tree_node* left_child = NULL;    //左孩子
    tree_node* right_child = NULL;    //右孩子
};

//插入孩子
void insert_child(tree_node*& root, int data) {
    if (!root) {
        tree_node* new_node = new tree_node();
        new_node->data = data;
        root = new_node;
    }
    else if (data < root->data)
        insert_child(root->left_child, data);
    else
        insert_child(root->right_child, data);
}

二叉排序树的查找操作

我们这里的查找操作就不想数组查找那样那么直接,需要从根结点开始往下遍历进行比较。那么如果我们想要查找上面这颗二叉树的结点 4 ,该如何进行查找呢:
(1)先从根结点开始查找。在这里插入图片描述
(2)可以发现我们要查找的结点 4 要比根结点 8 要小,所以我们进入根结点的左子树进行查找。
在这里插入图片描述
(3)结点 4 比结点 2 的值要大,所以我们进入其右子树进行查找。

在这里插入图片描述
(4)结果发现,当前遍历到的结点就是我们要查找的值,直接返回即可。

二叉排序树的删除操作

二叉排序树的删除操作就要比插入操作更加繁琐一些,需要分情况进行讨论,第一次学习不要慌张,一时头脑混乱非常正常,静下心来反复思考就可以吸收啦~
我们可以将删除操作分为三种情况:
(1)删除结点是叶子结点
如果删除结点是根结点,那么直接delete根结点指针即可,因为此时树中只有一个结点了。否则,同样delete删除结点即可,因为
假如我们要删除上面那棵树的结点 23 ,我们只用找到其父结点,然后将其父结点指向删除结点的指针指向空即可。
在这里插入图片描述
在这里插入图片描述
(2)如果删除的结点只有一个孩子
如果删除结点只有左孩子,那么只需将删除结点的左孩子连接到其父结点即可;如果删除结点只有右孩子,那么只需将删除结点的右孩子连接到其父结点即可。
如果删除结点是根结点则需另外操作,将根结点指针指向它的孩子即可。
假如我们要删除结点 11 ,那只用将结点 11 的右孩子直接接到其父结点 9 即可。
在这里插入图片描述
(3)如果删除结点既有左孩子也有右孩子。
这里我们有两种方法去实现,根结点和树中间的结点操作都是一样的,第一种方法是找到删除结点左子树中值最大的那个结点,然后与删除结点的值替换,并删除该结点。实际操作时,我们要从删除结点的左孩子开始查找,因为删除结点的左子树中,值最大的无非就是其左孩子或在左孩子的右子树当中,这里我们就拿删除根结点进行举例:
在这里插入图片描述
在这里插入图片描述
第二种方法是找到删除结点右孩子中最小的那个,交换并删除。同样,实际操作中也是从删除结点的右孩子开始查找,删除结点右子树中最小值一定在其右孩子或右孩子的左子树当中。这两种方法都保证删除后的树中结点仍然满足二叉排序树的性质,即左孩子都比根结点小,右孩子都比根结点大:
在这里插入图片描述
在这里插入图片描述
接下来我们来看代码的操作,这里我们是以上面第一种方法来实现的。另外,我们在实现代码时,会将一些操作合并在一起,因为有些情况的实现方法是相同的,比如说在当删除结点是叶子结点或删除结点只有一个孩子时,可以合在一起讨论,具体看下面的代码详解(代码略微有点长,需要大家细细品味和思考,这里删除操作如果找不到结点,就不会进行任何操作):

//删除结点
void delete_node(tree_node*& root, int key) {
    tree_node* cur = root;    //当前指针
    tree_node* fa = NULL;    //父指针

    //找到删除结点及其父结点
    while (cur != NULL)
    {
        if (cur->data == key)
            break;
        fa = cur;    //更新父指针
        if (key < cur->data)
            cur = cur->left_child;
        else
            cur = cur->right_child;
    }

    //如果根结点为空或没有该值,直接返回
    if (!cur)
        return;

    //1、删除结点有左右孩子
    if (cur->left_child && cur->right_child)
    {
        tree_node* temp = cur;    //用于指向左子树最大值的结点的父结点
        tree_node* left_max = cur->left_child;    //指向左子树中的最大值

        //找到删除结点左子树的最大值
        while (left_max->right_child != NULL)
        {
            temp = left_max;    //更新父指针
            left_max = left_max->right_child;
        }

        cur->data = left_max->data;    //将删除结点的前驱的值覆盖删除结点的值

        //如果删除结点的左孩子没有右孩子
        if (temp == cur)
        {
            temp->left_child = left_max->left_child;
            delete[] left_max;
        }
        //如果删除结点的左孩子有右孩子
        else
        {
            temp->right_child = left_max->left_child;
            delete[] left_max;
        }
    }

    //2、如果删除结点只有左孩子
    else if (cur->left_child)
    {
        //如果删除结点是根结点
        if (!fa)
            root = root->left_child;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = cur->left_child;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = cur->left_child;
        delete[] cur;
    }

    //3、如果删除结点只有右孩子
    else if (cur->right_child)
    {
        //如果删除结点是根结点
        if (!fa)
            root = root->right_child;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = cur->right_child;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = cur->right_child;
        delete[] cur;
    }

    //4、如果删除结点没有左右孩子即是叶子结点
    else
    {
        //如果删除结点是根结点
        if (!fa)
            root = NULL;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = NULL;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = NULL;
        delete[] cur;
    }
}

全部代码

#include <iostream>
using namespace std;

struct tree_node {
    int data;
    tree_node* left_child = NULL;    //左孩子
    tree_node* right_child = NULL;    //右孩子
};

//插入孩子
void insert_child(tree_node*& root, int data) {
    if (!root) {
        tree_node* new_node = new tree_node();
        new_node->data = data;
        root = new_node;
    }
    else if (data < root->data)
        insert_child(root->left_child, data);
    else
        insert_child(root->right_child, data);
}

//遍历二叉树
void in_order(tree_node* root) {
    if (root)
    {
        in_order(root->left_child);
        cout << root->data << " ";    //中序遍历(用于二叉排序树的输出)
        in_order(root->right_child);
    }
}

//删除结点
void delete_node(tree_node*& root, int key) {
    tree_node* cur = root;    //当前指针
    tree_node* fa = NULL;    //父指针

    //找到删除结点及其父结点
    while (cur != NULL)
    {
        if (cur->data == key)
            break;
        fa = cur;    //更新父指针
        if (key < cur->data)
            cur = cur->left_child;
        else
            cur = cur->right_child;
    }

    //如果根结点为空或没有该值,直接返回
    if (!cur)
        return;

    //1、删除结点有左右孩子
    if (cur->left_child && cur->right_child)
    {
        tree_node* temp = cur;    //用于指向左子树最大值的结点的父结点
        tree_node* left_max = cur->left_child;    //指向左子树中的最大值

        //找到删除结点左子树的最大值
        while (left_max->right_child != NULL)
        {
            temp = left_max;    //更新父指针
            left_max = left_max->right_child;
        }

        cur->data = left_max->data;    //将删除结点的前驱的值覆盖删除结点的值

        //如果删除结点的左孩子没有右孩子
        if (temp == cur)
        {
            temp->left_child = left_max->left_child;
            delete[] left_max;
        }
        //如果删除结点的左孩子有右孩子
        else
        {
            temp->right_child = left_max->left_child;
            delete[] left_max;
        }
    }

    //2、如果删除结点只有左孩子
    else if (cur->left_child)
    {
        //如果删除结点是根结点
        if (!fa)
            root = root->left_child;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = cur->left_child;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = cur->left_child;
        delete[] cur;
    }

    //3、如果删除结点只有右孩子
    else if (cur->right_child)
    {
        //如果删除结点是根结点
        if (!fa)
            root = root->right_child;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = cur->right_child;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = cur->right_child;
        delete[] cur;
    }

    //4、如果删除结点没有左右孩子即是叶子结点
    else
    {
        //如果删除结点是根结点
        if (!fa)
            root = NULL;
        //如果删除结点的父结点的左孩子是删除结点
        else if (fa->left_child == cur)
            fa->left_child = NULL;
        //如果删除结点的父结点的右孩子是删除结点
        else
            fa->right_child = NULL;
        delete[] cur;
    }
}

int main() {
    int t;
    cin >> t;    //输入二叉树的组数
    while (t--) {
        tree_node* root = NULL;
        int n, m;
        cin >> n;    //输入二叉树要插入的结点数
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            insert_child(root, x);
        }
        in_order(root);
        cout << endl;
        cin >> m;    //输入二叉树要删除的节点数
        while (m--) {
            int x;
            cin >> x;
            delete_node(root, x);
            in_order(root);
            cout << endl;
        }
    }
    return 0;
}

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

目录
相关文章
|
7月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
195 17
|
11月前
|
存储 C++
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
【数据结构——树】哈夫曼树(头歌实践教学平台习题)【合集】目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果:任务描述 本关任务:编写一个程序构建哈夫曼树和生成哈夫曼编码。 相关知识 为了完成本关任务,你需要掌握: 1.如何构建哈夫曼树, 2.如何生成哈夫曼编码。 测试说明 平台会对你编写的代码进行测试: 测试输入: 1192677541518462450242195190181174157138124123 (用户分别输入所列单词的频度) 预
398 14
【C++数据结构——树】哈夫曼树(头歌实践教学平台习题) 【合集】
|
11月前
|
算法 C++
【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】
【数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】 目录 任务描述 相关知识 测试说明 我的通关代码: 测试结果: 任务描述 本关任务:实现二叉排序树的基本算法。 相关知识 为了完成本关任务,你需要掌握:二叉树的创建、查找和删除算法。具体如下: (1)由关键字序列(4,9,0,1,8,6,3,5,2,7)创建一棵二叉排序树bt并以括号表示法输出。 (2)判断bt是否为一棵二叉排序树。 (3)采用递归方法查找关键字为6的结点,并输出其查找路径。 (4)分别删除bt中关键
291 11
【C++数据结构——查找】二叉排序树(头歌实践教学平台习题)【合集】
|
11月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
335 12
|
11月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
193 10
|
11月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
502 3
|
算法 测试技术 C++
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
|
存储 C++
【C++】AVL树
AVL树是一种自平衡二叉搜索树,由Georgy Adelson-Velsky和Evgenii Landis提出。它通过确保任意节点的两子树高度差不超过1来维持平衡,支持高效插入、删除和查找操作,时间复杂度为O(log n)。AVL树通过四种旋转操作(左旋、右旋、左-右旋、右-左旋)来恢复树的平衡状态,适用于需要频繁进行数据操作的场景。
464 2
|
存储 C++
【C++】AVL树
AVL树是一种自平衡二叉搜索树:它以苏联科学家Georgy Adelson-Velsky和Evgenii Landis的名字命名。
144 2
|
C++ 容器
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
158 5