数据结构 -- 二叉搜索树

二叉搜索树

概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于等于根节点的值。
  • 若它的右子树不为空,则右子树上所有节点的值都大于等于根节点的值。
  • 它的左右子树也分别为二叉搜索树。
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景来定义,在后续我们学习的 map / set / multimap / multiset 系列容器底层就是二叉搜索树,其中 map / set 不支持插入相等值, multimap / multiset 支持插入相等值。


性能分析

最优情况下,二叉搜索树为完全二叉树(或接近完全二叉树),其高度为:logN。

最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为N。

综合而言,二叉搜索树增删查改时间复杂度为O(N)。

那么这样的效率显然是无法满足我们的需求的,我们后续课程需要继续讲解二叉搜索树的变形:平衡二叉搜索树(AVL树)和红黑树,这两种才能适用于我们在内存中存储和搜索数据。

另外需要说明的是,二分查找也可以实现O(logN)级别的查找效率,但是存在两大缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

因此后续我们会学习平衡二叉搜索树,二叉搜索树是为了后面的学习打基础。


代码演示:

namespace Key
{//BSNode封装类template<class K>class BSNode{public:K _key;BSNode<K>* _left;BSNode<K>* _right;BSNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}};//BSTree封装类template<class K>class BSTree{typedef BSNode<K> Node;public:bool Insert(const K& key){//根节点为空时if (_root == nullptr){_root = new Node(key);return true;}// 根节点不为空Node* cur = _root;Node* Parent = nullptr;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 > cur->_key)Parent->_left = cur;elseParent->_right = cur;return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return true;}}return false;}bool Erase(const K& key){Node* cur = _root;Node* Parent = nullptr;while (cur){if (cur->_key > key){Parent = cur;cur = cur->_left;}else if (cur->_key < key){Parent = cur;cur = cur->_right;}else// 找到了{// 情况1、2、3if (cur->_left == nullptr)	//左孩子为空{if (Parent == nullptr){_root = cur->_right;}else{if (Parent->_left == cur){Parent->_left = cur->_right;}else{Parent->_right = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr)//右孩子为空{if (Parent == nullptr){_root = cur->_left;}else{if (Parent->_left == cur){Parent->_left = cur->_left;}else{Parent->_right = cur->_left;}}delete cur;return true;}else //都不为空   情况4{Node* replaceParent = cur;Node* replace = cur->_right;while (replace->_left){replaceParent = replace;replace = replace->_left;}//替换法cur->_key = replace->_key;if (replaceParent->_left == replace)//连接代替节点的父子节点replaceParent->_left = replace->_right;elsereplaceParent->_right = replace->_right;//删除替代节点delete replace;return true;}}}return false;}void Inorder(){_Inorder(_root);}private:void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_key <<" ";_Inorder(root->_right);}private:Node* _root = nullptr;};
}
  • BSNode 是一个模板类,表示二叉树的节点。每个节点包含一个关键字 _key 和两个指向左右子节点的指针 _left_right。节点的构造函数接受一个关键字 key 并初始化左右子节点为空指针。
  • BSTree 是一个模板类,表示二叉搜索树。其成员函数包括插入 (Insert)、查找 (Find)、删除 (Erase) 和中序遍历 (Inorder) 树。私有成员 _root 用于存储树的根节点。
  • 该方法通过比较待插入的值与当前节点的值,沿着树的左右子树不断向下寻找合适的位置,最终将节点插入。插入时需要更新父节点的指针。插入的具体过程如下:
    1. 树为空,则直接新增结点,赋值给 root 指针
    2. 树不空,按二叉搜索树性质,插入值比当前结点大往右邹,插入值比当前结点小往左走,找到空位置,插入新结点。
    3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右走,⼀会往左走。
  • 查找函数根据关键字进行二分搜索,通过与当前节点值的比较决定向左子树还是右子树继续查找。查找具体过程如下:
    1. 从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
    2. 最多查找高度次,走到到空,还没找到,这个值不存在。
    3. 如果不支持插⼊相等的值,找到x即可返回。
    4. 如果支持插入相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找到3,要找到1的右孩子的那个3返回。

  • 删除函数具体过程如下:
    首先查找元素是否在二叉搜索树中存在,如果不存在,则返回false。
    如果查找的元素存在则分为下面四种情况进行处理:(假设要删除的节点值为N)
    1.要删除的节点N的左右孩子均为nullptr。
    2.要删除的节点N的左孩子为nullptr,右孩子不为nullptr。
    3.要删除的节点N的右孩子为nullptr,左孩子不为nullptr。
    4.要删除的节点N的左右孩子节点均不为nullptr。
    对应上述四种情况,作出以下方案:
    第一种:把N节点的父亲节点对应孩子指针指向空,直接删除节点。或者我们可以把第一种当做第二或三种情况来处理。
    第二种:把N节点的父亲指向原删除孩子的指针指向N的右孩子,直接删除N节点。
    第三种:把N节点的父亲指向原删除孩子的指针指向N的左孩子,直接删除N节点。
    第四种:无法直接删除N节点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值的最大节点(最右节点)R或是N右子树的值的最小节点(最左节点)R来代替N,因为这两个节点中的任意一个,放到N的位置,都能满足二叉搜索树的规则。替代N的意思就是N和R的两个节点值交换,转而删除R节点,R因为符合情况1/2/3,所以可以直接删除。
  • 中序遍历函数 _Inorder 会递归遍历左子树,访问当前节点,再递归遍历右子树。这样遍历的结果是按顺序输出树中的所有节点。

 二叉搜索树使用场景

