c++的学习之路:24、 二叉搜索树概念

摘要

本章主要是讲一下二叉搜索树的实现

目录

摘要

 一、二叉搜索树概念

二、 二叉搜索树操作

1、二叉搜索树的查找

2、二叉搜索树的插入

3、二叉搜索树的删除

三、二叉搜索树的实现

1、插入

2、中序遍历

3、删除

4、查找

四、二叉搜索树的递归实现

1、插入

2、删除

3、查找

五、代码

test.c

BSTree.h

六、思维导图


 一、二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

1、若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2、若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

3、它的左右子树也分别为二叉搜索树

如下图所示的图片就是一个二叉搜索树。

二、 二叉搜索树操作

这个就是不在附图了,就是上面那个图,他的数值就是int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};这个数组所示的数值。

1、二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

2、二叉搜索树的插入

a、树为空,则直接新增节点,赋值给root指针

b、树不空,按二叉搜索树性质查找插入位置,插入新节点

如下图所示

3、二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情
况:

a、要删除的结点无孩子结点

b、要删除的结点只有左孩子结点

c、要删除的结点只有右孩子结点

d、要删除的结点有左、右孩子结点

根据上述情况有下面几种情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除,情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除,情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除,也就是找个保姆

三、二叉搜索树的实现

1、插入

想要插入首先要构建节点,如下方块种代码,就是申请一个节点,这个就不用多说了,第二个快的代码就是申请插入,就是如果没有也就是空的时候就申请一个节点,然后判断需要插入的数值,如果大于跟节点就给给左,如果小于就是右边,然后在循环中进行下去,直到找到合适的位置进行插入,如果有相同的就返回false,测试插入成功在中序遍历进行打印查看。

template<class T>
struct BSTreeNode
{
    BSTreeNode<T>* _left;
    BSTreeNode<T>* _right;
    T _key;
    BSTreeNode(const T& key)
        :_left(nullptr)
        , _right(nullptr)
        , _key(key)
    {}
};

typedef BSTreeNode<K> Node;
    bool Insert(const K& key)
    {
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        if (parent->_key > key)
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        return true;
    } 

2、中序遍历

在下方块种的代码就是测试中序的,这个就没啥说的就是递归打印,但是有一点要注意,root节点是私密的在外面访问不到,没法使用,这里需要借用一个没有参数的函数进行打印,如下方代码所示,测试结果如图。

void InOrder()
    {
        _InOrder(_root);
    }
    void _InOrder(Node* root)
    {
        if (root == nullptr)
            return;
        _InOrder(root->_left);
        cout << root->_key << ' ';
        _InOrder(root->_right);
    }

3、删除

这个就是优点麻烦了,他就是和上面所写操作中的三种情况,根据这三种情况进行写,首先就是寻找相等的,这个就没啥说的了,找到后就是判断左边为空和右边为空的两种情况,找到相等的时候,然后进行判断是否需要链接,这里是不管需不需要都进行链接,因为一种是后续有节点进行连接,一种是没有直接连接为空,刚好解决这两中情况,然后就是左右都不为空的时候,这个需要找个托孤的,就有点像我学Linux的时候遇到的孤儿进程被1号进程托孤一样,这里就是左边最大节点和右边最小节点,然后进行换一下,在进行删除,这里代码如下,测试如图。

bool Erase(const K& key)
    {
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            //判断找到相等的
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else//找到相等了
            {
                //左为空
                if (cur->_left == nullptr)
                {
                    if (cur == _root)//如果是根节点,就是左边都是空,只有右边的值
                    {
                        _root = cur->_right;
                    }
                    else//不是根节点
                    {
                        if (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
                        {
                            parent->_left = cur->_right;
                        }
                        else//如果cur在父节点的右边,就把cur左边的值托孤给父
                        {
                            parent->_right = cur->_right;
                        }

                    }
                    delete cur;
                }
                //右为空
                else if (cur->_right == nullptr)
                {
                    if (cur == _root)//如果是根节点,右边都是空,只有左边有值
                    {
                        _root = cur->_left;
                    }
                    else//不是根节点
                    {
                        if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
                        {
                            parent->_left = cur->_left;
                        }
                        else//如果cur在父节点的右边,就把cur左边的值托孤给父
                        {
                            parent->_right = cur->_left;
                        }

                    }
                    delete cur;
                }
                else
                {
                    // 找右树最小节点替代,也可以是左树最大节点替代
                    Node* pminRight = cur;
                    Node* minRight = cur->_right;
                    while (minRight->_left)
                    {
                        pminRight = minRight;
                        minRight = minRight->_left;
                    }

                    cur->_key = minRight->_key;

                    if (pminRight->_left == minRight)
                    {
                        pminRight->_left = minRight->_right;
                    }
                    else
                    {
                        pminRight->_right = minRight->_right;
                    }
                    delete minRight;
                }
                return true;
            }
        }
        return false;
    }

