数据结构——二叉搜索树

news2024/11/16 9:40:15

在这里插入图片描述

本章代码:二叉搜索树

文章目录

  • 🌲1.二叉搜索树概念
  • 🌳2. 二叉搜索树操作
    • 🌿2.1 结构定义
    • 🌿2.2 插入操作
    • 🌿2.3 查找操作
    • 🌿2.4 删除操作
    • 🌿2.5 遍历
  • 🌴3. 二叉搜索树应用场景
    • 🍀3.1 K模型
    • 🍀3.2 KV模型

🌲1.二叉搜索树概念

二叉搜索树又叫二叉排序树,它具有以下性质:

  • 若左子树不为空,则左子树上所有结点的值都小于根结点的值
  • 若右子树不为空,则右子树上所有结点的值都大于根结点的值
  • 它的左右子树也分别为二叉搜索树

image-20230817140841044

这个结构的时间复杂度为一般人会以为是O(logN),因为它每次都是往下一层,所以最多为二叉树的度,有n个结点的满二叉树的深度为log2(n+1)。但是实际上它的最坏情况可能是一颗斜树,这就等同于按顺序查找,时间复杂度为O(N)。所以我们按照最坏的情况来计算,二叉搜索的时间复杂度为O(N)

image-20230817141520997

🌳2. 二叉搜索树操作

🌿2.1 结构定义

template<class K>
struct BSTreeNode
{
	K _key;	//数据
	BSTreeNode<K>* _left;	//左孩子
	BSTreeNode<K>* _right;	//右孩子

	BSTreeNode(const K& key)	//初始化
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template<class K>
class BSTree
{
    typedef BSTreeNode<K> Node;
public:
    BSTree()
        :_root(nullptr)
    {}
private:
    Node* _root;	//结点
    

🌿2.2 插入操作

插入流程较简单,即:

  • 若结点为空,则新增节点,将值赋给root
  • 若不为空,则按照左子树小于根结点,右子树大于根结点来进行插入新节点

insert

//非递归
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->_right;
        }
        else if (cur->_key > key)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }
    cur = new Node(key);
    if (parent->_key < key)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }
    return true;
}
//递归
bool InsertR(const K& key)
{
    return _InsertR(_root, key);
}

private:
    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;
        }
    }

这里说明一下递归操作:

  1. 因为递归操作是需要传结点的,而我们的结点是私有的,外面访问不到,所以我们设一个子函数,来传结点

  2. 在非递归版本,我们需要记录父节点,因为我们要将树给连接上

    而递归版本,我们每次传的时候,都是root指向的左/节点

    image-20230817144752173

🌿2.3 查找操作

同样是按照二叉搜索树的性质来进行查找

find

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

//递归
bool FindR(const K& key)
{
    return _FindR(_root, key);
}
private:
    bool _FindR(Node* root, const K& key)
    {
        if (root == nullptr)
            return false;
        if (root->_key < key)
        {
            return _FindR(root->_right, key);
        }
        else if (root->_key > key)
        {
            return _FindR(root->_left, key);
        }
        else
        {
            return true;
        }
        return false;
    }

🌿2.4 删除操作

删除操作是比较复杂的,这个节点找到之后进行删除,要保证这棵树还是一个二叉搜索树。

我们分为2种情况:

  1. 该节点只有左孩子/右孩子(无孩子),也就是至多一个孩子

    我们找到该节点之后,判断哪边空,然后再让父节点指向它的另一边,然后删掉这个节点即可

    erase1

  2. 有2个孩子

    有2个孩子的话,直接删除的话,那这颗树的顺序就打乱了,根据它的性质,我们可以交换它的左子树里面的最大元素或者右子树里面的最小元素,这样就还能保证这棵树还是搜索树

erase2

//非递归
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->_right == cur)
                    {
                        parent->_right = cur->_right;
                    }
                    else
                    {
                        parent->_left = cur->_right;
                    }
                }
            }
            else if (cur->_right == nullptr)	//右边为空
            {
                if (cur == _root)
                {
                    _root = cur->_left;
                }
                else
                {
                    if (parent->_right == cur)
                    {
                        parent->_right = cur->_left;
                    }
                    else
                    {
                        parent->_left = cur->_left;
                    }
                }
            }
            else  //左右都不为空
            {
                Node* ptmp = cur;
                //左子树最大元素为例
                Node* tmp = cur->_left;
                while (tmp->_right)
                {
                    ptmp = tmp;
                    tmp = tmp->_right;
                }
                //交换元素
                std::swap(cur->_key, tmp->_key);

                if (ptmp->_left == tmp)
                {
                    ptmp->_left = tmp->_left;
                }
                else
                {
                    ptmp->_right = tmp->_left;
                }
                cur = tmp;
            }
            delete cur;
            return true;
        }
    }
    return false;
}