key搜索场景

        只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉搜索树支持增删查,但是不支持修改,修改key会破坏搜索树的结构。

场景1:校区无人值守车库,校区车库买了车位的业主才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进入时扫描车牌在不在系统中,在就允许进入,不再则不能进入。

场景2:检查一篇英文文章单词拼写是否正确,将词库所有单词放入二叉搜索树,读取文章中的单词,查找是否在二叉搜索树中,不再则波浪线标红提示。

key&value搜索场景

        每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不支持修改key,修改key破坏搜索树性质了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和vlaue(中文),搜索时输⼊英文,则同时查找到了英文对应的中文。
场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌和入场时间,出口离场时,扫描车牌,查找入场时间,用当前时间减入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。


 key&value二叉搜索树

代码演示:

namespace key_value
{//BSNode封装类template<class K, class V>class BSNode{public:K _key;V _value;BSNode<K, V>* _left;BSNode<K, V>* _right;BSNode(const K& key, const V& value):_key(key),_value(value), _left(nullptr), _right(nullptr){}};//BSTree封装类template<class K, class V>class BSTree{typedef BSNode<K, V> Node;public://默认构造BSTree() = default;//拷贝构造BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}//赋值重载BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}bool Insert(const K& key, const V& value){//根节点为空时if (_root == nullptr){_root = new Node(key, value);return true;}// 根节点不为空Node* cur = _root;Node* Parent = nullptr;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, value);if (Parent->_key > cur->_key)Parent->_left = cur;elseParent->_right = cur;return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* cur = _root;Node* Parent = nullptr;while (cur){if (cur->_key > key){Parent = cur;cur = cur->_left;}else if (cur->_key < key){Parent = cur;cur = cur->_right;}else// 找到了{// 情况1、2、3if (cur->_left == nullptr)	//左孩子为空{if (Parent == nullptr){_root = cur->_right;}else{if (Parent->_left == cur){Parent->_left = cur->_right;}else{Parent->_right = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr)//右孩子为空{if (Parent == nullptr){_root = cur->_left;}else{if (Parent->_left == cur){Parent->_left = cur->_left;}else{Parent->_right = cur->_left;}}delete cur;return true;}else //都不为空   情况4{Node* replaceParent = cur;Node* replace = cur->_right;while (replace->_left){replaceParent = replace;replace = replace->_left;}//替换法cur->_key = replace->_key;if (replaceParent->_left == replace)//连接代替节点的父子节点replaceParent->_left = replace->_right;elsereplaceParent->_right = replace->_right;//删除替代节点delete replace;return true;}}}return false;}void Inorder(){_Inorder(_root);}private:void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_key << ":"<<root->_value<<endl;_Inorder(root->_right);}void Destroy(Node* root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){//拷贝使用前序遍历if (root == nullptr)return;Node* newnode = new Node(root->_key, root->_value);newnode->_left = Copy(root->_left);newnode->_right = Copy(root->_right);return newnode;}private:Node* _root = nullptr;};
}
  • BSNode 是一个模板类,用于表示树中的每个节点。每个节点保存一个键值对(keyvalue),以及指向其左子节点和右子节点的指针。
  • BSTree 是一个模板类,表示二叉搜索树。它具有以下成员函数:
    构造函数:包括默认构造函数、拷贝构造函数和赋值重载。
    插入函数Insert,将键值对插入到树中。
    查找函数Find,根据键查找节点。
    删除函数Erase,根据键删除节点。
    遍历函数Inorder,中序遍历树并输出节点的键值对。

  • Insert 函数通过比较键的大小来决定将新节点插入到树中的位置。如果找到一个相同的键,则不插入。

  • Find 函数根据键值进行二分查找,返回匹配的节点指针。如果没有找到对应的节点,返回 nullptr

  • Erase 函数根据节点的子树情况处理:
    左右子树均为空:按一个子数为空处理。
    左子树为空:将右子树直接连接到父节点。
    右子树为空:将左子树直接连接到父节点。
    左右子树都不为空:找到右子树的最小节点,并替换当前节点。

  • Inorder 函数是树的中序遍历,通过递归打印节点的键值对,按升序输出。

  • Destroy 函数递归销毁树的所有节点,而 Copy 函数递归拷贝树的结构,生成一棵新的树。

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

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