4、查找

这个就是直接进行查找就好了,比根节点大就去右边找,小就是左边找,如下代码所示,测试如图。

bool Find(const K& key)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key > key)
            {
                cur->_left;
            }
            else if(cur->_key<key)
            {
                cur->_right;
            }
            else
            {
                cout << cur->_key << endl;
                return true;
            }
        }
        return false;
    }

四、二叉搜索树的递归实现

1、插入

这里世界利用递归去插入,如果为空就创建一个节点,没有的判断是否比根小,小的话就去左边递归,大的就去右边递归,因为这个需要root节点所以和上面所说的中序一样进行利用函数进行使用直接传递key值就可以了,如下代码,这里的测试放在最后了,和上面测试一样的。

bool InsertR(const K& key)
    {
        return _InsertR(_root, key);
    }

bool InsertR(const K& key)
    {
        return _InsertR(_root, key);
    }bool _InsertR(Node*& root, const K& key)
    {
        if (root == nullptr)
        {
            root = new Node(key);
            return true;
        }

        if (root->_key < key)
        {
            return _InsertR(root->_right, key);
        }
        else if (root->_key > key)
        {
            return _InsertR(root->_left, key);
        }
        else
        {
            return false;
        }
    }

2、删除

这个删除和上面差不多也是三种情况,这里也是利用递归去查找,就是在最后一种需要注意下,这里是利用左边最大的节点交换,然后进行递归查找,但是需要注意这里需要利用引用传值进行删除。

bool EraseR(const K& key)
    {
        return _EraseR(_root, key);
    }

    bool _EraseR(Node*& root, const K& key)
    {
        if (root == nullptr)
            return false;

        if (root->_key < key)
        {
            return _EraseR(root->_right, key);
        }
        else if (root->_key > key)
        {
            return _EraseR(root->_left, key);
        }
        else
        {
            Node* del = root;

            // 开始准备删除
            if (root->_right == nullptr)
            {
                root = root->_left;
            }
            else if (root->_left == nullptr)
            {
                root = root->_right;
            }
            else
            {
                Node* maxleft = root->_left;
                while (maxleft->_right)
                {
                    maxleft = maxleft->_right;
                }

                swap(root->_key, maxleft->_key);

                return _EraseR(root->_left, key);
            }

            delete del;

            return true;
        }
    }

3、查找

这个递归的查找就是判断,如果没有找到节点为空的时候就返回false,找到了就返回true,然后大于的话就去左边,小于就去右边,如下方代码所示,测试如图。

bool FindR(const K& key)
    {
        return _FindR(_root, key);
    }

    bool _FindR(Node* root, const K& key)
    {
        if (root == nullptr)
            return false;

        if (root->_key == key)
            return true;

        if (root->_key < key)
            return _FindR(root->_right, key);
        else
            return _FindR(root->_left, key);
    }

五、代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
#include "BSTree.h"//void Test()
//{
//	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//	BSTree<int> t1;
//	
//	for (auto e : a)
//	{
//		t1.Insert(e);
//	}
//	t1.Find(8);
//	t1.InOrder();
//	cout << endl;
//
//	for (auto e : a)
//	{
//		t1.Erase(e);
//		t1.InOrder();
//		cout << endl;
//	}
//
//	t1.InOrder();
//	cout << endl;
//}void Test()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){t1.InsertR(e);}cout<<t1.FindR(8)<<endl;t1.InOrder();cout << endl;for (auto e : a){t1.EraseR(e);t1.InOrder();cout << endl;}t1.InOrder();cout << endl;
}int main()
{Test();return 0;
}

BSTree.h