//递归
bool EraseR(const K& key)
{
    return _EraseR(_root, key);
}
private:
    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->_left == nullptr)
            {
                root = root->_right;
            }
            else if (root->_right == nullptr)
            {
                root = root->_left;
            }
            else
            {
                Node* tmp = root->_left;
                while (tmp->_left)
                {
                    tmp = tmp->_right;
                }
                std::swap(tmp->_key, root->_key);
                return _EraseR(root->_left, key);
            }
            delete del;
            return true;
        }
    }

🌿2.5 遍历

这里采用的是中序遍历

void InOrder()
{
    _InOrder(_root);
    cout << endl;
}
private:
    void _InOrder(Node* root)
    {
        if (root == NULL)
            return;
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }

🌴3. 二叉搜索树应用场景

🍀3.1 K模型

K模型即只有key作为数据元素,这可用于数据匹配,例如拼写检查。

我们上面实现的就是属于k模型

🍀3.2 KV模型

KV模型,每个key码都对应一个value值,我们生活中的字典、统计人员出入等,可采用这种模型

这里只做简单的逻辑演示,以下两种演示的代码都放在开头的代码仓库了,有兴趣可以查看

字典逻辑:

kv1

次数统计:

kv2


那本期的分享就到这里咯,我们下期在家,如果还有下期的话

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

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

相关文章

[JavaWeb]【五】web后端开发-Tomcat SpringBoot解析

目录 一 介绍Tomcat 二 基本使用 2.1 解压绿色版 2.2 启动TOMCAT 2.3 关闭TOMCAT 2.4 常见问题 2.5 修改端口号 2.6 部署应用程序 三 SpringBootWeb入门程序解析 前言&#xff1a;tomcat与SpringBoot解析 一 介绍Tomcat 二 基本使用 2.1 解压绿色版 2.2 启动TOMCAT 2…

作业day4

1.通过字符设备驱动分步注册方式编写LED灯的驱动&#xff0c;应用程序使用ioctl函数编写硬件控制逻辑 head.h #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct {unsigned int MODER; unsigned int OTYPER; unsigned int OSPEEDR; unsigned int PUPDR; unsigned int …

主机防护的重要性和方式

01 主机防护的重要性 主机防护是网络安全的重要组成部分。在互联网时代&#xff0c;网络攻击成为了一种常见的威胁&#xff0c;而主机防护则是保护计算机系统免受网络攻击的重要手段。 主机防护可以防范各种网络攻击&#xff0c;如病毒、木马、黑客攻击等&#xff0c;从而保…

如何做好会员管理,有哪些好用的会员管理系统?

会员管理对于企业或中小商户来说非常重要&#xff0c;会员管理可以建立和维护与顾客之间的紧密关系&#xff0c;通过会员管理系统记录和分析会员的购买历史、偏好和行为&#xff0c;可以更好地了解他们的需求和兴趣&#xff0c;增加销售机会和满意度。 那么我们应该如何做好会员…

【深度学习 | 感知器 MLP(BP神经网络)】掌握感知的艺术: 感知器和MLP-BP如何革新神经网络

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

使用教程|CloudQuery: 数据库操作管控云平台初体验

&#x1f600;前言 前言&#xff1a;就在刚刚&#xff0c;在一个技术交流群里了解到了一场活动&#xff1a;CloudQuery 社区的有奖征文活动。除了有丰厚的礼物&#xff0c;使用过程中还有技术同学的支持。但是 CloudQuery 这个产品我也没有过多的了解&#xff0c;抱着对产品的好…

AndroidStudio升级Gradle之坑

最近在做旧工程的升级&#xff0c;原来的Gradle版本是4.6的&#xff0c;需要升级到7.6&#xff0c;JDK从8升级到17&#xff0c;一路淌了很多坑&#xff0c;逐个记录下吧 1、Maven仓库需要升级到https 你会遇到这个报错 Using insecure protocols with repositories, without …

item_search-按关键字搜索淘宝商品

一、接口参数说明&#xff1a; item_search-按关键字搜索淘宝商品&#xff0c;点击更多API调试&#xff0c;请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_search 名称类型必须描述keyString是调用key&#xff08;点…

边缘计算节点BEC典型实践:如何快速上手PC-Farm服务器?