相关文章

Springboot配置全局异常通用返回

Springboot配置全局异常通用返回 前言 前端对接了部分接口后&#xff0c;开始抱怨&#xff0c;“后端接口出参的格式总是千奇百怪&#xff0c;没有一个固定的格式&#xff0c;错误信息提示也不明朗&#xff0c;业务的状态码总是东一个西一个&#xff0c;前端这都不好做统一的…

以太坊系地址衍生算法分层确定性生成逻辑

文章目录 一、前言1.1 衍生算法生成的私钥1.2 随机生成的私钥二、私钥生成及私钥提取2.1 golang如何使用衍生算法生成私钥,然后为用户生成地址2.1.1 实现步骤2.1.2 golang代码示例2.1.3 代码说明2.2 地址交易时,如何提取地址私钥2.2.1 私钥恢复说明2.2.2 golang代码通过助记词…

前端怎么获取视口大小

方式一&#xff1a;使用 window.innerWidth 和 window.innerHeight 这两个属性分别返回浏览器窗口的视口宽度和高度&#xff08;单位为像素&#xff09;&#xff0c;包括滚动条占用的空间&#xff08;如果有的话&#xff09;。 例如&#xff1a; const viewportWidth window.…

D3 可以加载的数据格式有哪些?(12种)

D3.js 支持多种数据格式&#xff0c;这些格式涵盖了从简单的表格数据到复杂的地理数据。以下是一些常见的数据格式及其加载方法&#xff1a; D3.js 数据加载方法 d3.blob(input, init) 用途: 加载二进制数据&#xff0c;返回一个 Blob 对象。参数: input: 数据源 URL。init: …

stm32在linux环境下的开发与调试

环境安装 注&#xff1a;文末提供一键脚本 下载安装stm32cubeclt 下载地址为&#xff1a;https://www.st.com/en/development-tools/stm32cubeclt.html 选择 linux版本下载安装 安装好后默认在家目录st下 > $ ls ~/st/stm32cubeclt_1.16.0 …

Go语言中的类型

Go语言是一种静态类型的编程语言&#xff0c;这意味着每个变量和表达式在编译时都有确定的类型。Go提供了丰富的内置类型&#xff0c;包括基本数据类型、复合数据类型和引用类型等。本文将详细介绍Go中的各种类型&#xff0c;并提供实例说明。 基本数据类型 布尔型&#xff0…

【从零开始的LeetCode-算法】3270. 求出数字答案

给你三个 正 整数 num1 &#xff0c;num2 和 num3 。 数字 num1 &#xff0c;num2 和 num3 的数字答案 key 是一个四位数&#xff0c;定义如下&#xff1a; 一开始&#xff0c;如果有数字 少于 四位数&#xff0c;给它补 前导 0 。答案 key 的第 i 个数位&#xff08;1 < …

⾃动化运维利器 Ansible-变量

⾃动化运维利器Ansible-变量 一、变量命名规则二、变量的类型2.1 全局变量2.3 剧本变量2.4 资产变量2.5 facts变量2.6 注册变量 三、变量的优先级 按顺序食用&#xff0c;口味更佳 ( 1 ) ⾃动化运维利器Ansible-基础 ( 2 ) ⾃动化运维利器 Ansible-Playbook ( 3 ) ⾃动化运维利…

蓝桥杯每日真题 - 第12天