#pragma oncetemplate<class T>
struct BSTreeNode
{BSTreeNode<T>* _left;BSTreeNode<T>* _right;T _key;BSTreeNode(const T& key):_left(nullptr), _right(nullptr), _key(key){}
};
template<class K>
class BSTree
{
public:typedef BSTreeNode<K> Node;BSTree() = default; // 制定强制生成默认构造BSTree(const BSTree<K>&t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);//_root = nullptr;}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key);if (parent->_key > key){parent->_left = cur;}else{parent->_right = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur->_left;}else if(cur->_key<key){cur->_right;}else{cout << cur->_key << endl;return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){//判断找到相等的if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else//找到相等了{//左为空if (cur->_left == nullptr){if (cur == _root)//如果是根节点,就是左边都是空,只有右边的值{_root = cur->_right;}else//不是根节点{if (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父{parent->_left = cur->_right;}else//如果cur在父节点的右边,就把cur左边的值托孤给父{parent->_right = cur->_right;}}delete cur;}//右为空else if (cur->_right == nullptr){if (cur == _root)//如果是根节点,右边都是空,只有左边有值{_root = cur->_left;}else//不是根节点{if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父{parent->_left = cur->_left;}else//如果cur在父节点的右边,就把cur左边的值托孤给父{parent->_right = cur->_left;}}delete cur;}else{// 找右树最小节点替代,也可以是左树最大节点替代Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}void InOrder(){_InOrder(_root);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ' ';_InOrder(root->_right);}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool FindR(const K& key){return _FindR(_root, key);}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key == key)return true;if (root->_key < key)return _FindR(root->_right, key);elsereturn _FindR(root->_left, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}bool EraseR(const K& key){return _EraseR(_root, key);}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 开始准备删除if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else{Node* maxleft = root->_left;while (maxleft->_right){maxleft = maxleft->_right;}swap(root->_key, maxleft->_key);return _EraseR(root->_left, key);}delete del;return true;}}
private:Node* _root = nullptr;
};

六、思维导图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/823269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode刷题之合并两个有序数组

Leetcode刷题之合并两个有序数组 一、题目描述二、题目解析 一、题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数…

去哪网拿去花不能提现,只能用于透支消费,那么拿去花提现是怎么实现呢?

去哪网拿去花不能提现&#xff0c;只能用于透支消费&#xff0c;那么拿去花提现是怎么实现呢&#xff1f; 申请携程拿去花之后&#xff0c;有一些人就会想着把钱提现出来拿去用。一般来说&#xff0c;他们都是通过线下门店来提现拿去花&#xff0c;拿去花允许用户先消费后付款&…

Python文件操作大全

1 文件操作 1.1 文件打开与关闭 1.1.1 打开文件 在Python中&#xff0c;你可以使用 open() 函数来打开文件。以下是一个简单的例子&#xff1a; # 打开文件&#xff08;默认为只读模式&#xff09; file_path example.txt with open(file_path, r) as file:# 执行文件操作…

LeetCode-二叉树修剪

每日一题 今天遇到的题比较简单&#xff0c;是一道二叉树的题。 题目要求 给定一个二叉树 根节点 root &#xff0c;树的每个节点的值要么是 0&#xff0c;要么是 1。请剪除该二叉树中所有节点的值为 0 的子树。 节点 node 的子树为 node 本身&#xff0c;以及所有 node 的…

appium2报错:Failed to create session. ‘automationName‘ can‘t be blank

1、问题概述&#xff1f; 今天在window环境中安装了appium2.5.2版本&#xff0c;通过appium inspector连接真机的时候报错如下&#xff1a; Failed to create session. automationName cant be blank 原因分析&#xff1a;这是因为appium2的比appium1有了很大的改进&#xff…

Linux 指令之文件

1.开发背景 记录 linux 下对文件操作的指令 2.开发需求 记录常用的文件操作指令 3.开发环境 linux 操作系统&#xff0c;如果不支持需要查看是否存在对应的可执行文件 4.实现步骤 4.1 查找字符串 查找指定目录下包含指定的字符串 grep -rn "Timer frequency" .…

python中如何求阶乘

第一种、利用functools工具处理 import functools result (lambda k: functools.reduce(int.__mul__, range(1, k 1), 1))(5) print(result)第二种、普通的循环 x 1 y int(input("请输入要计算的数:")) for i in range(1, y 1):x x * i print(x) 第三种、利用…

美格智能出席紫光展锐第三届泛金融支付生态论坛,引领智慧金融变革向新

4月16日&#xff0c;以“融智创新&#xff0c;共塑支付产业新生态”为主题的紫光展锐第三届泛金融支付生态论坛在福州举办&#xff0c;来自金融服务机构、分析师机构、终端厂商、模组厂商等行业各领域生态伙伴汇聚一堂&#xff0c;探讨金融支付产业的机遇与挑战。作为紫光展锐重…