百度智能云边缘计算节点BEC&#xff08;Baidu Edge Computing&#xff09;基于运营商边缘节点和网络构建&#xff0c;一站式提供靠近终端用户的弹性计算资源。边缘计算节点在海外覆盖五大洲&#xff0c;在国内覆盖全国七大区、三大运营商。BEC通过就近计算和处理&#xff0c;大…

【Linux命令详解 | tar命令】 tar命令用于打包和解压文件,常用于备份和压缩文件

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 打包文件和目录2. 解包归档文件3. 压缩归档文件4. 列出归档文件内容5. 排除特定文件6. 保留文件权限和所有权7. 保留时间戳8. 增量备份9. 使用文件列表10. 压缩级别控制 总结 简介 在Linux中&#xff0c;tar命令…

游戏出海工具都有哪些?

游戏出海是一个复杂的过程&#xff0c;需要运用多种工具来进行市场分析、推广、本地化等工作。以下是一些常用的游戏出海工具&#xff1a; 一、必备工具&#xff1a; 1、游戏平台&#xff1a;要想进行游戏出海运营&#xff0c;游戏平台时必不可少的&#xff0c;选择游戏平台时…

一节网课中有哪些“黑科技”,猿辅导给出了这样的答案

近年来&#xff0c;AI正以润物细无声的方式重塑多个行业的面貌&#xff0c;教育行业也不例外。同时&#xff0c;随着Chat GPT对社会带来的冲击不断加强&#xff0c;AI教育已经成为整个行业不可逆转的趋势。作为最早踏入智能教育领域的企业之一&#xff0c;猿辅导深谙技术革新对…

如何正确使用生成式 AI?

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 在过去几年中&#xff0c;数据的创建速度呈指数级增长&#xff0c;这主要意味着数字世界的日益扩散。 估计吧&#xff1f;仅在过去两年中&#xff0c;世界上90%的数据就产生了。 我们以各种形式与互…

Android 命令行如何运行 JAR 文件

​ 最近有位老哥问了一个问题&#xff0c;说如果将java的jar文件在Android中执行&#xff1f;这个其实很简单的一个问题&#xff0c;直接写个App放里面不就可以了么&#xff1f;但是人家说没有App&#xff0c;直接使用命令行去运行。说明这个需求的时候&#xff0c;把我给整懵了…

人尽其才、数尽其用,Smartbi Eagle智慧数据运营平台全新亮相

数据是企业数字化转型的基石&#xff0c;也是赢得未来的核心资产和竞争力。数字化转型的关键&#xff0c;是在全公司建立一种数据驱动的组织和机制&#xff0c;营造数据文化的氛围&#xff0c;让更多的用户、在更多的场景中&#xff0c;有意愿、有能力使用数据&#xff0c;从而…

COMSOL光电仿真专题第三十五期线上通知

培训背景&#xff1a; COMSOL多物理场仿真软件以高效的计算性能和杰出的多场耦合分析能力实现了精确的数值仿真&#xff0c;已被广泛应用于各个领域的科学研究以及工程计算&#xff0c;为工程界和科学界解决了复杂的多物理场建模问题。光电作为物理类专业课程中极为重要的一部…

Jmeter 快速生成测试报告

我们使用Jmeter工具进行接口测试或性能测试后一般是通过察看结果数、聚合报告等监听器来查看响应结果。如果要跟领导汇报测试结果&#xff0c;无法直接通过监听器的结果来进行展示和汇报&#xff0c;因为太low了&#xff0c;因此测试完成后去整理一个数据齐全且美观的报告是非常…

OSM模型案例:以游戏陪练app为例

OSM模型的概念 O指目标Objective&#xff1a;整个业务、乃至局部的小功能 能解决什么问题&#xff0c;提供什么样的用户价值&#xff0c;满足用户什么需求&#xff1f; S指策略Strategy&#xff1a;如何达成目标&#xff0c;以什么方式达成目标&#xff1f; M指度量Measure&…

RabbitMq-发布确认高级(避坑指南版)

在初学rabbitMq的时候&#xff0c;伙伴们肯定已经接触到了“发布确认”的概念&#xff0c;但是到了后期学习中&#xff0c;会接触到“springboot”中使用“发布确认”高级的概念。后者主要是解决什么问题呢&#xff1f;或者是什么样的场景引出这样的概念呢&#xff1f; 在生产环…

前端原生写自定义旋转变换轮播图

html部分&#xff1a; <div class"banner_box"><div class"swiperWrapper" v-show"bannerList.length>0"><div class"swiper-item" :id"swiperSlide${index}" :class"{active:index0,next:index1,pr…