题目&#xff1a;&#xff08;数三角&#xff09; 题目描述&#xff08;14届 C&C B组E题&#xff09; 解题思路&#xff1a; 给定 n 个点的坐标&#xff0c;计算其中可以组成 等腰三角形 的三点组合数量。 核心条件&#xff1a;等腰三角形的定义是三角形的三条边中至少有…

项目技术栈-解决方案-注册中心

项目技术栈-解决方案-注册中心 ZookeeperEureka、Nacos、Consul和Etcd参考文章 服务注册中心&#xff08;Registry&#xff09;&#xff1a;用于保存 RPC Server 的注册信息&#xff0c;当 RPC Server 节点发生变更时&#xff0c;Registry 会同步变更&#xff0c;RPC Client 感…

uniapp适配暗黑模式配置plus.nativeUI.setUIStyle适配DarkMode配置

uniapp适配暗黑模式配置 目录 uniapp适配暗黑模式配置setUIStyleDarkMode 适配app-plus manifest.json配置theme.json配置pages.json配置页面切换代码实现同步手机暗黑配置额外适配 参考官方文档&#xff1a;https://uniapp.dcloud.net.cn/tutorial/darkmode.html 主要用到api…

Wireshark中的length栏位

注&#xff1a;Ethernet II的最小data length为46&#xff0c;如果小于&#xff0c;会补全到46. 1.指定网卡抓取的&#xff0c;链路为ethernet。 IPv4 Ethernet II 长度为 14 bytes - L1ipv4 header中的length包括header和payload的总长度 - L2wireshark中length表示抓取的pac…

Spring 4.3 源码导读

Spring Framework 是一个功能强大的开源框架&#xff0c;广泛用于企业级 Java 应用程序开发。Spring 4.3 是一个成熟的版本&#xff0c;提供了丰富的功能和模块。以下是对 Spring 4.3 源码的一些关键部分的导读&#xff0c;帮助你更好地理解其架构和设计理念。 1. 源码获取 从…

前端开发迈向全栈之路:规划与技能

一、前端开发与全栈开发的差异 前端开发主要负责构建和实现网页、Web 应用程序和移动应用的用户界面。其工作重点在于网页设计和布局&#xff0c;使用 HTML 和 CSS 技术定义页面的结构、样式和布局&#xff0c;同时运用前端框架和库如 React、Angular 或 Vue.js 等构建交互式和…

后端总指挥---文件接口

为了方便编写项目时前后端的连接&#xff0c;在这里引进文件接口的概念&#xff0c;通常包含以下几个部分&#xff1a; 功能请求路径请求方式传输的数据类型请求入参数据样本响应的出参响应成功后的数据样本 这样前端编程与后端编程 可以根据文件接口这个文档来分别编写前后端&…

ISUP协议视频平台EasyCVR视频设备轨迹回放平台智慧农业视频远程监控管理方案

在当今快速发展的农业领域&#xff0c;智慧农业已成为推动农业现代化、助力乡村全面振兴的新手段和新动能。随着信息技术的持续进步和城市化进程的加快&#xff0c;智慧农业对于监控安全和智能管理的需求日益增长。 视频设备轨迹回放平台EasyCVR作为智慧农业视频远程监控管理方…

通过wsl配置Qt的中文开发环境

本文列举了一些在WSL上配置Qt编译环境中遇到的一些问题及解决方案 关键词: Win10 22HUbuntu18.04xfce桌面交叉编译arm64qt / qtcreator中文输入及显示标题 安装qt,qtcreatorqt.qpa.screen: QXcbConnection: Could not connect to displayld: cannot find -lGLqmake: could no…

前端web

题目&#xff1a;制作带有下拉悬停菜单的导航栏 效果图 一、先制作菜单栏 <body> <div id"menu"> <div id"container"> <div class"item">游戏1 <div cla…

PostgreSQL 跨数据库访问

有这样的一个业务场景。有一个表对象在数据库 A 中&#xff0c;但是在同一个实例的数据库 B 中我想访问位于 A 库中的这个对象&#xff0c;那么有什么好的方法可以实现呢。 假设在数据库 cloud_test 中有如下的对象&#xff0c;现在想在数据库 cloud_health 中访问这个对象。 …

《应用概率统计》

《应用概率统计》主要有综合报告、学术论文、应用研究、应用简报、教学研究、学术活动报道、书评、新书介绍等栏目。 一、《应用概率统计》是中国数学会概率统计学会主办的全国性学术刊物&#xff0c;主要刊登概率论与数理统计的理论和应用两方面有创造性的学术论文&#xff0c…