浮点数的存储方式、bf16和fp16的区别

目录 1. 小数的二进制转换2. 浮点数的二进制转换3. 浮点数的存储3.1 以fp32为例3.2 规约形式与非规约形式 4. 各种类型的浮点数5. BF16和FP16的区别Ref 1. 小数的二进制转换 十进制小数转换成二进制小数采用「乘2取整&#xff0c;顺序排列」法。具体做法是&#xff1a;用 2 2…

数据结构复杂度

算法的时间复杂度 常对幂指阶 小练习1 小练习2

【实战】Dubbo应用可观测性升级指南与踩坑记录

应用从dubbo-3.1.*升级到dubbo-*:3.2.*最新稳定版本&#xff0c;提升dubbo应用的可观测性和度量数据准确性。 1. dubbo版本发布说明(可不关注) dubbo版本发布 https://github.com/apache/dubbo/releases 【升级兼容性】3.1 升级到 3.2 2. 应用修改点 注意&#xff1a;Sprin…

qutip,一个高级的 Python 量子力学研究库!

目录 前言 安装 特性 基本功能 量子态的创建和操作 量子态的测量 示例代码 动力学模拟 高级功能 退相干和噪声模拟 控制和优化 量子信息学工具 实际应用场景 量子态演化研究 量子计算机模拟 量子纠错协议 总结 前言 大家好&#xff0c;今天为大家分享一个高级的 Pytho…

机器学习理论入门---线性回归从理论到实践

线性回归是机器学习里面最简单也是最常用的算法&#xff0c;理解了线性回归的推导之后对于后续的学习有很大帮助&#xff0c;所以我决定从这里开始深入学习相关的机器学习模型。 本篇首先从矩阵求导开始切入&#xff0c;然后介绍一次线性回归的推导&#xff0c;再到代码实现。本…

酒店餐厅装水离子雾化壁炉前和装后对比

酒店餐厅装水离子雾化壁炉前和装后的对比可以体现出餐厅氛围和客户体验的显著改变&#xff1a; 装前&#xff1a; 普通的氛围&#xff1a;餐厅可能显得比较普通&#xff0c;缺乏特色或独特的装饰元素。 视觉上缺乏焦点&#xff1a;餐厅空间可能显得相对平淡&#xff0c;缺乏…

压缩感知(ISTA-Net论文)学习笔记

压缩感知&#xff08;ISTA-Net论文&#xff09;学习笔记 第一天&#xff0c;主要查找相关视频和笔记&#xff0c;补全预备知识 【nabla算子】与梯度、散度、旋度_哔哩哔哩_bilibili 近端梯度(Proximal Gradient)下降算法的过程以及理解|ISTA算法|LASSO问题_哔哩哔哩_bilibil…

华为ensp中静态路由和默认路由的原理及配置

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月17日17点37分 默认路由 [Router] ip route-static <目的网络> <目的网络掩码> <下一跳地址>默认路由的作用是将无法匹配路由表中其他路由表项的…

【行业前沿】制造业的数字化转型如何做?

随着科技的迅速发展&#xff0c;数字化转型已经成为制造型企业提高竞争力的关键因素。它可以帮助制造型企业&#xff0c;在产品优化设计、材料采购、生产流程方面实现精细化管理&#xff1b;提升上下游协同生产能力&#xff0c;提高生产效率、降低生产成本、优化产品质量&#…

RUM 最佳实践-视觉稳定性的探索与实践

写在前面的话 在当今数字时代&#xff0c;网页的视觉稳定性对于提供良好的用户体验至关重要。其中一个衡量视觉稳定性的关键指标就是累积布局偏移&#xff08;Cumulative Layout Shift&#xff0c;简称 CLS&#xff09;。CLS 作为 Web Vitals 指标之一&#xff0c;它衡量的是网…

jql联表查询涉及到权限的最好用上临时表

JQL联表查询的两种方法 联表查询 为方便文档描述定义以下两个概念&#xff1a; 临时表&#xff1a;getTemp方法返回的结果&#xff0c;例&#xff1a;const article db.collection(article).getTemp()&#xff0c;此处 article 就是一个临时表虚拟联表&#xff1a;主表与副…

【MySQL数据库】 (篇一 ) 让你快速上手——新手速通版

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、如何起步&#xff1f;&#x1f3c3;‍1.创建数据库&#xff1a;2.选择数据库&#xff1a;3.删除数据库&#xff1a;4.创建表&#xff1a;5.删除表&#xff…